Більшість дискусій про бази даних починаються як філософія і закінчуються як інцидент-репорт. Ваш додаток працює — поки вам не знадобляться дві речі одночасно: семантика Postgres і глобальна доступність. Тоді настає розрахунок. Іноді це рахунок за продуктивність. Іноді — за операційну складність. Іноді — за «чому сторінка оформлення замовлення зависає тільки в Сан-Паулу?».
Якщо ви вирішуєте між «один Postgres, добре налаштований» і «розподілений Postgres-стиль, з Raft, по всьому світу», ось погляд з продакшну: що ламається, що дороге, що блискуче, а що — надмірність.
Що ви насправді обираєте (це не про функції)
На папері PostgreSQL і YSQL від YugabyteDB можуть виглядати як «SQL з таблицями, індексами, транзакціями, JSON і планувальником запитів». Але рішення не в цьому. Рішення в тому:
- Чи хочете ви масштабуватися переважно шляхом покращення одного екземпляра бази (більша машина, швидші диски, оптимізовані запити, репліки для читання, партиціонування), або
- Чи хочете ви масштабуватися, розподіляючи зберігання та консенсус по вузлах, приймаючи вищу базову складність заради стійкості до відмов і горизонтального росту.
PostgreSQL — золотий стандарт для «простого, що може бути складним». Він дає змогу тримати чисту продакшн-систему, де робота з продуктивністю — це здебільшого індекси, vacuum, пам’ять і уникання дурних піків підключень.
YugabyteDB — це розподілена SQL база з сумісним API Postgres (YSQL), побудована на розподіленому шарі зберігання (DocDB) і консенсусі Raft. Вона прагне відповідати, коли ваші вимоги виглядають як список вимог: «має переживати втрату вузла, має масштабувати записи, має працювати в зонах/регіонах, має бути Postgres-подібною і зберігати ACID».
Гострий кінець: розподілена база змінює «фізику» вашого додатка. Багато запитів стають «трохи мережевими». Деякі — «дуже мережевими». Ви також почнете звертати увагу на розміщення, лідерів, розділення планшетів і різницю між локальними та глобальними транзакціями. SQL лишається — але ви міняєте один тип експертизи на інший.
Правило великого пальця, яке я відстоюватиму: Якщо ваш найбільший біль — «ми не можемо тримати Postgres вгору» або «ми постійно добираємося до CPU на одному записувальному вузлі», не починайте з розподіленого SQL. Почніть з нудного, але правильного Postgres. Якщо ваш головний біль — «наш бізнес вимагає active‑active по зонах/регіонах з низьким RPO і без вузького місця єдиного записувача», тоді YugabyteDB (або подібне) стає реальною опцією.
Коротка історія та факти, що важливі в продакшні
Трохи контексту допомагає зрозуміти маркетинг. Ось конкретні факти, які змінюють спосіб експлуатації цих систем:
- Корені PostgreSQL сягають POSTGRES (1986), задовго до того, як «cloud native» стало модним словосполученням. Сучасний проект PostgreSQL випускає надійні релізи десятиліттями, а культура цінує коректність більше за новизну.
- MVCC у Postgres (мультиверсійний контроль паралельності) — причина, чому читачі зазвичай не блокують записувачів. Це також причина існування vacuum і того, що «мертві кортежі» — реальна операційна річ.
- Потокова реплікація (WAL‑базована) стала масовою в еру Postgres 9.x, що зробило практичними гарячі стендаби. Це сформувало стандартний шаблон HA: один primary, репліки, оркестрація failover.
- Фізична реплікація Postgres — це «все або нічого» на рівні екземпляра. Вона чудова для failover кластера в цілому, але не для шардингу навантаження записів по багатьом primary без ретельної фрагментації на рівні додатка.
- Raft став робочою конячкою консенсусу в сучасних розподілених системах у 2010‑х. Він популярний, бо простіший для розуміння, ніж Paxos, але не безкоштовний. Кожен запис платить податок координації.
- YugabyteDB використовує розподілений двигун зберігання (DocDB), натхненний LSM‑tree дизайном. Це означає компроміси: компрекції, SSTable‑файли і інші IO‑поведінкові відмінності від heap‑зберігання Postgres.
- «Distributed SQL» — це друга спроба старої ідеї. Бази без спільного доступу та розподілені транзакції — не новинка; нове — краща автоматизація, кращі мережі і готовність витрачати більше CPU, щоб спростити дизайн додатка.
- Google Spanner (2012) нормалізував ідею глобально розподіленого ACID зі сильною консистентністю, ціною — ретельною інженерією часу/консенсусу. Багато систем запозичили амбіцію, хоч реалізації відрізняються.
- Розширення Postgres — це суперсила (PostGIS, pg_stat_statements тощо). У «Postgres‑сумісних» системах підтримка розширень часто часткова, і це змінює, як ви інструментуєте і вирішуєте проблеми.
Жарт №1: Розподілена база даних — це звичайна база даних, яка виявила, що телепортація ненадійна, і тепер затаїла на неї образу.
Архітектурні відмінності, які змінюють ваше чергування
PostgreSQL: одна первинна істина, плюс репліки
У класичному продакшні Postgres є один записувальний primary. Репліки слідують через WAL. Failover піднімає репліку. Читання можна відвантажити, але записи — на одному вузлі.
Це не недолік; це дизайн, який зберігає ядро простішим і передбачуваним. На хороших дисках, з розумною схемою і connection pooling, Postgres багато що витягує. А режими відмов добре відомі: насичення диска, лаг реплікації, блоут, некоректна робота autovacuum, шторм чекпоінтів, погані запити та сплески підключень.
Операційна перевага в тому, що зазвичай ви можете відповісти на «де істина?» — «на primary». Це звучить дрібно, поки ви не відлажуєте гейзенбаг о третій ночі.
YugabyteDB: розподілене зберігання, лідери планшетів і консенсус скрізь
YugabyteDB ділить дані на планшети (shards). Кожен планшет реплікується (звично RF=3). У планшета є лідер і фоловери. Записи проходять через Raft для коміту. Читання можуть обслуговуватися лідерами або фоловерами залежно від налаштувань консистентності і шляху запиту.
Результат: ви можете втрачати вузли і продовжувати обслуговувати трафік. Ви можете масштабувати зберігання і пропускну здатність записів, додаючи вузли. Але ви також вводите:
- Проблеми розміщення лідерів (затримка залежить від того, де розташовані лідери).
- Проблеми «гарячих» планшетів (один шард навантажується і стає вузьким місцем).
- Фонові роботи (компакти, розділення планшетів, ребалансування), що конкурують із запитами в передньому плані.
- Більше способів бути «вгору», але повільним (кворум існує, але хвостова латентність може бути жахливою).
Операційна модель змінюється. Ви керуєте не просто процесом бази даних; ви керуєте невеликою розподіленою системою з власною контрольною площиною і семантикою відмов.
Відмінності двигунів зберігання: heap+WAL проти LSM‑подібної реальності
Postgres зберігає рядки в heap‑файлах, використовує WAL для довговічності і покладається на vacuum для звільнення простору, бо MVCC зберігає старі версії, поки їх не можна безпечно видалити.
Підлягаюча система зберігання YugabyteDB поведінкою ближча до LSM‑tree: записи лог‑структуровані, а потім компактуються. Це часто означає write amplification і борг компактацій, які можуть стати проблемою продуктивності. Це не «гірше», це інакше. Ваші диски і моніторинг мають відповідати цій різниці.
Транзакції: локальні проти розподілених — там оселяється ваша затримка
Транзакції Postgres локальні для екземпляра. Блокування та правила видимості відбуваються в процесі. Мережа не є частиною латентності коміту.
У розподіленій SQL‑системі деякі транзакції зачіпають кілька планшетів. Це означає додаткову координацію. Якщо ці планшети знаходяться в різних зонах/регіонах, шлях коміту включає WAN‑латентність. База все ще може бути коректною. Користувачі — все одно будуть незадоволені.
Практичний наслідок: у YugabyteDB моделювання даних і вибір локальності (tablespaces/placement, партиціонування ключів, шаблони доступу) можуть визначати різницю між «достатньо швидко» і «чому кожний запит на 80 мс повільніший, ніж учора».
Затримка, консистентність і вартість: трикутник, якого не уникнути
Поговоримо про обмеження, з якими не домовишся:
Затримка: базова латентність важливіша за середнє
У Postgres простий індексований пошук може займати кілька сотень мікросекунд до кількох мілісекунд на пристойному обладнанні, за умови попадання в кеш і спокійної системи. Хвостова латентність зазвичай виникає через IO‑стрибки, контенцію блокувань або фонові роботи типу відставання vacuum.
У розподілених системах базова латентність включає координацію. Навіть якщо медіана виглядає нормальною, p95/p99 можуть стрибнути, коли змінюється лідерство, коли вузол має паузу GC, коли компакти різко навантажують IO або коли крос‑планшетна транзакція зачіпає найповільнішого учасника.
Консистентність: що мають на увазі продукт‑менеджери vs що робить ваш код
Postgres дає сильну консистентність всередині екземпляра, а реплікація зазвичай асинхронна, якщо ви не налаштували синхронну реплікацію. Це означає вибір між «без втрат даних при падінні primary» і «не додавати латентність до кожного коміту». Синхронну реплікацію можна ввімкнути, але це бізнес‑рішення, замасковане під конфігурацію.
YugabyteDB зазвичай використовує синхронну реплікацію через Raft для записів у групі планшета. Це сильніша за замовчуванням позиція щодо довговічності, але латентність залежить від розміщення реплік. Ви також можете налаштовувати компроміси для читань. Система дозволить зробити швидко; вона також дозволить зробити дивно.
Вартість: ви платите за стійкість додатковими машинами і роботою
Добре налаштований кластер Postgres може виглядати так: один потужний primary, дві репліки, pooler підключень і хороші бекапи. Левовий важіль масштабування — «більший primary», поки це працює.
Кластер YugabyteDB потребує кількох вузлів навіть для невеликих навантажень, бо надмірність частина дизайну. При RF=3 сирі витрати простору — приблизно в 3 рази (плюс накладні витрати), і потрібен запас на ребалансування та компактації. Ви купуєте операційну плавність під час відмов — але платите вузлами, мережею і складністю.
Жарт №2: Найприємніше в алгоритмах консенсусу те, що вони гарантують згоду — переважно щодо того, як довго триватиме ваш деплой.
Ідея про надійність (перефразована, чесно)
Перефразована думка, приписана John Allspaw: «Реальна поведінка вашої системи — це те, що вона робить під навантаженням і під час відмов, а не те, що обіцяв діаграмний архітект.»
Сумісність з Postgres: що дає «Postgres-стиль» — і чого не дає
YugabyteDB YSQL говорить Postgres‑протоколом і прагне до Postgres‑семантики. Це цінно: існуючі драйвери, ORM‑и та велика база знань по SQL.
Але «сумісний» не означає «ідентичний», і в продакшні ви житимете у відмінностях. Ось місця, де команди спіткалися:
- Розширення: У Postgres розширення вирішують реальні проблеми (observability, типи, індексація). У YugabyteDB підтримка розширень більш обмежена. Якщо ви залежите від конкретного розширення, можливо, доведеться переробляти, а не мігрувати.
- Планувальник і характеристики продуктивності: Навіть якщо запит валідний SQL, витрати на виконання різняться. Розподілені скани і розподілені з’єднання можуть бути «нормальними в staging, дорогими в prod», коли обсяг даних перевищує поріг.
- Нюанси ізоляції і блокувань: Мета — поведінка, схожа на Postgres, але розподілений контроль паралельності має обмеження. Читайте дрібний шрифт щодо рівнів ізоляції і повторів транзакцій.
- Операційні інструменти: Postgres має глибокий набір інструментів. YugabyteDB має власні інструменти, метрики і робочі процеси. Видимість буде гарною, але деякі інстинкти доведеться переймати заново.
Якщо ваш додаток використовує ванільний SQL, стандартні індекси, і ваш головний біль — вихід за межі одного primary, YugabyteDB може здаватися магією. Якщо ваш додаток сильно покладається на Postgres‑специфічні можливості і розширення, розподілений Postgres‑стиль може виглядати як переїзд в іншу квартиру і виявлення, що ваш диван не пролізе в ліфт.
Практичні завдання: команди, виводи, рішення (12+)
Ось реальні перевірки, які я запускаю (або прошу когось запустити), коли вирішую, чи вистачає Postgres, чи кластер YugabyteDB здоровий, або чому щось повільне. Кожна включає: команду, що означає вивід і яке рішення прийняти.
Завдання 1 — Postgres: підтвердити стан реплікації і відставання
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select application_name, state, sync_state, write_lag, flush_lag, replay_lag from pg_stat_replication;"
application_name | state | sync_state | write_lag | flush_lag | replay_lag
------------------+-----------+------------+-----------+-----------+------------
pg-replica-1 | streaming | async | 00:00:00 | 00:00:00 | 00:00:01
pg-replica-2 | streaming | async | 00:00:00 | 00:00:00 | 00:00:02
(2 rows)
Значення: Репліки стрімінгують; відставання по replay — кілька секунд. Рішення: Схема читання і готовності до failover виглядає нормально. Якщо відставання зростає — перевіряйте мережу/диск на репліках або довгі запити, що затримують replay.
Завдання 2 — Postgres: знайти найгірші запити за загальним часом
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select queryid, calls, total_time, mean_time, rows, left(query, 80) as q from pg_stat_statements order by total_time desc limit 5;"
queryid | calls | total_time | mean_time | rows | q
----------+-------+------------+-----------+-------+--------------------------------------------------------------------------------
98122311 | 12000 | 955432.12 | 79.61 | 12000 | select * from orders where customer_id = $1 order by created_at desc limit 50
11220091 | 1500 | 440010.55 | 293.34 | 1500 | update inventory set qty = qty - $1 where sku = $2
(2 rows)
Значення: Один запит домінує за загальним часом; інший має високу середню латентність. Рішення: Оптимізуйте/додайте індекс для першого, якщо він гарячий; дослідіть контенцію або відсутній індекс для оновлення. Якщо це вже «достатньо добре», можливо, вам не потрібен розподілений SQL.
Завдання 3 — Postgres: перевірити тиск блоату і стан vacuum
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select relname, n_dead_tup, n_live_tup, last_vacuum, last_autovacuum from pg_stat_user_tables order by n_dead_tup desc limit 5;"
relname | n_dead_tup | n_live_tup | last_vacuum | last_autovacuum
----------------+------------+------------+----------------------+----------------------
order_events | 8200000 | 51000000 | | 2025-12-30 09:01:12
sessions | 1900000 | 12000000 | 2025-12-29 03:11:55 | 2025-12-30 08:57:48
(2 rows)
Значення: Мертві кортежі великі; autovacuum працює, але може відставати. Рішення: Налаштуйте autovacuum за таблицями, додайте відповідні індекси для полегшення vacuum і перевірте довгі транзакції. Не «вирішуйте» це міграцією бази.
Завдання 4 — Postgres: дивитися активні блокування і хто блокує
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select blocked.pid as blocked_pid, blocker.pid as blocker_pid, blocked.query as blocked_query, blocker.query as blocker_query from pg_locks blocked join pg_stat_activity blocked_sa on blocked_sa.pid = blocked.pid join pg_locks blocker on blocker.locktype = blocked.locktype and blocker.database is not distinct from blocked.database and blocker.relation is not distinct from blocked.relation and blocker.page is not distinct from blocked.page and blocker.tuple is not distinct from blocked.tuple and blocker.pid != blocked.pid join pg_stat_activity blocker_sa on blocker_sa.pid = blocker.pid where not blocked.granted;"
blocked_pid | blocker_pid | blocked_query | blocker_query
------------+-------------+-----------------------------------+--------------------------------
29411 | 28703 | update inventory set qty = qty-1 | vacuum (verbose, analyze) inventory
(1 row)
Значення: Vacuum блокує оновлення (або навпаки). Рішення: Налаштуйте налаштування вартості vacuum, заплануйте вікна обслуговування або переробіть транзакційні шаблони. Якщо ваш додаток чутливий до блокувань, розподілений варіант не магічно прибере контенцію.
Завдання 5 — Postgres: підтвердити тиск чекпоінтів
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time from pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | checkpoint_write_time | checkpoint_sync_time
------------------+-----------------+-----------------------+----------------------
1021 | 980 | 7123456 | 502311
(1 row)
Значення: Багато запитаних чекпоінтів; часи запису/синхронізації високі. Рішення: Налаштуйте shared_buffers, checkpoint_timeout, checkpoint_completion_target і забезпечте, що диск витримує хвилі записів. Знову: виправте фундамент перед купівлею нової бази.
Завдання 6 — Postgres: перевірити сплески підключень і потребу в пулері
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select state, count(*) from pg_stat_activity group by 1 order by 2 desc;"
state | count
-----------+-------
idle | 420
active | 65
idle in transaction | 12
(3 rows)
Значення: Сотні idle сесій; деякі «idle in transaction» (погано). Рішення: Додайте/перевірте connection pooling (PgBouncer), виправте обробку транзакцій в додатку і встановіть таймаути для запитів/idle. Багато скарг «Postgres повільний» — насправді «занадто багато підключень».
Завдання 7 — YugabyteDB: перевірити загальний стан кластера
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_all_tablet_servers
UUID RPC Host/Port State
3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 yb-tserver-1:9100 ALIVE
c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 yb-tserver-2:9100 ALIVE
a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 yb-tserver-3:9100 ALIVE
(3 rows)
Значення: Усі tablet servers живі. Рішення: Якщо один відсутній або DEAD — зупиніться і стабілізуйте кластер перед аналізом продуктивності; «повільно» часто означає «дефектна реплікація».
Завдання 8 — YugabyteDB: інспектувати лідерів планшетів і стан реплікації
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_tablets appdb.orders
Tablet-UUID Range Leader-UUID RF
b8c9d1110c3b4f9ea0d0000000000001 [hash 0x00, 0x55) 3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 3
b8c9d1110c3b4f9ea0d0000000000002 [hash 0x55, 0xaa) c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 3
b8c9d1110c3b4f9ea0d0000000000003 [hash 0xaa, 0xff) a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 3
Значення: Лідерство збалансоване між tserver‑ами. Рішення: Якщо лідери зосереджені на одному вузлі — очікуйте гарячих точок і нерівномірного CPU. Ребалансуйте лідерів або перегляньте розміщення/партиціонування.
Завдання 9 — YugabyteDB: перевірити недорепліковані планшети
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_under_replicated_tablets
Tablet-UUID Table
(0 rows)
Значення: Немає недореплікованих планшетів. Рішення: Якщо є записи — спочатку виправляйте реплікацію (диск, мережа, здоров’я вузла). Недореплікація підвищує латентність і ризик.
Завдання 10 — YugabyteDB (YSQL): знайти повільні запити на SQL‑рівні
cr0x@server:~$ ysqlsh -h yb-tserver-1 -U yugabyte -d appdb -c "select queryid, calls, total_time, mean_time, left(query, 80) from pg_stat_statements order by mean_time desc limit 5;"
queryid | calls | total_time | mean_time | left
----------+-------+------------+-----------+--------------------------------------------------------------------------------
77190012 | 3300 | 222000.11 | 67.27 | select * from orders where customer_id = $1 order by created_at desc limit 50
(1 row)
Значення: Та сама історія, що й у Postgres: гарячі шляхи додатка видимі. Рішення: Перед тим як звинувачувати «розподіленість», перевіряйте індекси і форму запитів. Розподілені системи суворіші до повних сканів.
Завдання 11 — YugabyteDB: перевірити, чи запит локальний чи розподілений (explain)
cr0x@server:~$ ysqlsh -h yb-tserver-1 -U yugabyte -d appdb -c "explain (analyze, dist, costs off) select * from orders where customer_id = 42 order by created_at desc limit 50;"
QUERY PLAN
------------------------------------------------------------------------
Limit (actual time=8.211..8.227 rows=50 loops=1)
-> Index Scan using orders_customer_created_idx on orders (actual time=8.210..8.221 rows=50 loops=1)
Storage Read Requests: 3
Storage Write Requests: 0
Storage Execution Time: 6.902 ms
Planning Time: 0.312 ms
Execution Time: 8.411 ms
(8 rows)
Значення: «Storage Read Requests: 3» натякає на кілька читань планшетів. Рішення: Якщо це число ростиме з обсягом даних — проектуйте для локальності (hash partition key, colocate related tables або уникайте крос‑партиційних join‑ів).
Завдання 12 — YugabyteDB: перевірити тиск компактацій/LSM через диск і ознаки IO
cr0x@server:~$ iostat -xz 1 3
avg-cpu: %user %nice %system %iowait %steal %idle
18.22 0.00 6.11 21.40 0.00 54.27
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
nvme0n1 120.0 8200.0 0.0 0.0 4.10 68.3 950.0 64200.0 0.0 0.0 24.80 67.6 9.10 98.5
Значення: Диск завантажений; write await високий. В LSM‑подібному двигуні компакти можуть викликати тривалі записи. Рішення: Додайте IO‑пропускну здатність, налаштуйте компактації, обмежте фонові роботи або додайте вузли для розподілу навантаження. Якщо бюджет не дозволяє швидкі диски, розподілене зберігання буде проблемою.
Завдання 13 — Мережна перевірка: затримка між зонами/регіонами
cr0x@server:~$ ping -c 5 yb-tserver-3
PING yb-tserver-3 (10.10.3.21) 56(84) bytes of data.
64 bytes from 10.10.3.21: icmp_seq=1 ttl=62 time=18.9 ms
64 bytes from 10.10.3.21: icmp_seq=2 ttl=62 time=19.4 ms
64 bytes from 10.10.3.21: icmp_seq=3 ttl=62 time=18.7 ms
64 bytes from 10.10.3.21: icmp_seq=4 ttl=62 time=20.1 ms
64 bytes from 10.10.3.21: icmp_seq=5 ttl=62 time=19.2 ms
--- yb-tserver-3 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 18.7/19.2/20.1/0.5 ms
Значення: ~19 ms RTT — ок для людей, дорого для консенсусу. Рішення: Якщо Raft‑кворуми охоплюють такі лінки, латентність комітів успадкує її. Тримайте кворуми в межах низьколатентного домену коли можливо; використовуйте стратегії гео‑партиціонування замість ілюзії, що фізика не існує.
Завдання 14 — Postgres: перевірити, що ваші бекапи реальні (не номінальні)
cr0x@server:~$ pgbackrest --stanza=appdb check
stanza: appdb
status: ok
cipher: none
db (current)
wal archive min/max (0000000100002A9F000000D1/0000000100002A9F000000F4) ok
database size 145.3GB, backup size 52.1GB
Значення: WAL архівування працює; бекапи узгоджені. Рішення: Якщо це фейлить — виправте бекапи перед масштабуванням. Надійність — не функція бази, а операційна звичка.
Завдання 15 — YugabyteDB: базова перевірка кількості планшетів і дисбалансу
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_tablet_servers
UUID Host Tablets Leaders
3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 yb-tserver-1 620 240
c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 yb-tserver-2 610 210
a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 yb-tserver-3 590 170
Значення: Розподіл планшетів і лідерів нерівномірний. Рішення: Перевірте налаштування ребалансера і переміщення лідерів. Скіс часто прямолінійно перетворюється на скіс CPU і хвостову латентність.
Швидкий план діагностики: знайти вузьке місце без читання з кави
Коли щось «повільно», потрібен триаж. Не рукописний десятисторінковий ранбук. Трьохпрохідний фільтр, що звужує проблему швидко.
Перше: система деградована чи просто зайнята?
- Postgres: перевірте здоров’я реплікації і насичення диска. Якщо primary IO‑зв’язаний — все інше шумливе наслідок.
- YugabyteDB: перевірте живість вузлів і недореплікацію. Кластер може бути «вгору», але Raft‑групи — нездорові, і саме тоді латентність йде в дику фазу.
Друге: це один запит/шлях чи все?
- Дивіться pg_stat_statements (в обох системах на рівні YSQL), щоб знайти топ‑споживачів за середнім і загальним часом.
- Якщо один запит домінує — швидше за все, проблема зі схемою/індексом/запитом.
- Якщо все повільне — скоріше ресурсний вузький місце (CPU, IO, мережа) або системна поведінка (vacuum/compaction, сплески підключень, дисбаланс лідерів).
Третє: латентність походить від обчислень, IO чи мережі?
- Обчислення: високий CPU, черга виконання; шукайте гарячі лідери (YB) або погані плани (PG).
- IO: високий iowait, високі await, завантаження пристрою близько 100%.
- Мережа: RTT між зонами, втрата пакетів або дизайн, що змушує кворум проходити через відстань.
Швидке рішення: Якщо p95 можна віднести до мережевого RTT та розподілених шляхів коміту — потрібні зміни локальності даних, а не «ще вузлів». Якщо це кілька запитів — потрібні індекси і кращі шаблони доступу, а не нова база.
Три корпоративні міні‑історії з передової
1) Інцидент через неправильне припущення: «репліки для читання врятують нас»
Середньорозмірний SaaS запускав Postgres з двома репліками для читання. Додаток був переважно читачевим, тож команда відвантажила «важкі ендпоїнти» на репліки і була задоволена. Декілька місяців все було добре. Потім вони запустили фічу, яка писала audit‑рядок на кожне читання (комплаєнс був радісний).
Припущення було, що ендпоїнт «читачевий», тож маршрутизація лишилася на репліки. Але ORM писав audit‑події в тому ж запиті. На репліках такі записи зазнавали помилок, повторів і падали на primary. Primary раптово отримав сплеск записів і одночасно хвилю підключень від повторів. CPU підскочив; з’явилися блокування; p95 перетворився в p999.
Інцидент був шумний, бо нічого не «впало». Primary був живий. Реплікація жила. Але користувацький досвід був жахливий. Логи додатка були забиті вводячими в оману попередженнями «could not execute statement», що виглядали як тимчасові мережеві помилки.
Виправлення було смішно простим: маршрутизувати всі ендпоїнти, які можуть писати — навіть «малі записи» — на primary і винести інжекцію аудиту в асинхронний пайплайн з буфером. Також додали захист: у додатку з’єднання, позначене як read‑only, відхиляло спроби запису на рівні драйвера.
Висновок: Масштабування читань через репліки працює, але «read‑only» має бути примусово гарантованим. І якщо роадмап продукту тихо перетворює шляхи читання на шляхи запису, архітектура має це помічати.
2) Оптимізація, що відкотилася: «зробимо більші батчі і більше паралелізму»
Платформа e‑commerce мала повільні оформлення і вирішила «оптимізувати базу». Збільшили розмір батчів для оновлень інвентаря і запустили більше воркерів паралельно. Ідея: менше транзакцій, більша пропускна здатність, менше накладних витрат.
У Postgres великі транзакції тримають блокування довше. Зросла кількість дедлоків. Autovacuum відстав через довгі транзакції, що перешкоджали прибиранню. Блоут повз і індекси збільшилися; hit rate кешу впав; IO піднявся. Система стала повільнішою для всіх, а не лише для checkout.
Пізніше вони протестували розподілений SQL (YugabyteDB) і застосували той самий підхід: більші батчі, більше паралельних воркерів. Цього разу відкат виглядав інакше. Навколо популярних SKU сформувалися гарячі шард‑точки. Кілька планшетів стали лідерами для найзавантаженіших ключів і були під удрюченням. На гарячих вузлах підскочили компактації. Латентність стала спайковою: «переважно нормально, іноді жахливо».
Вони відновилися, зробивши протилежне від «ефективного»: менші транзакції, рівномірніший розподіл ключів і контроль контенції по SKU. Також ввели идемпотентність і розумні політики повторів, бо розподілені транзакції вимагають повторних спроб при конфліктах.
Висновок: «Більші батчі» — не універсальний трюк продуктивності. Це змінює тривалість блокувань у Postgres і інтенсивність гарячих точок у розподілених системах. Іноді оптимізація просто переміщує біль у дорожчу форму.
3) Нудна, але правильна практика, що врятувала день: репетиції відновлення
Компанія, близька до платіжних процесів, використовувала Postgres для основного реєстру і експериментувала з YugabyteDB для глобально доступного сервісу профілів користувачів. Вони не були гламурні щодо операцій. Вони були строгі.
Щомісяця вони проводили drill відновлення. Не теоретично — справжнє «відновити в нове середовище і прогнати набір перевірок» тренування. Верифікували безперервність WAL, рахували рядки і проганяли інваріанти: баланси підсумовуються, зовнішні ключі збігаються, останні записи присутні.
Одного дня підсистема зберігання пошкодила частину блоків на репліці. Primary був цілий, але кандидат для промоціі став під сумнівом. Бо вони практикувалися, команда не гадала. Негайно відключили підозрілу репліку від промоції і відновили чисту репліку з бекапу, тримаючи primary стабільним.
Два місяці потому, коли інший інцидент вимагав підняття репліки під час невдалої віконної технічної операції, вони точно знали, які вузли безпечні і скільки займе відбудова. Ніхто не імпровізував на продакшн‑даних. Ніхто не творив «rm -rf».
Висновок: Практикуйте відновлення. Це несексуально, займає час і запобігає «ми втратили дані і гідність» інцидентам, що закінчуються кар’єрами.
Типові помилки: симптом → корінь → виправлення
1) Симптом: p95 латентності Postgres стрибає кожні кілька хвилин
Корінь: Пачки чекпоінтів і насичення IO (часто у поєднанні з малим checkpoint_timeout і недостатньою пропускною здатністю диска).
Виправлення: Збільште checkpoint_timeout, встановіть checkpoint_completion_target, переконайтеся, що WAL і дані на швидкому сховищі, і слідкуйте за pg_stat_bgwriter. Перевірте, що ОС не свопить.
2) Симптом: таблиці Postgres ростуть, продуктивність падає за дні
Корінь: Vacuum не встигає (autovacuum thresholds занадто високі, довгі транзакції або недостатній maintenance_work_mem).
Виправлення: Налаштуйте autovacuum для гарячих таблиць, скоротіть тривалість транзакцій, додайте індекси, що допомагають vacuum, і моніторьте n_dead_tup.
3) Симптом: «ми додали репліки, але записи стали повільніші»
Корінь: Вибух підключень і неправильна конфігурація пулера; репліки не зменшили роботу primary, бо додаток все ще йде на primary для записів і багатьох читань.
Виправлення: Використайте pooler (PgBouncer), зменшіть max connections і перемістіть по‑справжньому лише для читання навантаження. Перевірте, вимірюючи CPU/IO primary до і після.
4) Симптом: YugabyteDB «вгору», але латентність жахлива після втрати вузла
Корінь: Недорепліковані планшети або триває перере‑плікація; Raft‑групи працюють активніше, і лідери могли переміститися.
Виправлення: Перевірте недорепліковані планшети, стабілізуйте диски/мережу, дайте закінчитися ребалансуванню і уникайте великих змін схеми під час відновлення.
5) Симптом: в одному вузлі YugabyteDB високий CPU, а інші ні
Корінь: Концентрація лідерів або гарячі планшети через скіс в шаблонах доступу до ключів.
Виправлення: Ребалансуйте лідерів, збільште кількість планшетів відповідно і переробіть ключі партиціонування для розподілу записів. Розгляньте colocate або гео‑партиціонування для локальності.
6) Симптом: записи в багаторегіональному YugabyteDB відчуваються «випадково повільними»
Корінь: Кворум охоплює високолатентні лінії; шлях коміту включає WAN‑RTT, і розміщення лідера може бути неоптимальне.
Виправлення: Тримайте Raft‑кворуми в межах регіону/зони коли можливо, використовуйте follower‑читання або гео‑партиціонування для читань і встановіть очікування: сильні глобальні записи коштують латентності.
7) Симптом: додаток бачить часті повтори транзакцій у YugabyteDB
Корінь: Висока контенція ключів, гарячі рядки або довгі транзакції, що підвищують ймовірність конфліктів.
Виправлення: Зменшіть контенцію (шардові лічильники, уникайте «глобального рядка послідовності»), скоротіть транзакції, реалізуйте повтори з jitter і обмеженнями, і забезпечте идемпотентність.
8) Симптом: «ми мігрували і аналітика стала гіршою»
Корінь: Розподілені скани і join‑и дорогі; локальність даних і індексація не узгоджені з аналітичними шаблонами доступу.
Виправлення: Відокремте OLTP від аналітики, використовуйте materialized views/ETL або тримайте аналітику в Postgres/даних‑сховищі. Розподілений OLTP — не безкоштовний аналітичний рушій.
Чеклісти / покроковий план
Покроково: вирішити, чи вистачає Postgres
- Міряйте, а не відчувайте: увімкніть pg_stat_statements і збирайте 7–14 днів форми навантаження (топ‑запити за загальним/середнім часом, кількість викликів).
- Виправте очевидне: відсутні індекси, погані ORM‑шаблони, N+1, «idle in transaction» і неправильна конфігурація пулера.
- Приведіть vacuum до ладу: налаштування autovacuum за таблицями для частих змін; переконайтеся, що довгі транзакції не блокують прибирання.
- Підтвердіть запас диска: iostat і використання файлової системи; перевірте тиск чекпоінтів і насичення запису WAL.
- Впровадьте HA нудним шляхом: щонайменше одна репліка, автоматизований failover, бекапи з репетиціями відновлення і pooler.
- Масштабуйте читання правильно: репліки для чисто читальних навантажень, кешування там, де безпечно, і зменшення кругових викликів.
- Масштабуйте записи чесно: якщо один primary — межа, оцініть партиціонування/шардинг на рівні додатка або розгляньте розподілений SQL.
Покроково: вирішити, чи виправданий YugabyteDB
- Запишіть непохитні вимоги: «вижити при втраті зони», «active‑active», «масштабування записів», «RPO/RTO». Якщо ви не можете це сформулювати — ви шукаєте ендорфіни від технологій.
- Спроєктуйте бюджет латентності: який p95 вам потрібен і який RTT між розміщеннями? Якщо шлях коміту охоплює 20–50 мс RTT, ви це відчуєте.
- Прототипуйте гарячий шлях: не загальний бенчмарк. Ваші реальні топ‑5 запитів, реальні транзакційні форми, реальні індекси.
- Проєктуйте для локальності: обирайте ключі партиціонування і розміщення так, щоб більшість транзакцій була локальною. Розподілені крос‑регіональні транзакції — виняток.
- Плануйте повтори: реалізуйте идемпотентність і логіку повторів у додатку. Розподілені системи будуть вимагати цього.
- Бюджетуйте опси: моніторинг дисбалансу лідерів, розподілу планшетів, боргу компактацій і здоров’я вузлів. Призначте відповідальних; «продавець впорається» — не план on‑call.
Чекліст міграції (якщо ви мусите)
- Інвентаризуйте використовувані можливості Postgres: розширення, користувацькі типи, тригери, логічна реплікація, процедури.
- Раннє виявлення розривів сумісності; переробіть ризикові частини першими.
- Налаштуйте подвійні записи або CDC у staging; валідуйте коректність інваріантами.
- Проганяйте навантажувальні тести з включенням відмов: вбивайте вузол, додавайте латентність, заповнюйте диски.
- Визначте критерії відкату і відрепетируйте їх. Якщо план відкату — «ми розберемося» — його не буде.
Питання й відповіді
1) Чи є YugabyteDB «просто Postgres, але розподілений»?
Ні. YSQL прагне сумісності з Postgres на рівні SQL і протоколу, але модель зберігання і реплікації інша. Це змінює продуктивність і режими відмов.
2) Чи можна отримати active‑active з Postgres?
Не в простому сенсі «багато записувачів зі сильною консистентністю». Можна використовувати логічну реплікацію, multi‑primary патерни або шардинг на рівні додатка, але кожен має гострі кути і обробку конфліктів. Для справжніх розподілених записів без шардингу додатка ви переходите в територію Distributed SQL.
3) Чи буде YugabyteDB автоматично швидшим за Postgres?
Ні. Для багатьох OLTP‑навантажень, що поміщаються на один міцний Postgres primary, Postgres буде швидшим і простішим. YugabyteDB дає стійкість і горизонтальне масштабування, а не безкоштовне зниження латентності.
4) Яка найпоширеніша причина невдач міграцій до YugabyteDB?
Припущення, що «Postgres‑сумісний» = «вставив і все працює». Потім виявляється, що розширення, шаблон запитів або операційні очікування не переносяться гладко. Сумісність реальна, але не магічна.
5) Коли розподілений SQL дійсно правильний вибір?
Коли бізнес‑вимоги вимагають високої доступності через домени відмов, масштабування записів за межі одного вузла і ви не можете/не хочете шардити в додатку. Також коли простіше платити за додаткові вузли, ніж терпіти простої.
6) Як повтори змінюють дизайн додатка в розподілених системах?
Потрібна идемпотентність для операцій запису і структурована логіка повторів з jitter і обмеженнями. Інакше повтори перетворюють тимчасові конфлікти в тини і хвилі навантаження.
7) Чи означає багаторегіональність автоматично «всі користувачі отримують низьку латентність»?
Ні. Багаторегіональність може означати «доступно звідусіль», але не «швидко звідусіль». Сильні консистентні записи через регіони платять WAN‑латентністю. Щоб отримати низьку латентність скрізь, зазвичай потрібні стратегії локальності і іноді ослаблена консистентність для читань.
8) Чи можна використовувати YugabyteDB для аналітики теж?
Можна запускати аналітичні запити, але розподілені OLTP‑рушії зазвичай роблять великі скани і join‑и дорогими. Багато команд тримають OLTP в Postgres або YugabyteDB і переміщують аналітику в окрему систему.
9) Що варто моніторити по‑іншому між ними?
Postgres: прогрес vacuum, блоут, блокування, чекпоінти, лаг реплікації, кількість підключень. YugabyteDB: здоров’я планшетів, розподіл лідерів, недореплікація, борг компактацій, ребалансування і затримки між вузлами.
10) Якщо я сьогодні малий, чи варто «футурофіцувати» з YugabyteDB?
Зазвичай ні. Спочатку зробіть Postgres нудно відмінним. Найкраще «футурофікування» — чистий дизайн схеми, дисципліна запитів і операційна гігієна. Міграції завжди складніші, ніж планують.
Практичні наступні кроки
Якщо ви хочете рішення, яке витримає продакшн‑стрес, робіть це в порядку:
- Зробіть поточний Postgres нудно відмінним: pg_stat_statements, pooler підключень, налаштування vacuum, розумні таймаути, перевірені бекапи, відрепетирувані відновлення.
- Квантифікуйте стіну масштабування: чи це CPU, IO, контенція блокувань, чи пропускна здатність єдиного записувача? Показуйте графіки, а не думки.
- Запишіть вимоги доступності в операційних термінах: які домени відмов потрібно пережити, які RPO/RTO і який бюджет латентності для користувачів.
- Якщо вам справді потрібні розподілені записи і стійкість між доменами: прототипуйте YugabyteDB з вашим реальним навантаженням, вимірюйте хвостову латентність і практикуйте відмови (втрата вузла, тиск на диск, ін’єкція латентності).
- Оберіть те, що зможете експлуатувати: найкраща база — та, яку ваша команда може тримати швидкою, коректною і спокійною о 2‑й годині ночі.
Postgres усе ще за замовчуванням, бо він передбачуваний і добре вивчений. YugabyteDB привабливий, коли завдання дійсно розподілене. Якщо ви не впевнені, яку проблему маєте — швидше за все, у вас проблема з Postgres.