Twitter | LinkedIn | YouTube | Instagram
Ця стаття також доступна на YouTube!
Сьогодні я вирішив запитати у людей на Twitter, яка система черг повідомлень (Message Queue) першою приходить їм на думку. І, на моє здивування, однією з відповідей була: Postgres.
Я відкрив посилання і був здивований не лише можливістю використовувати Postgres як брокер повідомлень (Message Broker):
"Використовуйте Postgres як чергу повідомлень із параметром SKIP LOCKED замість Kafka (якщо вам потрібна лише черга повідомлень)". — Стефан Шмідт
Але також… можливістю використовувати Postgres як кеш на заміну Redis:
"Використовуйте Postgres для кешування замість Redis за допомогою UNLOGGED таблиць і текстового типу JSON (TEXT as JSON). Використовуйте збережені процедури (Stored Procedures) або, як я, залучайте ChatGPT для написання процедур, щоб додати й забезпечити термін дії даних, як у Redis". — Стефан Шмідт
І це справді здивувало мене, адже в моєму досвіді вивчення Redis я часто чув, що багато хто (з прихильників Redis) стверджує: "Redis — це база даних, і вона має бути вашою основною базою даних".
І це дійсно має сенс. Redis — це справжня база даних, яка просто відмінно працює як кеш. Причина її ефективності як кешу — швидкість. Вона надзвичайно швидка. До такої міри, що може виконувати мільйони операцій за одну секунду.
І, власне… читати про те, що Postgres, моя улюблена реляційна база даних, тепер може замінити Redis, мою улюблену нереляційну базу даних, трохи перевернуло моє уявлення про світ.
Отже, чи варто мені замінити Redis на Postgres або Postgres на Redis?
Але перед тим, як розглянути це питання, я хотів зрозуміти: чи дійсно Postgres як кеш — це хороша ідея? Чи може він справді замінити Redis? Саме це я хочу дізнатися сьогодні.
Стаття, яку мені надіслали й яка, як я пізніше дізнався, стала популярною на Twitter, була написана Стефаном Шмідтом.
Стефан не лише закликає замінити Redis на Postgres, він фактично пропонує замінити все на Postgres. За його словами, це дозволить зменшити складність і працювати швидше.
"Просто використовуйте Postgres для всього (як зменшити складність і прискорити роботу)" — Стефан Шмідт
Однак, він не єдиний, хто виступає за заміну Redis, і справді, кілька інших людей також це підтримують:
Але чому, власне, мені варто замінити Redis на Postgres?
Стефан уже назвав дві причини: менша складність і швидші зміни. Але, можливо, є щось більше?
Використання Postgres як кешу — не найпоширеніше рішення, але є певні сценарії, де це може мати сенс. Розглянемо деякі з них:
Єдиний технологічний стек
Postgres — одна з найпопулярніших баз даних. Вона безкоштовна, з відкритим кодом, і, швидше за все, ви вже використовуєте її у своїй програмі.
Використання її як кешу може спростити ваш технологічний стек, зменшивши необхідність керувати та підтримувати кілька систем баз даних.
Знайомий інтерфейс
Postgres підтримує складні запити й індексацію. Це полегшує виконання завдань із отримання та трансформації даних безпосередньо на рівні кешу. Використання SQL для кешування може бути вигідним, якщо ваша команда вже добре володіє SQL, що, ймовірно, є правдою.
Вартість
У деяких випадках використання наявних ресурсів Postgres для кешування може бути більш економічним, ніж розгортання окремого рішення для кешування, такого як Redis. Використання Postgres як для основного зберігання, так і для кешування може призвести до кращого використання ресурсів, особливо в умовах обмежених бюджетів на інфраструктуру.
Що варто очікувати від сервісу кешування?
Традиційні сервіси кешування, такі як Redis, пропонують набір функцій, які підвищують продуктивність і масштабованість наших додатків. Щоб зрозуміти, чи дійсно Postgres може замінити Redis, нам потрібно знати, які функції забезпечують ці сервіси кешування. Ось кілька ключових аспектів, які ми очікуємо від сервісу кешування:
Продуктивність
Основна мета сервісів кешування — підвищення продуктивності додатків шляхом прискорення доступу до даних.
Високопродуктивні рішення для кешування можуть обробляти великі навантаження й забезпечувати час відповіді менше мілісекунди, значно прискорюючи процеси, які отримують дані.
Термін дії (Expiration)
Встановлюючи термін дії для кешованих даних, ми можемо забезпечити автоматичне видалення застарілих даних із кешу після визначеного періоду. Запобігання видачі застарілих даних додаткам є важливим аспектом для будь-якого сервісу кешування.
Витіснення (Eviction)
Сервіси кешування зазвичай зберігають свої дані в пам’яті, яка історично має більше обмежень. Через це налаштування політики витіснення дозволяє автоматично видаляти менш використовувані дані для звільнення місця для нових записів.
Зберігання у форматі ключ-значення
Основою більшості сервісів кешування є зберігання даних у вигляді пар "ключ-значення" (Key-Value Storage). Ця проста, але потужна модель дозволяє швидко отримувати дані, що полегшує зберігання й доступ до часто використовуваних даних.
Коротко кажучи, від сервісу кешування очікується, що він дозволить швидше отримувати доступ до даних і забезпечить, щоб дані були максимально актуальними.
Як перетворити Postgres на кеш?
За словами Мартіна Хайнца, який описав це у своєму блозі [Вам не потрібен окремий сервіс для кешування — PostgreSQL як кеш], майже всі функції, які ми згадували у попередньому розділі, можна реалізувати й у Postgres.
І Стефан, і Мартін стверджують, що ми можемо зробити з Postgres сервіс кешування, використовуючи UNLOGGED таблиці. Майже всі приклади, які я далі покажу, взято з публікації Мартіна.
Unlogged таблиці та Write Ahead Log
Unlogged таблиці в Postgres дозволяють уникнути генерації WAL (Write Ahead Log) для конкретних таблиць.
WAL, у свою чергу, забезпечує запис усіх змін, зроблених у базі даних, перед тим, як ці дані потрапляють у файли бази. Це допомагає підтримувати цілісність даних, особливо у випадку збою чи втрати живлення.
Цікавий факт: Redis пропонує схожий механізм під назвою Append Only File (AOF), який не тільки дозволяє зберігати дані в Redis, але й працює подібним чином, ведучи лог усіх операцій, виконаних у Redis. Якщо Redis використовується як основна база даних, ми вмикаємо AOF, тоді як для використання Postgres як кешу, ми вимикаємо (для певних таблиць) WAL.
Вимкнення WAL підвищує продуктивність
При кожній модифікації даних Postgres має записати зміни як у WAL, так і у файл даних. Це подвоює кількість операцій запису, які необхідно виконати.
Крім того, щоб забезпечити фізичний запис кожної завершеної транзакції на диск, WAL налаштований на примусове очищення кешу диска (fsync). Часті операції очищення кешу диска впливають на продуктивність, оскільки викликають затримку, чекаючи на підтвердження запису даних на диск.
Це також означає відмову від збереження даних
Основне, що слід знати про unlogged таблиці, — вони не зберігаються постійно.
Це тому, що Postgres використовує WAL для відтворення і застосування будь-яких змін, зроблених з моменту останньої контрольної точки (checkpoint). Якщо ми не ведемо цей лог, база даних не зможе відновити послідовність стану, відтворивши записи з WAL. У випадку з кешем, це, мабуть, очікувана ситуація, чи не так?
CREATE UNLOGGED TABLE cache (
id serial PRIMARY KEY,
key text UNIQUE NOT NULL,
value jsonb,
inserted_at timestamp);
CREATE INDEX idx_cache_key ON cache (key);
Застарівання (Expiration) за допомогою збережених процедур
Як зазначають Мартін і Стефан, застарівання (Expiration) можна реалізувати за допомогою збережених процедур (Stored Procedures). І ось тут починається складність.
Збережені процедури можуть бути складними у реалізації, і Стефан навіть пропонує використовувати ChatGPT для їх написання, натякаючи на їхню потенційну складність.
CREATE OR REPLACE PROCEDURE expire_rows (retention_period INTERVAL) AS
$$
BEGIN
DELETE FROM cache
WHERE inserted_at < NOW() - retention_period;
COMMIT;
END;
$$ LANGUAGE plpgsql;
CALL expire_rows('60 minutes'); -- Це видалить рядки, старіші за 1 годину
Правда в тому, що більшість сучасних додатків уже не покладаються на збережені процедури, і багато розробників не рекомендують їх використовувати.
Зазвичай, причина полягає в тому, що ми хочемо уникнути витоку бізнес-логіки в базу даних. Крім того, зі збільшенням кількості збережених процедур їх управління та розуміння стають обтяжливими.
Крім цього, нам також потрібно викликати ці збережені процедури за розкладом. І для цього потрібне розширення під назвою pg_cron
.
Після встановлення розширення нам ще потрібно створити свої розклади:
-- Створення розкладу для запуску процедури щогодини
SELECT cron.schedule('0 * * * *', $$CALL expire_rows('1 hour');$$);
-- Перегляд усіх запланованих завдань
SELECT * FROM cron;
Складність зростає, чи не так?
Витіснення (Eviction) за допомогою збережених процедур
Стефан навіть не згадує про витіснення (Eviction) у своїй статті, тоді як Мартін зазначає, що це може бути необов’язковим, оскільки застарівання (Expiration) вже дозволить контролювати розмір кешу.
Якщо все ж потрібно увімкнути витіснення, він пропонує додати стовпець last_read_timestamp
до таблиці й запускати ще одну збережену процедуру час від часу, щоб реалізувати політику витіснення "останній нещодавно використаний" (LRU, Last Recently Used).
CREATE OR REPLACE PROCEDURE lru_eviction(eviction_count INTEGER) AS
$$
BEGIN
DELETE FROM cache
WHERE ctid IN (
SELECT ctid
FROM cache
ORDER BY last_read_timestamp ASC
LIMIT eviction_count
);
COMMIT;
END;
$$ LANGUAGE plpgsql;
-- Виклик процедури для видалення заданої кількості рядків
CALL lru_eviction(10); -- Це видалить 10 рядків, до яких найдавніше зверталися
Redis пропонує вісім типів політик витіснення за замовчуванням. Потрібен інший вид політики витіснення для вашого "Postgres Cache"? Просто запитайте ChatGPT.
А як щодо продуктивності?
Продуктивність тут найважливіша, чи не так? Зрештою, причина, через яку ми зазвичай хочемо використовувати кеш, полягає в тому, що ми хочемо отримувати доступ до даних швидше.
Грег Сабіно Маллане чудово описав у своїй статті [PostgreSQL Unlogged Tables — Look Ma, No WAL!], порівнюючи продуктивність НЕЗАПИСАНИХ (UNLOGGED) і ЗАПИСАНИХ (LOGGED) таблиць у Postgres. Його дані показують, що продуктивність запису в НЕЗАПИСАНІ таблиці вдвічі швидша, ніж у ЗАПИСАНІ. Зокрема:
Незаписана таблиця:
• Затримка: 2.059 мс
• TPS (Транзакції в секунду): 485,706168
Записана таблиця:
• Затримка: 5.949 мс
• TPS: 168,087557
А як щодо продуктивності читання?
І ось тут найцікавіше.
Стратегія оптимізації продуктивності Postgres покладається на спільні буфери (Shared Buffers).
Спільні буфери зберігають часто використовувані дані та індекси безпосередньо в пам'яті, що забезпечує швидкий доступ і зменшує потребу в читанні з диска. Це покращує продуктивність запитів і доступ до даних як для записаних (logged), так і незаписаних (unlogged) таблиць.
Хоча незаписані таблиці можуть перебувати в цих буферах, вони все ж будуть записані на диск, якщо їх розмір занадто великий або пам’яті обмаль. Тому незаписані таблиці покращують швидкість запису, але не читання.
Щоб довести це, я провів швидкий експеримент за допомогою pgbench
. Ось як я це зробив.
Результати показують, що продуктивність як записаних, так і незаписаних таблиць насправді дуже схожа. Читання з обох типів таблиць зайняло приблизно 0.650 мс у середньому. Зокрема:
Незаписана таблиця:
• Затримка: 0.679 мс
• TPS: 14.724,204
Записана таблиця:
• Затримка: 0.627 мс
• TPS: 15.946,025
Ці результати підтверджують, що незаписані таблиці в основному покращують продуктивність запису. Для операцій читання переваги незаписаних таблиць не такі помітні, оскільки і записані, і незаписані таблиці однаково виграють від кешування і стратегій оптимізації Postgres.
Як продуктивність порівнюється з Redis?
Окрім тестування Postgres, я також провів експеримент із Redis. Деталі експерименту можна переглянути тут. Результати для Redis демонструють значну перевагу в продуктивності як для операцій читання, так і запису:
Читання:
• Затримка (p50): 0.095 мс
• Запитів за секунду (RPS): 892.857,12
Запис:
• Затримка (p50): 0.103 мс
• Запитів за секунду (RPS): 892.857,12
Порівняння продуктивності показує, що Redis значно випереджає Postgres як у записі, так і в операціях читання:
Redis досягає затримки у 0.095 мс, що приблизно на 85% швидше, ніж затримка у 0.679 мс для незаписаних таблиць (unlogged tables) в Postgres.
Redis також обробляє набагато більше запитів за секунду, з показником 892.857,12 запитів на секунду порівняно з 15.946,025 транзакціями на секунду у Postgres.
А для операцій запису, враховуючи значно вищу пропускну здатність і нижчу затримку, також можна побачити, що Redis забезпечує кращу продуктивність.
Що, якщо я запущу Postgres у RAM?
Під час перегляду цієї статті колега з Xebia, Максим Федоров, запитав:
“А що, якщо створити незаписані таблиці в просторі таблиць (tablespace), що відповідає файлу, змапленому в пам'ять? Мій здогад, що ми побачили б зовсім інші результати.”
Щоб перевірити це, я провів тести з Postgres, дані якого зберігалися в RAM. Дивно, але результати не покращилися. Тест показав:
Читання:
• Затримка: 0.652 мс
• Запитів за секунду (TPS): 15.329,776954
Після подальших досліджень я зрозумів, що, хоча дані зберігаються в RAM, доступ до них через спільні буфери Postgres (shared buffers) потребує додаткових витрат. Ці витрати виникають через керування блокуваннями та іншими внутрішніми процесами, необхідними для збереження цілісності даних і доступу до них у багатокористувацькому режимі.
Postgres завжди спершу перевіряє, чи дані знаходяться в спільних буферах. Якщо ні, то копіює дані з файлової системи tmpfs у спільні буфери перед тим, як надати їх, навіть коли база даних зберігається в RAM.
Чи варто замінити Redis на Postgres?
На основі цього дослідження, якщо вам потрібен сервіс кешування для підвищення швидкості запису, Postgres можна оптимізувати за допомогою незаписаних таблиць. Однак, незважаючи на те, що незаписані таблиці пропонують кращу продуктивність запису, ніж записані таблиці, вони все одно поступаються Redis.
Основна причина для використання сервісу кешування – це покращення часу отримання даних. Незаписані таблиці (unlogged tables) не покращують продуктивність читання, тоді як Redis відзначається надзвичайно швидкими операціями читання.
Крім того, Redis допомагає уникнути великої кількості маловартісних запитів до вашої бази даних, чого не можуть забезпечити незаписані таблиці. Redis також пропонує вбудовані функції, такі як експірація (expiration), політики витіснення (eviction policies) та інші, які складно реалізувати в Postgres.
Хоча для когось управління Postgres може здаватися простішим, перетворення його на кеш не дає переваг, які забезпечує спеціалізований сервіс кешування. Водночас Redis є легким у навчанні, розгортанні та використанні.
Для швидшої роботи і простоти вибір спеціалізованого сервісу кешування, такого як Redis, є очевидним рішенням.
Сподіваюсь, вам сподобалась ця стаття! Я отримав масу задоволення, проводячи це дослідження!
Особлива подяка Максиму Федорову, Жоао Пауло Гомесу та Гернані Фернандесу за перегляд цієї статті.
Залишайтеся допитливими!
Підтримати автора
Написання потребує часу та зусиль. Я люблю писати та ділитися знаннями, але в мене також є рахунки, які потрібно сплачувати. Якщо вам подобається моя робота, будь ласка, розгляньте можливість підтримати мене на Buy Me a Coffee: https://www.buymeacoffee.com/RaphaelDeLio
Або надішліть BitCoin на адресу: 1HjG7pmghg3Z8RATH4aiUWr156BGafJ6Zw
Підписуйтесь на мене в соцмережах
Залишайтеся на зв’язку та глибше занурюйтесь у світ Redis разом зі мною! Слідкуйте за моїм шляхом на всіх основних соціальних платформах для ексклюзивного контенту, порад та обговорень.
Twitter | [LinkedIn](https://www.linkedin.LinkedIn | YouTube | Instagram
Перекладено з: Can Postgres replace Redis as a cache?