Ви купили «три маленькі сервери», бо бюджети люблять трійки. Потім розгорнули розподілену SQL БД, бо маркетинг любить слово «відмовостійкість».
Тепер p95 на вашій панелі виглядає так, ніби вирішив їхати мальовничим маршрутом, а в кожному огляді інциденту фігурує фраза «але локально все працювало».
Ось той податок затримки, який ніхто не ставить на слайд: розподілена коректність має реальну вартість на запит, і саме на маленьких серверах ця вартість стає голосною.
Не катастрофічною. Просто достатньо голосною, щоб порушити ваш сон.
Податок затримки: за що ви платите при консенсусі
Якщо запам’ятаєте одну річ — нехай це буде ця: типовий OLTP-потік MySQL — «запис виконується локально, потім реплікується пізніше».
Типовий OLTP-потік CockroachDB — «координуємо запис з кворумом, потім комітимо».
На пристойній мережі й із здоровими дисками кворумні записи проходять нормально. На маленьких серверах з комодітним сховищем і 1Gbps мережею (або гірше: віртуалізованою «best effort»)
кворумні записи перетворюються на підсилювач затримок. Не завжди в медіані. У хвостовій латентності. Тій, яку помічають користувачі, а SRE-і успадковують.
Операційна відмінність не тонка:
- MySQL: Один primary може бути надзвичайно швидким. Це також єдиний пункт, де фізика налаштована на вашу користь, а коректність — локальна. Репліки служать для масштабування читання і відмовостійкості. Узгодженість — це політика.
- CockroachDB: Кожен запис бере участь у консенсусі для відповідного діапазону. Узгодженість — за замовчуванням, а не додаток. Затримка — це плата.
Практичне порівняння: якщо ваш додаток чутливий до p99 латентності і ви запускаєтеся на малих вузлах, розподілений SQL може відчуватися як найм комітету, який має погоджувати кожен лист, що ви надсилаєте.
Цитата, яка переживає багато переглядів інцидентів: Усе ламається, постійно.
— Вернер Вогельс.
Він не мав на увазі саме ваш трьохвузловий кластер, але міг би.
Де проявляється податок (і чому це дивує людей)
Дивує не те, що розподілена система має накладні витрати. Дивує те, де вони проявляються.
Ви подивитеся на CPU і побачите запас. Ви подивитеся на середній час запиту і заспокоїтеся. Потім відкриєте гістограму й помітите стрибки p95/p99.
Типові місця прояву податку:
- Кворумні записи: для коміту потрібні підтвердження від більшості реплік для даного діапазону. Це принаймні один раунд, іноді більше.
- Узгодженість читань: суворо узгоджені читання можуть потребувати координації (розташування leaseholder має значення). Старі читання можуть бути швидшими, але ви маєте явне погодження і розуміння наслідків.
- Розбиття діапазонів і ребаланс: фонові роботи не є «безкоштовними». На маленьких машинах вони конкурують безпосередньо з переднім планом.
- Компакації: сховища на базі LSM обмінюють ампліфікацію записів на ефективність читань. Та ампліфікація стає дисковим і CPU-податком у невідповідні моменти.
Два числа, які мають переслідувати вашу таблицю підбору ресурсів
Ви не зможете «оптимізувати» фізику — лише обійти її.
- RTT мережі між вузлами: кожен мілісекунд має значення, коли шлях коміту включає міжвузлову координацію. RTT 1–2 мс здається нормальним, доки вам не знадобиться кілька хопів під навантаженням.
- Латентність fsync на сховищі: якщо ваш шар зберігання іноді перетворює fsync на пригоду 20–100 мс, розподілений консенсус чесно внесе цю пригоду в видиму користувачу затримку.
Жарт №1: трьохвузловий кластер на крихітних ВМ — це як естафета з трьома людьми, де кожен наполягає на тому, щоб зав’язати шнурки між передачами.
Факти та історія, які знадобляться в аргументах
Це не просто факти для вікторини в пабі. Вони пояснюють, чому системи поводяться саме так.
- MySQL з’явився у середині 1990-х і став дефолтним вибором для веб-OLTP, бо був простим у розгортанні, легко реплікувався і був швидким на одній машині.
- InnoDB став дефолтним рушієм зберігання для MySQL (після довгого домінування MyISAM), додавши відновлення після збоїв, блокування на рівні рядка та реальні транзакції для широкого впровадження.
- Більшість HA-патернів MySQL історично віддавали перевагу асинхронній реплікації, бо це просто і зберігає низьку латентність на primary; компроміс — ризик втрати даних при відмові.
- Алгоритми консенсусу на кшталт Raft були створені, щоб зробити розподілений консенсус простішим для розуміння й реалізації, але не без затримок.
- CockroachDB збудовано з Raft від початку, з метою надати SQL-подібні семантики, з дизайном shared-nothing та гео-розподіленістю.
- Google Spanner популяризував «розподілений SQL зі сильною узгодженістю» використовуючи TrueTime; CockroachDB орієнтується на подібні цілі без спеціального годинникового обладнання, що впливає на те, як виникають невизначеність і повтори транзакцій.
- Рушії з LSM-деревами стали поширеними в розподілених базах, бо добре обробляють високу пропускну здатність записів, але вимагають компактацій — тобто фонових I/O, що можуть вдарити по хвостовій латентності.
- Ера CAP-теореми зробила «узгодженість проти доступності» топовим питанням у нарадах; розподілені SQL системи зазвичай обирають узгодженість і толерантність до розділень, що ускладнює доступність під час розділень.
- p99 латентність стала мантрою SRE, бо UX визначається хвостовою поведінкою, а не середніми; розподілена координація — класичний генератор хвостової латентності.
Чому маленькі сервери роблять розподілений SQL гіршим
«Малі сервери» зазвичай означають поєднання: менше ядер, менше RAM, повільніші NVMe (або гірше: сховище в мережі), менш передбачувані сусіди і мережа, спроектована під «звичайний» трафік.
Розподілений SQL не ненавидить малі сервери. Він просто відмовляється робити вигляд, що вони великі.
Малі вузли не тільки зменшують потужність; вони зменшують запас
Запас — невидимий маржін, який тримає хвостову латентність стабільною.
На великій машині компактації, флаші або невеликий мережевий джиттер можуть бути поглинені.
На малому вузлі та сама фонові робота конкурує з роботою на передньому плані і перемагає частіше, ніж вам би хотілося.
Трьохвузлові кластери привабливі операційно (але механічно жорсткі)
Три вузли — мінімальна форма, яка виглядає як «кластер». Також це мінімальна форма, де будь-яке потрясіння може стати видимим для користувача.
Втратите один вузол — і ви в режимі «жодних більше відмов, будь ласка».
- MySQL на трьох вузлах зазвичай означає 1 primary + 2 репліки. Primary залишається гарячим; репліки — здебільшого пасажири до моменту failover.
- CockroachDB на трьох вузлах означає, що всі три у write path (кворум). Здоров’я кожного вузла має значення щохвилини.
Тихий вбивця: неоднорідність
Невеликі кластери рідко однорідні. Один вузол на трохи гіршому гіпервізорі.
Один диск трохи повільніший.
Один NIC ділить переривання з чимось неввічливим.
Розподілені системи зазвичай працюють у темпі найгіршого учасника — особливо на p99.
MySQL: базова низьколатентна альтернатива з гострими краями
Суперсила MySQL нудна: одна машина, один комітний лог, одна ієрархія кешів.
Якщо ваше навантаження вміщується на одному primary, MySQL може забезпечити дуже низьку латентність з передбачуваною поведінкою.
Ви безсумнівно можете погано запустити MySQL, але для цього треба постаратися.
У чому MySQL чудовий (на малих серверах)
- Низьколатентні одиночні записи рядків, коли схема та індекси розумні.
- Стабільні p95/p99, коли сховище пристойне і ви уникаєте точок контенції.
- Операційна простота для команд, які можуть терпіти одиночний primary і мають чіткий план failover.
Режими відмов MySQL не тонкі
- Насичення primary: немає граційного деградування; утворюється черга.
- Затримка реплікації: масштабування читань і безпека при відмові залежать від відстаючого асинхронного потоку, якщо ви не платите за синхронізацію.
- Коректність при failover: спліт-брейн можна уникнути, але це не автоматично; потрібні інструменти і дисципліна.
Реалії MySQL
Якщо вам потрібна сильна узгодженість між кількома записувачами в різних локаціях, MySQL не перетвориться магічно на розподілену БД.
Ви можете побудувати це за допомогою semi-sync, group replication або зовнішньої координації — але ви заробите кожне сповіщення на виклик.
CockroachDB: коректність у масштабі, навіть коли масштаб не був потрібен
Позиція CockroachDB проста: збережіть SQL, позбудьтеся крихкості одиночного primary.
Записи реплікуються. Читання можна обслуговувати «з правильного місця». Failover автоматизований і швидкий.
Це реальна інженерія, а не казкова пилюка.
У чому CockroachDB чудовий (навіть на малих серверах)
- Виживання при відмові вузла без ручних ритуалів failover.
- Масштабування читань і записів додаванням вузлів (в межах розумного та з увагою до локальності).
- Узгоджені семантики, які спрощують логіку додатка порівняно з ручною реплікацією.
Що CockroachDB вимагає від вас
- Повага до затримки: ви координуєте, тож вимірюйте мережу та диск серйозно.
- Розуміння контенції: гарячі точки не зникають у кластері; вони стають розподіленими гарячими точками з повторними спробами.
- Уважні рішення щодо локальності: leaseholder’и та репліки повинні бути розміщені навмисно, якщо вам важлива латентність.
Повтори транзакцій: «фіча», яка відчувається як баг
CockroachDB буде повторювати транзакції при контенції або невизначеності. Це частина того, як він зберігає коректність.
Симптом — помилки на рівні додатка (або повтори драйвера) та стрибки латентності, що здаються випадковими, поки ви не звʼяжете їх з метриками контенції.
Жарт №2: Розподілений SQL — єдиний продукт, який може продати вам «немає єдиної точки відмови» і все одно провалити запит, бо два годинники ввічливо не погодилися.
Порівняння за типами навантажень: хто перемагає і чому
Простий OLTP (читає/запис одного рядка, жорстке SLO на p99)
На малих серверах MySQL зазвичай перемагає за сирою латентністю. Коміт локальний. Кеш локальний. Шлях коду зрілий і короткий.
CockroachDB може відповідати реалістичним SLO, але ви відчуєте витрати кворума і рушія зберігання більше, особливо якщо вузли слабкі або мережа нервова.
Якщо ваш продукт живе чи помирає від p99 5–20 мс для малих транзакцій, не міняйте базу даних на консенсусну просто так у надії, що це «вимкнеться».
Висока доступність з автоматичним failover
CockroachDB перемагає за операційною поведінкою. Якщо у вас немає персоналу для чіткого виконання MySQL failover процедур, ви рано чи пізно провалите failover.
CockroachDB робить «нудне» (втрату вузла) справді нудним.
MySQL може бути високодоступним, але ви маєте це реалізувати і відрепетирувати. Технологія існує. Організаційна послідовність часто відсутня.
Масштабування записів (кілька записувачів, горизонтальний ріст)
Горизонтальне масштабування записів — не рідна зона комфорту MySQL.
Можна шардувати, але тоді ваш додаток стає шаредером. Це легітимна архітектура, але це зобов’язання.
CockroachDB розроблено для масштабування записів шляхом розбиття діапазонів і їх розподілу. На малих вузлах ви все ще можете бути обмежені диском і CPU на вузол,
але принаймні дизайн відповідає вимозі.
Масштабування читань
Репліки MySQL грубі, але ефективні. Вони також операційно поблажливі: якщо репліка відстає, ви просто маршрутуєте запити і обходите її.
CockroachDB також може розподіляти читання, але ви повинні розуміти leaseholder’ів і локальність; інакше читання стрибають по кластеру і платять зайві хопи.
Багаторегіональна латентність
Якщо ви справді багаторегіональні, швидкість світла стає вашим продакт-менеджером. Сильна узгодженість через океани дорога.
CockroachDB дає інструменти для розміщення даних і контролю локальності читань/записів, але якщо ви вимагаєте однозначних мілісекунд глобально, ваші вимоги помилкові.
MySQL у багаторегіональному сценарії часто стає primary в одному регіоні з асинхронними репліками в інших. Це швидко локально і «в кінцевому підсумку узгоджено» віддалено.
Іноді це саме те, що вам потрібно.
Еволюція схеми та операційне тертя
Обидві системи мають підводні камені при зміні схем. MySQL має десятиліття оперативної народної мудрості щодо онлайн-змін схем.
CockroachDB підтримує онлайн-зміни схем, але все ще може здивувати вас беками та їхнім впливом на маленькі кластери.
Практичні завдання: команди, результати та рішення
Ви не вирішите розподілену латентність атмосферними побажаннями. Ви вирішуєте її вимірами, які вказують на найповільнішу частину шляху коміту:
мережа, диск, контенція або топологія.
Нижче конкретні завдання. Кожне містить (1) команду, (2) приклад виводу, (3) що це означає і (4) рішення, яке ви приймаєте.
Запускайте їх на справжніх вузлах. Бажано під час навантажувального тесту, що нагадує продакшн, а не в тихий вівторок.
Завдання 1: Підтвердити RTT мережі між вузлами бази даних
cr0x@server:~$ ping -c 5 db-node-2
PING db-node-2 (10.0.0.12) 56(84) bytes of data.
64 bytes from 10.0.0.12: icmp_seq=1 ttl=64 time=0.78 ms
64 bytes from 10.0.0.12: icmp_seq=2 ttl=64 time=0.91 ms
64 bytes from 10.0.0.12: icmp_seq=3 ttl=64 time=1.02 ms
64 bytes from 10.0.0.12: icmp_seq=4 ttl=64 time=0.85 ms
64 bytes from 10.0.0.12: icmp_seq=5 ttl=64 time=0.80 ms
--- db-node-2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4092ms
rtt min/avg/max/mdev = 0.78/0.87/1.02/0.09 ms
Значення: RTT менше мілісекунди до ~1 мс — пристойно для малого кластера. Якщо ви бачите 3–10 мс всередині одного «датацентру», очікуйте проблем з p99 під кворумними записами.
Рішення: Якщо RTT високий або нестабільний, виправте розміщення вузлів (той самий стій/зону), налаштування NIC offload і проблеми з шумними сусідами перед тим, як звинувачувати базу даних.
Завдання 2: Виміряти TCP-латентність і ретрансляції під навантаженням
cr0x@server:~$ ss -ti dst 10.0.0.12:26257 | head -n 12
ESTAB 0 0 10.0.0.11:52144 10.0.0.12:26257
cubic wscale:7,7 rto:204 rtt:1.3/0.4 ato:40 mss:1448 pmtu:1500 rcvmss:1448 advmss:1448 cwnd:10 bytes_acked:812345 segs_out:12034 segs_in:11877 send 89.2Mbps lastsnd:12 lastrcv:12 lastack:12 pacing_rate 178Mbps retrans:3/12034
Значення: rtt показує спостережуваний RTT і варіацію. retrans вказує на втрату пакетів або перевантаження.
Рішення: Якщо ретрансляції ростуть під час піків, хвостова латентність — це не «налаштування бази». Це перевантаження. Виправляйте QoS, пропускну здатність, невідповідність MTU або шумний схід-захід трафік.
Завдання 3: Перевірити розподіл латентності диска (справжній лиходій)
cr0x@server:~$ iostat -x 1 5
Linux 6.5.0 (db-node-1) 12/30/2025 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
18.20 0.00 6.40 3.60 0.00 71.80
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 210.0 8120.0 0.0 0.0 1.20 38.7 480.0 24576.0 0.0 0.0 14.80 51.2 7.40 92.0
Значення: w_await приблизно 15 мс з %util ~92% означає, що записи стоять у черзі. Саме там помирає p99.
Рішення: Якщо завантаження диска постійно високе або очікування стрибає, переходьте на швидше сховище, розділяйте пристрої WAL, зменшуйте ампліфікацію записів або знижуйте тиск під час компактацій перед тим, як змінювати SQL-налаштування.
Завдання 4: Перевірити файлову систему та опції монтування
cr0x@server:~$ findmnt -no TARGET,SOURCE,FSTYPE,OPTIONS /var/lib
/var/lib /dev/nvme0n1p2 ext4 rw,relatime,discard,data=ordered
Значення: Опції на кшталт discard можуть додавати латентність в деяких налаштуваннях; ext4 за замовчуванням зазвичай підходить, але деталі мають значення.
Рішення: Якщо бачите мережеві файлові системи, тонке провізування або небезпечні опції, виправте сховище спочатку. Розподілений консенсус не пробачає ненадійних дисків.
Завдання 5: Перевірити CPU steal time (податок віртуалізації)
cr0x@server:~$ mpstat 1 5 | tail -n 7
12:22:11 PM all 15.40 0.00 6.90 2.10 0.00 4.80 0.00 70.80
12:22:12 PM all 17.20 0.00 8.10 2.40 0.00 6.20 0.00 66.10
12:22:13 PM all 14.80 0.00 6.30 1.90 0.00 7.60 0.00 69.40
Значення: Якщо бачите ненульовий %steal (тут він 0.00), гіпервізор позичає ваш CPU. Це збільшує час координації.
Рішення: Високий steal → переходьте на виділені інстанси, прикріплюйте vCPU або приймайте, що ваш p99 тепер спільний ресурс з пакетними завданнями сусідів.
Завдання 6: Швидко виміряти поведінку fsync за допомогою fio
cr0x@server:~$ fio --name=fsync-test --directory=/var/lib/dbtest --size=1G --bs=4k --rw=write --ioengine=sync --fdatasync=1 --runtime=30 --time_based --direct=1
fsync-test: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=sync, iodepth=1
fio-3.33
Starting 1 process
fsync-test: (groupid=0, jobs=1): err= 0: pid=22811: Tue Dec 30 12:23:40 2025
write: IOPS=4200, BW=16.4MiB/s (17.2MB/s)(492MiB/30001msec); 0 zone resets
clat (usec): min=70, max=24500, avg=220, stdev=410
clat percentiles (usec):
| 95.00th=[ 310], 99.00th=[ 950], 99.90th=[ 8500]
Значення: 99.90th на 8.5 мс — прийнятно; якщо ви бачите 50–200 мс на 99.9-му відсотку, ваша база періодично «заморожуватиметься» на p99.
Рішення: Поганий хвостовий fsync → змініть клас сховища, вимкніть брехливі write cache, забезпечте захист при відключенні живлення або перемістіть WAL/pebble/redo журнали на кращі носії.
Завдання 7: MySQL — перевірити політику флашу InnoDB
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';"
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
Значення: 1 — найнадійніший варіант: флаш при кожному коміті. 2 або 0 можуть зменшити латентність, але ризикують втратити нещодавні коміти при краші.
Рішення: Якщо ви порівнюєте MySQL з CockroachDB — тримайте це на 1 заради чесності. Якщо змінюєте, запишіть ризик у свій інструктаж на випадок інциденту.
Завдання 8: MySQL — перевірити, чи semi-sync справді ввімкнений
cr0x@server:~$ mysql -e "SHOW STATUS LIKE 'Rpl_semi_sync_master_status';"
+-----------------------------+-------+
| Variable_name | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | OFF |
+-----------------------------+-------+
Значення: Якщо semi-sync OFF, primary не чекає підтвердження репліки при коміті. Ось чому MySQL швидкий, і одночасно чому при failover можна втратити дані.
Рішення: Якщо бізнес вимагає «без втрати даних», або ввімкніть semi-sync (заплативши латентністю), або перестаньте вдавати, що асинхронна реплікація відповідає цій вимозі.
Завдання 9: MySQL — виявити лаг реплікації і вирішити, що означає «свіже»
cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep "Seconds_Behind_Source|Replica_IO_Running|Replica_SQL_Running"
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 42
Значення: 42 секунди відставання — це не «трохи». Це інша база даних.
Рішення: Маршрутуйте читання, які потребують свіжості, на primary, налаштуйте реплікацію (паралельне застосування, фікси схеми/індексів) або свідомо прийміть eventual consistency у поведінці додатка.
Завдання 10: CockroachDB — перевірити здоров’я вузлів і liveness
cr0x@server:~$ cockroach node status --insecure
id | address | sql_address | build | started_at | updated_at | is_available | is_live
-----+-------------------------------+---------------+---------+----------------------------------+----------------------------------+--------------+----------
1 | db-node-1:26257 | db-node-1:26257 | v23.2.6 | 2025-12-30 11:02:17.123456+00:00 | 2025-12-30 12:25:01.123456+00:00 | true | true
2 | db-node-2:26257 | db-node-2:26257 | v23.2.6 | 2025-12-30 11:02:19.223456+00:00 | 2025-12-30 12:25:00.923456+00:00 | true | true
3 | db-node-3:26257 | db-node-3:26257 | v23.2.6 | 2025-12-30 11:02:21.323456+00:00 | 2025-12-30 12:25:01.003456+00:00 | true | true
Значення: Усі вузли живі й доступні. Якщо один інколи стає недоступним, очікуйте стрибків латентності записів і повторів транзакцій.
Рішення: Виправляйте стабільність вузла перед тим, як налаштовувати SQL. «Він інколи флапається» не є дрібницею в системах з консенсусом.
Завдання 11: CockroachDB — проінспектувати розподіл діапазонів і недо-реплікацію
cr0x@server:~$ cockroach node ranges --insecure
node_id | ranges | leaseholders | replicas | avg_range_size
----------+--------+--------------+----------+----------------
1 | 412 | 190 | 824 | 64MiB
2 | 398 | 145 | 796 | 66MiB
3 | 421 | 205 | 842 | 63MiB
Значення: Досить збалансовано. Якщо один вузол тримає більшість leaseholder’ів, він стає точкою латентності для читань/записів цих діапазонів.
Рішення: Якщо є перекіс, налаштуйте локальність/zone configs, додайте потужності або дослідіть чому ребаланс застряг (часто через диск або мережу).
Завдання 12: CockroachDB — шукати контенцію й повтори транзакцій у SQL-статистиках
cr0x@server:~$ cockroach sql --insecure -e "SELECT app_name, sum(retry_count) AS retries, sum(cnt) AS stmts FROM crdb_internal.statement_statistics GROUP BY app_name ORDER BY retries DESC LIMIT 5;"
app_name | retries | stmts
------------+---------+--------
web-api | 1842 | 98412
worker | 210 | 12001
console | 0 | 1422
Значення: Ваш API дуже часто повторюється. Це час, який користувач відчуває як латентність (навіть якщо транзакція в кінцевому рахунку успішна).
Рішення: Знайдіть гарячі ключі, зменшіть контенцію (шардируйте лічильники, пакетні записи інакше), і переконайтеся, що логіка повторів на клієнті налаштована коректно і обмежено.
Завдання 13: CockroachDB — визначити локальність leaseholder’а для гарячої таблиці
cr0x@server:~$ cockroach sql --insecure -e "SHOW RANGES FROM TABLE app.orders;"
start_key | end_key | range_id | replicas | lease_holder
------------+---------+----------+----------+--------------
NULL | /1000 | 84 | {1,2,3} | 2
/1000 | /2000 | 91 | {1,2,3} | 2
/2000 | /3000 | 99 | {1,2,3} | 1
Значення: Leaseholder’и, зосереджені на вузлі 2, означають, що вузол 2 обробляє координацію для цих діапазонів. Якщо вузол 2 повільніший, всі сповільнюються.
Рішення: Зафіксуйте leaseholder’и через zone configs/locality або виправте диск/мережу/CPU вузла 2, щоб він перестав бути «настроєм» кластера.
Завдання 14: Порівняти розподіли латентності запитів зі сторони клієнта
cr0x@server:~$ pt-query-digest --type slowlog /var/log/mysql/mysql-slow.log | head -n 12
# 2.1s user time, 20ms system time, 28.11M rss, 100.00% CPU
# Query_time distribution
# 1us
# 10us
# 100us ################################################
# 1ms ###############################################
# 10ms ######
# 100ms ##
# 1s #
Значення: Розподіл показує, чи є у вас невелика кількість патологічно повільних запитів, чи широка хвостова деградація.
Рішення: Якщо патологічно кілька → виправляйте індекси/план. Якщо широка хвостова проблема → дивіться на сховище/мережу/локи та фонові роботи.
Завдання 15: Перевірити планувальник Linux і дисбаланс IRQ (класика малих вузлів)
cr0x@server:~$ cat /proc/interrupts | egrep "eth0|nvme" | head -n 6
48: 18823401 0 0 0 IR-PCI-MSI 524288-edge eth0-TxRx-0
49: 0 0 0 0 IR-PCI-MSI 524289-edge eth0-TxRx-1
50: 0 0 0 0 IR-PCI-MSI 524290-edge eth0-TxRx-2
51: 0 0 0 0 IR-PCI-MSI 524291-edge eth0-TxRx-3
Значення: Якщо всі переривання падають на одне CPU (як вище), ви можете отримати дивну латентність під навантаженням навіть при «низькому» середньому використанні CPU.
Рішення: Увімкніть irqbalance або правильно прив’яжіть черги; для малих вузлів це може бути різницею між стабільним p99 і хаосом.
Швидкий план діагностики
Коли латентність стрибає, у вас немає часу на філософію. Потрібна коротка послідовність, що з високою ймовірністю знаходить вузьке місце.
Ось план, який я використовую, коли хтось каже: «Cockroach повільний» або «MySQL раптово відстає» на малих серверах.
Перше: мережа, диск чи контенція?
-
Перевірте RTT між вузлами і ретрансляції (ping + ss).
Якщо RTT/джиттер або ретрансляції корелюють зі стрибками латентності — зупиніться і виправте мережу. -
Перевірте диск await і завантаження (iostat).
Якщо записи стоять у черзі (w_awaitпідвищений,%utilвисокий) — зупиніться і виправте сховище або ампліфікацію записів. -
Перевірте повтори транзакцій / очікування блокувань (статистика транзакцій Cockroach; performance_schema або статус InnoDB для MySQL).
Якщо повтори/очікування ростуть, у вас контенція, а не «повільне залізо».
Друге: підтвердити топологію та локальність
- CockroachDB: розташування leaseholder’ів для гарячих діапазонів; баланс розподілу діапазонів; недо-реплікація.
- MySQL: ролі primary/реплік, затримка реплікації, маршрутизація читань і чи semi-sync несподівано ввімкнений (або вимкнений).
Третє: ідентифікувати фонові роботи, що крадуть ресурси
- CockroachDB: компактації, ребаланс діапазонів, бекфіли схем. На малих вузлах це може бути голосно.
- MySQL: purge lag, тиск на чекпоінти, довгі транзакції або джоб бекапу, який «ввічливо» споживає I/O, але насправді не ввічливий.
Умови зупинки (щоб уникнути випадкових налаштувань)
- Якщо
iostatпоказує високе await — не чіпайте SQL-настройки ще. - Якщо ретрансляції пакетів стрибнули — не переробляйте схему ще.
- Якщо домінують повтори/очікування — не купуйте нове залізо, поки не виправите гарячі точки.
Три міні-історії з корпоративного світу (анонімізовано, правдоподібно, технічно точно)
Міні-історія 1: Інцидент, спричинений неправильним припущенням
Середній SaaS мігрував сервіс профілів користувачів з MySQL на CockroachDB. Архітектурний документ казав: «Нам потрібна HA; трьохвузловий кластер дає це».
Команда була розумною, код чистий, репетиції міграції пройшли добре — у стейджингу з низьким навантаженням і дружніми сусідами.
Припущення, що провалилося: «Якщо кожний вузол менше ніж 30% CPU, у нас достатньо запасу».
В продакшні латентність різко зросла після релізу, але CPU залишався спокійним. Інженер на чергуванні дивився на графіки CPU, ніби ті мали щось зізнатися.
Тим часом користувачі чекали додаткові 200–600 мс при оновленні профілю під час піку.
Справжній вузький горлечко — хвостова латентність сховища на одному вузлі. Його SSD іноді мав довгі fsync-паузи.
MySQL раніше маскував це, бо primary був на іншому боксі, і лаг реплікації не був помітний користувачеві.
В CockroachDB той самий вузол був частиною кворуму для великої частини діапазонів, тож його паузи стали паузами кворуму.
Виправлення було безсоромно недатабазове: замінити диск і ребалансувати leaseholder’и.
Також змінили правила закупівель: ніяких «таємних SSD» і ніяких змішаних класів сховища в межах кластера консенсусу.
Урок: у розподіленому SQL один повільний вузол — це не «трохи повільніший». Він стає персональністю кластера.
Міні-історія 2: Оптимізація, яка обернулася проти
Платформа e-commerce працювала на MySQL з асинхронною реплікацією. Оформлення замовлень було швидким, але керівництво хотіло «сильнішої узгодженості» під час відмов.
Хтось запропонував увімкнути semi-sync, щоб хоча б одна репліка підтвердила записи перед комітом.
Це звучало як безоплатне підвищення безпеки. Так не виявилося.
Вони ввімкнули semi-sync у вікні обслуговування і побачили лише помірне збільшення середньої латентності.
Тиждень по тому перша реальна мережева проблема: топ-оф-рейк комутатор почав періодично скидати пакети.
Primary почав чекати підтверджень від реплік, які приходили з затримкою або взагалі не приходили.
Латентність не просто зросла. Вона стала спайковою. Потік оформлення накопичився, пула нитей наситилася, і тоді таймаути на шарі додатка почалися.
Інцидент виглядав як «база повільна». Корінь був: «мережа ненадійна, і ми поставили її в шлях коміту».
Оптимізація, що обернулася проти, — не в semi-sync самому по собі; а в увімкненні його без валідації якості east-west мережі і без налаштування таймаутів під реальність.
Після інциденту вони залишили semi-sync — але лише після розміщення реплік у кращому сегменті мережі і встановлення чіткої поведінки на випадок відмови semi-sync.
Урок: все, що додає підтвердження в шлях коміту, перетворює мережеві гикавки в користувацьку біль. Це стосується і MySQL, і це — дефолт у CockroachDB.
Міні-історія 3: Нудна, але правильна практика, яка врятувала день
Фінансова команда працювала з CockroachDB на невеликих, але пристойних вузлах. Бюджет не був безмежним, тож вони спиралися на дисципліну:
стабільні типи інстансів, виділені диски і строгий календар змін для міграцій схем.
Найменш гламурна практика виявилася найціннішою: перед кожним релізом вони запускали фіксований «тест прийнятності хвостової латентності», який вимірював p95 та p99 для набору критичних транзакцій,
одночасно запускаючи фонову I/O роботу, що імітувала шумні умови.
Одного дня тест провалився. Медіана була в порядку; p99 подвоївся. Що змінилося? Новий індекс на таблиці з великою кількістю записів плюс бекфіл, що збільшив ампліфікацію записів.
Розробники стверджували, що код правильний — і вони були праві. Але коректність у продакшні включає й фізику.
Оскільки у них був тест, вони зловили проблему до того, як її помітили клієнти. Вони перенесли бекфіл на непіковий час, відкоригували constraints, щоб зберегти leaseholder’ів ближче до шару API,
і додали обмеження пропускної здатності для джоба міграції.
День врятувала нудна рутина і відмова вважати p99 чиєюсь чужою метрикою.
Поширені помилки: симптом → корінь проблеми → виправлення
Це патерни, що зʼявляються в каналах інцидентів. Симптоми повторювані; корені зазвичай банальні.
Виправлення конкретні, а не надихаючі.
CockroachDB: стрибки p99 записів кожні кілька хвилин
- Симптоми: короткі латентні обриви, повтори транзакцій, вузли «здорові» на перший погляд.
- Корінь проблеми: компактації або флаші сховища, що конкурують з переднім планом; або один вузол з довгими fsync хвостами.
- Виправлення: перевірити хвостовий fsync через fio, зменшити насичення диска, використовувати краще сховище і гарантувати однорідність вузлів. Розглянути розділення навантажень і розмірювання для запасу під компактації.
CockroachDB: читання несподівано повільні в «однорегіональному» кластері
- Симптоми: латентність читань вища, ніж очікується, навіть для простих SELECT.
- Корінь проблеми: leaseholder’и не розташовані поруч з SQL gateway/шаром додатка; запити стрибають між вузлами.
- Виправлення: перевірити розподіл leaseholder’ів, налаштувати локальність/zone constraints і маршрутизувати SQL-зʼєднання відповідно.
CockroachDB: додаток бачить спорадичні помилки серіалізації
- Симптоми: помилки, що підлягають повтору, збільшений час запиту, «випадкові» збої під навантаженням.
- Корінь проблеми: контенція на гарячих рядках/діапазонах; довгі транзакції; оновлення-лічильники.
- Виправлення: переробити гарячі точки (шардовані лічильники, пакетні записи), скоротити транзакції, додати індекси та гарантувати коректність і обмеженість повторів на клієнті.
MySQL: репліки відстають під пік і ніколи не наздоганяють
- Симптоми: зростаючий
Seconds_Behind_Source, затримані читання, ризикові failover-и. - Корінь проблеми: повільні запити на репліці, недостатня паралельність застосування, важкі DDL або насичення I/O.
- Виправлення: оптимізувати гарячі запити, увімкнути/налаштувати паралельну реплікацію, ізолювати репліки для читань і планувати DDL обережно.
MySQL: раптове збільшення латентності після «зробити безпечніше»
- Симптоми: коміти повільніші, таймаути додатка, CPU в нормі.
- Корінь проблеми: увімкнення semi-sync або суворіших налаштувань надійності без перевірки хвостової латентності диска або мережі.
- Виправлення: виміряйте RTT і ретрансляції, встановіть реалістичні таймаути, розмістіть репліки краще і вирішіть явно, які семантики відмов ви потребуєте.
Обидві: середня латентність в порядку, а користувачі все одно скаржаться
- Симптоми: панелі показують «зелений», а служба підтримки каже «іноді повільно».
- Корінь проблеми: хвостова латентність від контенції, GC/компактацій, фонових джобів або мережевого джиттера.
- Виправлення: інструментувати і ставити алерти на p95/p99, корелювати з дисковими/мережевими метриками і включити хвостову латентність у критерії релізу.
Чек-листи / покроковий план
Чек-лист A: вибір MySQL проти CockroachDB для малих серверів
- Якщо потрібен p99 в одиничних мілісекундах для OLTP в одній локації: почніть з MySQL на потужному primary. Додайте репліки для читань і бекапів. Не виправдовуйтеся.
- Якщо потрібен автоматичний failover з мінімальною операційною рутиною і ви можете терпіти плату за латентність: CockroachDB — розумний вибір, але розміркуйте диски і мережу серйозно.
- Якщо потрібен multi-writer без шардингу в додатку: CockroachDB підходить за формою. MySQL змусить вас шардувати або використовувати складну координацію.
- Якщо ваші вузли «крихітні» і сховище посереднє: не вибирайте розподілений SQL заради продуктивності. Обирайте його заради доступності та операційної поведінки і прийміть податок.
- Якщо бізнес вимагає «без втрати даних»: переконайтеся, що ви не покладаєтеся на асинхронні семантики в MySQL і що ваш CockroachDB може зберегти кворум під відмовою.
Чек-лист B: змусити CockroachDB поводитися на малих вузлах
- Тримайте вузли максимально однаковими (CPU, RAM, тип диска, налаштування ядра).
- Вимірюйте і контролюйте хвостову латентність fsync; не вгадуйте.
- Слідкуйте за повторними спробами транзакцій і контенцією; виправляйте гарячі точки в схемі та патернах доступу.
- Перевіряйте локальність leaseholder’ів для гарячих діапазонів; не думайте, що БД «знає» топологію вашого додатка.
- Плануйте зміни схем і бекфіли; обмежуйте їх; ставте їх як події продакшну.
- Залишайте запас для компактацій і ребалансів. «80% завантаження диска» — це не запас.
Чек-лист C: зробити MySQL безпечним без руйнування латентності
- Тримайте
innodb_flush_log_at_trx_commit=1, якщо не можете пояснити ризик в одному реченні неінженеру. - Визначте, яку узгодженість потребують читання; маршрутуйте відповідно (primary проти реплік).
- Репетируйте failover. Руководство, яке ви ніколи не виконували — це вигадка.
- Моніторте лаг реплікації і встановлюйте пороги, що запускають операційні дії, а не лише панелі.
- Тримайте бекапи і відновлення нудними і протестованими; реплікація — це не бекап.
- Робіть онлайн-зміни схем перевіреними інструментами і з планом відкату.
Питання й відповіді
1) Чи «повільніший» CockroachDB за MySQL?
На малих серверах для низьколатентного OLTP часто — так, особливо на p95/p99 — бо кворумна реплікація і LSM-компактації додають неминучу роботу.
На навантаженнях, що потребують масштабування записів або автоматичного failover, «повільніше на транзакцію» може бути «швидше для бізнесу», бо відмови коштують більше за мілісекунди.
2) Чому латентність CockroachDB виглядає нормальною за середнім, але поганою на p99?
Бо хвостова латентність — це місце, де проявляється координація і фонова робота: випадкові повільні fsync, сплески компактацій, глюки реплік, мережевий джиттер і повтори.
Медіани ховають це; користувачі — ні.
3) Чи можу я зробити CockroachDB таким же швидким, як одиночний MySQL primary?
Ви можете звузити розрив хорошим залізом, конфігурацією з урахуванням локальності та усуненням контенції. Ви не зможете прибрати кворум і при цьому зберегти ті ж семантики відмов.
Якщо вам потрібна латентність одиночного вузла — запустіть одновузлову базу для цього шляху.
4) Чи достатньо трьох вузлів CockroachDB?
Це мінімум для HA на основі кворуму, і воно працює. Це також крихке в сенсі, що будь-яке технічне обслуговування або нестабільність вузла одразу з’їдає запас.
Якщо є можливість, п’ять вузлів дають операційне дихання і часто кращу хвостову поведінку.
5) Що в MySQL відповідає сильній узгодженості CockroachDB?
MySQL може наблизитися до суворішої надійності/узгодженості за допомогою semi-sync або group replication, але ви змінюєте шлях коміту і отримуєте латентнісний відбиток.
Ключова відмінність — CockroachDB побудований навколо цієї моделі; MySQL може робити це, але його легше неправильно налаштувати і важче зрозуміти під час відмов.
6) Хто простіше у супроводі для маленької команди?
Якщо ваше навантаження вміщується на одному primary, MySQL зазвичай простіший у повсякденній експлуатації.
Якщо вам потрібен автоматичний failover і ви не можете собі дозволити ручні HA-операції, CockroachDB може зменшити операційну рутину — за умови інвестицій у вимірювання диска/мережі та розуміння контенції.
7) Чи вимагає CockroachDB швидкі NVMe?
Він не вимагає їх для функціювання. Він їх вимагає, щоб «відчувати себе добре» під навантаженням, особливо на малих вузлах.
Повільне або нестабільне сховище проявляється як латентність кворумного коміту і зупинки компактацій.
8) Чи можна використовувати CockroachDB на 1Gbps мережі?
Так, але ви маєте стежити за ретрансляціями і схід-захід перевантаженням, і тримати вузли близько (низький RTT, низький джиттер).
Якщо мережа спільна і вибухова, ваш p99 це відобразить.
9) Чому малі кластери виглядають «добре», поки трафік трохи не виросте?
Бо першим закінчується запас, а не CPU. Фонова робота і ефекти чергування ростуть нелінійно.
На малому масштабі система має достатньо простою часу, щоб приховати гріхи; на помірному масштабі відсотки нараховують відсотки.
10) Якщо я вже на MySQL, коли варто переходити на CockroachDB?
Перейдіть, коли ваш біль головним чином у failover, рості multi-writer, операційній крихкості або складності шардингу та коректності.
Не переходьте тільки через модний тренд. Якщо головний біль — латентність запитів, розподілена система — дорогою спосіб отримати ще більше латентності.
Висновок: що робити наступного тижня
Якщо ви на малих серверах і роздумуєте між MySQL і CockroachDB, ставте це як інженерне рішення, а не як брендингове.
MySQL — це базова точка для латентності. CockroachDB — базова точка для доступності. Ви можете налаштувати обидві, але їхні дефолти не підлягають торгу.
Практичні кроки, які дійсно змінюють результати:
- Виміряйте RTT і хвостову латентність fsync на ваших реальних вузлах. Якщо одне з цих двох погане — виправляйте це перш ніж змінювати бази даних.
- Визначте вашу модель істини: асинхронна реплікація (швидше, ризикованіше) проти кворумної/сильно узгодженої (надійніше, повільніше). Запишіть це.
- Запустіть навантажувальний тест, сфокусований на p99, з фоновим I/O. Якщо ви не тестуєте хвости, ви знайдете їх у продакшні.
- Якщо обираєте CockroachDB на малих вузлах: інвестуйте в однорідне залізо, усвідомленість локальності/leaseholder’ів і діагностику контенції з першого дня.
- Якщо обираєте MySQL: інвестуйте в дисципліну failover, моніторинг лагу реплікації і перевірені бекапи. Не дозволяйте «простому» стати «ненаглянутим».
Податок затримки — не прихований платіж. Він у чеці. Потрібно просто подивитися на p99, щоб його побачити.