Ви додаєте «просту» панель у реальному часі. Продукт у захваті. Керівники — теж. Потім оформлення замовлення починає вести себе дивно: p95 латентності подвоюється, CPU йде в піки, а MySQL починає викидати помилки «too many connections», ніби готується до медичної драми.
Панель не зла. Архітектура — ось де проблема. OLTP-бази даних і аналітичні навантаження підпорядковуються різним законам, і ігнорувати це — значить одного разу пояснювати фінансам, чому «конверсія» на хвилину стала «нуль».
Справжня проблема: змішані навантаження і спільні області відмов
Панелі в реальному часі виходять з ладу надзвичайно передбачуваним способом: вони змушують OLTP-системи поводитися як OLAP. Іноді можна вирішити це грубо: індекси, репліки, кеш. Але якщо панель важлива, вона розростається. З’являться більше фільтрів, більше джойнів, ще «ще одна вимірність». Потім одного дня вона запускається під час піку кампанії, і ваш платіжний потік отримує перший рядок у вашому аналітичному шоу.
Основна проблема не в швидкості запитів. Вона в взаємних перешкодах. OLTP і аналітика борються за одні й ті ж обмежені ресурси:
- CPU (аналітика його любить; OLTP потрібен для конкуренції)
- Буферний пул / page cache (аналітика його інтенсивно перегортає; OLTP виграє від стабільності)
- I/O (аналітика сканує; OLTP потребує низької латентності випадкових читань/записів)
- Блокування і захвати (не завжди очевидні, але з’являються в найневідповідніший момент)
- Слоти підключень (панелі часто «балакучі»)
Панелі також соціально привілейовані. Ніхто не каже «вимкніть оформлення замовлення на годину». Люди сказали б «чому панель не в реальному часі?» Так ви й потрапляєте на неправильну оптимізацію.
Сухий факт: «реальний час» зазвичай означає «достатньо свіже, щоб ухвалювати рішення». Це може бути 2 секунди. Може бути 2 хвилини. Якщо ви не визначите це, ви отримаєте найгірший тип: дорогий «реальний час».
MySQL проти ClickHouse: за що кожен рушій дійсно добре працює
MySQL: робоча кінь оформлення замовлення
MySQL — це OLTP-база даних з дорослою екосистемою, сильними інструментами експлуатації та передбачуваною поведінкою під інтенсивними транзакційними записами. InnoDB оптимізований для:
- Багатьох маленьких читань/записів
- Пошуку за первинним ключем або добре підібраними вторинними індексами
- Транзакційних гарантій та консистентних зчитувань
- Високої конкуренції з короткими запитами
MySQL може виконувати аналітичні запити. Він навіть може робити це добре, якщо ваш набір даних невеликий і запити дисципліновані. Але коли панелі починають робити великі скани, важкі group-by, широкі джойни та «останні 90 днів за X і Y і Z», MySQL платить в I/O, churn буферного пулу та CPU. Ціна проявляється там, де не хочеться її бачити: у tail latency.
ClickHouse: двигун панелей, що їсть скани на сніданок
ClickHouse — колонкова OLAP-база, створена для високопродуктивних аналітичних запитів по великих наборах даних. Вона блискуче працює, коли потрібно:
- Швидкі агрегації по мільярдах рядків
- Вимірності з високою кардинальністю (з урахуванням обмежень і правильного дизайну)
- Стиснення і ефективне зберігання для даних подій із домінуючим додаванням
- Паралельне виконання запитів
Переваги ClickHouse мають інше коло очікувань: ви моделюєте під читання, приймаєте eventual consistency-патерни і вивчаєте особливості merges, parts та фонової роботи. Якщо використовувати його як MySQL (рядок за рядком оновлення, транзакційні робочі процеси, багато дрібних мутацій), він делікатно нагадає вам, що ви тримаєте його неправильно.
Ось правило прийняття рішення, яке я використовую у продакшн: якщо форма запиту — «scan + filter + group-by + часове вікно», це місце для ClickHouse. Якщо це «читати/оновити невелику кількість рядків, забезпечити інваріанти, брати гроші» — залишайте в MySQL.
Жарт №1: реальні панелі на MySQL — як буксирувати човен на скутері. Можна на короткий час, але всі дізнаються щось неприємне.
Цікавинки та історичний контекст (те, що пояснює сьогоднішні компроміси)
- Колонкові сховища стали мейнстрімом, коли аналітичні набори перевищили припущення кешування рядкових сховищ; стиснення і векторне виконання змінили правила гри.
- ClickHouse починався в Yandex для великомасштабної веб-аналітики; його будували, щоб швидко відповідати на «що сталося?», а не «чи цей платіж пройшов?»
- Дизайн буферного пулу InnoDB відмінний для «гарячих» робочих наборів; скани панелей можуть вижати сторінки, які потрібні оформленню замовлення.
- Реплікація MySQL історично використовувалася для зняття навантаження на читання; це допомагає, але аналітичні шаблони читання можуть все ще наситити репліки і викликати серйозний лаг.
- Зростання CDC (change data capture) став звичним, оскільки команди хотіли розділити OLTP і OLAP без щотижневих ручних «експортних джобів».
- Матеріалізовані подання — не новинка; змінилося те, наскільки дешево їх підтримувати сучасними сховищем і обчисленням, особливо в колонкових рушіях.
- Висококардинальні вимірності колись вважалися «податком»; ClickHouse зробив їх реальними, але не безкоштовними — dictionary encoding і уважне сортування досі важливі.
- «Реальний час» колись означав батч раз на годину для багатьох бізнесів; тепер очікування — хвилини або секунди через швидші петлі алертингу й маркетингу.
Архітектури, що працюють (і чому)
Патерн A: MySQL для OLTP, ClickHouse для панелей (рекомендовано для серйозного realtime)
Це доросла архітектура. Ви зберігаєте транзакційну істину в MySQL, стримуєте зміни і запитуєте ClickHouse для панелей. Ключова перевага — не швидкість. Це контроль радіусу ураження. Панелі можуть працювати некоректно без того, щоб відбирати кисень у оформлення замовлення.
Основні компоненти:
- MySQL primary для записів
- MySQL replica(и) для оперативного читання (опціонально)
- CDC-пайплайн (на базі binlog) у ClickHouse
- Таблиці ClickHouse, оптимізовані під запити (родина MergeTree)
- Попередні агрегації там, де вони виправдані (materialized views або rollup-таблиці)
Складність у виборі що реплікувати і як це змоделювати. Якщо банально реплікувати нормалізовані OLTP-таблиці в ClickHouse і очікувати ті самі шаблони джойнів, ви отримаєте повільні запити і збентежену команду. Денормалізуйте стратегічно.
Патерн B: «Просто використайте репліку MySQL для панелей» (працює, поки не перестане)
Репліка спокушає, бо здається безкоштовним обідом. Це не так. Лаг реплікації під важкими читаннями реальний, і якщо панелі б’ють репліку великими сканами, ви по суті будуєте I/O-піч. Це може бути прийнятно, якщо:
- Набір даних невеликий
- Панелі обмежені і стабільні
- Вимоги до свіжості м’які
- Ви терпите іноді піки лагу
Цей патерн зазнає поразки, коли використання панелей зростає (а воно зросте) або коли ключові бізнес-події корелюють з трафіковими піками (а вони корелюють).
Патерн C: Dual-write (додаток пише в MySQL і ClickHouse)
Dual-write може дати низьку латентність. Але він також додає ризик коректності: часткові записи, різний порядок, повтори і феномен «працює у стейджингу». Якщо ви це робите, вам потрібні ідемпотентність, бекфілл і історія примирення. Інакше панель стане проєктом творчого письма.
Патерн D: Батчевий ETL у ClickHouse (нудно, дешево, інколи ідеально)
Якщо бізнес може жити зі свіжістю 5–15 хвилин, батч перемагає простотою. Ви можете робити періодичні витяги з MySQL (оптимально — з репліки), завантажувати в ClickHouse і тримати пайплайн зрозумілим. Люди недооцінюють, наскільки цінне «зрозуміло» о 3:00 ранку.
Моделювання даних: частина, яку всі пропускають, а потім за це платять
Вибір схем MySQL, що саботують панелі
В OLTP нормалізація допомагає з коректністю і продуктивністю записів. В аналітиці нормалізовані схеми переносять вартість у джойни і повторні пошуки. Запит панелі, що джойнить orders, order_items, users, payments, shipments і promotions — чудовий спосіб спалити CPU і I/O.
Хитрість не в «денормалізуйте все». Хитрість — створити модель подій або фактів, яка відповідає на питання, які ви ставите:
- Факти: order_created, payment_captured, shipment_delivered, refund_issued
- Вимірності: merchant, country, device type, campaign, payment method
- Час: завжди першого класу; панелі — це часові ряди з думкою
Проєктування таблиць ClickHouse: ORDER BY — ваш індекс, і це зобов’язання
У ClickHouse ORDER BY в таблицях MergeTree — головний важіль швидкості запитів. Він визначає, як дані фізично організовані. Обирайте його, виходячи з найпоширеніших фільтрів. Для панелей у реальному часі це зазвичай:
- Часове вікно першим (наприклад, event_date, event_time)
- Невеликий набір поширених вимірів (наприклад, merchant_id, region, event_type)
Якщо ви виберете ORDER BY user_id, бо здається «первинним ключем», ви пошкодуєте, коли кожна панель фільтруватиме за часом і просканує все.
Попередні агрегації: коли вони допомагають, коли брешуть
Попередні агрегації (materialized views, rollups) потужні. Вони також створюють тонкий ризик: люди довіряють цифрам з панелі як джерелу істини, поки логіка попередніх агрегацій тихо відходить від семантики джерела. Це особливо поширено з поверненнями, частковими захопленнями і концепціями «підсумкового стану».
Моє правило: агрегувати лише тоді, коли ви можете точно визначити метрику, включно з пізньоприбуваючими подіями і корекціями. Інакше тримайте сирі факти і приймайте вищу вартість запитів (у ClickHouse це часто допустимо).
Пайплайни інгесту: CDC, батчі, стримінг і ілюзія «реального часу»
CDC з MySQL: на що ви насправді підписуєтеся
CDC — зазвичай вибір для приблизно realtime-панелей, бо дає низьку латентність без забивання MySQL частими запитами. Але CDC не магія; це розподілена система. Потрібно відповісти на питання:
- Порядок: чи події приходять у порядку комітів? по таблиці? по партиції?
- Зміни схеми: як обробляються DDL і зміни типів?
- Видалення/оновлення: чи модельовані як нові події або як мутації?
- Бекфіли: як безпечно ре-інгестувати історичні дані?
- Ідемпотентність: чи можна перемотати без подвiйного підрахунку?
Для ClickHouse багато команд віддають перевагу стрічці подій тільки з додаванням, навіть коли джерело оновлює рядки. Замість «оновити рядок» ви пишете «стан змінився» події. Це не завжди можливо, але часто чистіше.
Бюджети свіжості: визначіть їх або страждайте
«Реальний час» має бути бюджетом зі SLO: p95 ingest lag < 30 секунд, p99 < 2 хвилин, наприклад. Без цього команди ганятимуться за неправильними вузькими місцями. Також: вам зрештою знадобиться банер «дані затримуються» в UI панелі. Це не опціонально; це чесність.
Цитата (парафраз): Вернер Вогельс часто наполягав, що модель консистентності потрібно обирати навмисно; надійність походить від явних компромісів.
Плейбук швидкої діагностики (знайти вузьке місце за хвилини)
Коли панелі повільні або оформлення замовлення страждає, не починайте з думок. Почніть з де витрачається час і яка система насичена. Такий порядок добре працює під тиском.
1) Біль — у MySQL, ClickHouse чи в шарі застосунку?
- Перевірте p95 латентності запитів MySQL і CPU/iowait
- Перевірте латентність запитів ClickHouse і навантаження merges/фонового процесу
- Перевірте насичення сервісу панелей (треди, ліміти пулу, GC якщо релевантно)
2) Якщо оформлення замовлення повільне: підтвердіть спочатку взаємні перешкоди MySQL
- Шукайте довго виконувані SELECT від користувачів панелі
- Шукайте churn буферного пулу і читання з диска
- Шукайте спайки підключень і контенцію потоків
3) Якщо панелі повільні: визначте, чи це свіжість даних, форма запиту чи розмітка сховища
- Чи зростає ingest lag? Тоді проблема в пайплайні або в пропускній здатності вставок/мерджів ClickHouse.
- Якщо ingest в нормі, але запит повільний: проблема в ORDER BY / партиціюванні / занадто широких сканах.
- Якщо запити швидкі, але UI повільний: проблема в застосунку та стратегії кешування.
4) Вирішіть негайну міру пом’якшення
- Обмежте запити панелей (rate limit, cached snapshots)
- Перемістіть панелі з MySQL негайно (навіть на репліку), якщо оформлення горить
- Тимчасово зменшіть часове вікно і кількість вимірів
- Масштабируйте ClickHouse або тюньте merges, якщо він вузьке місце
Практичні завдання: команди, виходи та яке рішення ухвалити
Нижче — практичні дії, які я реально запускаю під час інцидентів і циклів тюнінгу. Кожна містить команду, приклад виходу, що це означає і яке рішення вона навчає. Більшість із них можна запускати з бастіону або безпосередньо на хостах БД з належним доступом.
Завдання 1: Виявити запити панелей, що зараз шкодять MySQL
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST\G" | sed -n '1,120p'
*************************** 1. row ***************************
Id: 83421
User: dashboard_ro
Host: 10.22.8.14:51122
db: checkout
Command: Query
Time: 37
State: Sending data
Info: SELECT date(created_at) d, count(*) FROM orders WHERE created_at > now() - interval 7 day GROUP BY d
*************************** 2. row ***************************
Id: 83455
User: checkout_app
Host: 10.22.3.7:60318
db: checkout
Command: Query
Time: 1
State: updating
Info: UPDATE inventory SET reserved = reserved + 1 WHERE sku = 'A17-44'
Значення: у вас довготривалий скан/group-by на primary, який конкурує з транзакційними оновленнями.
Рішення: вбити або обмежити запит панелі; перемістити панелі в ClickHouse або принаймні на репліку; негайно додати захисні механізми для запитів.
Завдання 2: Вбити конкретного порушника (точечно, не героїчно)
cr0x@server:~$ mysql -e "KILL 83421;"
Query OK, 0 rows affected (0.00 sec)
Значення: проблемний запит припинено.
Рішення: дотримуватися політики доступу: користувач панелі не повинен виконувати запити на primary, ні за яких обставин.
Завдання 3: Перевірити поточне навантаження MySQL і найповільніші інструкції через Performance Schema
cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, ROUND(AVG_TIMER_WAIT/1e9,2) AS avg_ms FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 3\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT date(created_at) d, count(*) FROM orders WHERE created_at > ? GROUP BY d
COUNT_STAR: 1821
total_s: 614.35
avg_ms: 337.41
*************************** 2. row ***************************
DIGEST_TEXT: SELECT * FROM cart_items WHERE cart_id = ?
COUNT_STAR: 92144
total_s: 211.02
avg_ms: 2.29
*************************** 3. row ***************************
DIGEST_TEXT: UPDATE inventory SET reserved = reserved + ? WHERE sku = ?
COUNT_STAR: 40122
total_s: 88.44
avg_ms: 2.20
Значення: дайджест панелі домінує за загальним DB-часом.
Рішення: віддати пріоритет перенесенню цієї метрики в ClickHouse або переписати її з індексом/summary-таблицею, і ввести обмеження на запити.
Завдання 4: Підтвердити, чи відбувається churn буферного пулу
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Innodb_buffer_pool_reads| 19433211 |
+-------------------------+----------+
+----------------------------------+------------+
| Variable_name | Value |
+----------------------------------+------------+
| Innodb_buffer_pool_read_requests | 8821132441 |
+----------------------------------+------------+
Значення: читання з диска немалі; якщо перше число швидко зростає під час використання панелей, скани витісняють гарячі сторінки.
Рішення: ізолюйте аналітику від primary; розгляньте збільшення buffer pool тільки після ізоляції (велика піч лишається піччю).
Завдання 5: Перевірити лаг реплікації MySQL перед тим, як звинувачувати «сервер панелей»
cr0x@server:~$ mysql -h mysql-replica-1 -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: 187
Значення: репліка відстає приблизно на ~3 хвилини; панелі, що читають з неї, будуть застарілими і можуть викликати питання «чому числа не сходяться?».
Рішення: або прийміть явні SLO свіжості, додайте більше реплік, або перемістіть аналітику в ClickHouse із контрольованим інгестом.
Завдання 6: Перевірити формат binlog MySQL на сумісність з потребами CDC
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'binlog_format'; SHOW VARIABLES LIKE 'gtid_mode';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_mode | ON |
+---------------+-------+
Значення: ROW binlog-и і GTID спрощують коректність CDC і відновлення.
Рішення: якщо не ROW — плануйте міграцію; CDC на STATEMENT — пастка, якщо ви не полюбляєте ребусів.
Завдання 7: Інспектувати виконувані запити ClickHouse (знайти «халява» панелі)
cr0x@server:~$ clickhouse-client --query "SELECT query_id, user, elapsed, read_rows, formatReadableSize(read_bytes) rb, query FROM system.processes ORDER BY elapsed DESC LIMIT 2"
d8e2c6c1-8d1b-4c3f-bc45-91b34a6c12de dashboard 12.941 812334112 34.21GiB SELECT merchant_id, count() FROM events WHERE event_time > now() - INTERVAL 30 DAY GROUP BY merchant_id
a1a01f2a-9d72-4d85-9b43-423a1c91a8f1 internal 1.120 182991 17.02MiB INSERT INTO events FORMAT JSONEachRow
Значення: запит панелі читає 34 GiB, щоб відповісти на питання, яке, ймовірно, потребує вузьшого часового вікна або кращого ORDER BY.
Рішення: виправити ORDER BY/партиціювання, додати rollup-и і обмежити за замовчуванням lookback панелі. Також розглянути квоти на запити.
Завдання 8: Перевірити тиск merges у ClickHouse (фонові процеси крадуть ваш обід)
cr0x@server:~$ clickhouse-client --query "SELECT database, table, sum(merge_type = 'Regular') AS regular_merges, sum(merge_type = 'TTL') AS ttl_merges, round(sum(elapsed),1) AS total_s FROM system.merges GROUP BY database, table ORDER BY total_s DESC LIMIT 5"
analytics events 4 0 912.4
analytics sessions 1 2 211.9
Значення: merges активні і довготривалі на гарячій таблиці events.
Рішення: тюньте розміри батчів вставок, партиціювання і налаштування merge; додайте потужність, якщо merges постійно відстають.
Завдання 9: Підтвердити «вибух частин» у ClickHouse (класичні «занадто дрібні вставки»)
cr0x@server:~$ clickhouse-client --query "SELECT table, count() parts, formatReadableSize(sum(bytes_on_disk)) disk FROM system.parts WHERE active AND database='analytics' GROUP BY table ORDER BY parts DESC LIMIT 3"
events 12844 1.92TiB
sessions 2211 204.11GiB
rollup_minute 144 9.87GiB
Значення: 12k активних parts свідчить, що вставки занадто гранулярні, що призводить до боргу merges і накладних витрат на запити.
Рішення: батчувати вставки, використовувати buffering або налаштувати інструмент інгесту; зменшити кількість parts до того, як сліпо масштабувати залізо.
Завдання 10: Виміряти ingest lag як метрику першого класу (не вгадуйте)
cr0x@server:~$ clickhouse-client --query "SELECT max(event_time) AS max_event, now() AS now_ts, dateDiff('second', max_event, now_ts) AS lag_s FROM analytics.events"
2025-12-30 11:58:29 2025-12-30 12:00:03 94
Значення: ClickHouse відстає приблизно на 94 секунди від «тепер» для цієї таблиці.
Рішення: якщо ваше SLO — 30 секунд, фокусуйтеся на пайплайні і пропускній здатності мерджів; якщо SLO — 2 хвилини, ви в межах і можете перестати панікувати.
Завдання 11: Перевірити ефективність pruning у ClickHouse за допомогою EXPLAIN (чи ви скануєте все?)
cr0x@server:~$ clickhouse-client --query "EXPLAIN indexes=1 SELECT count() FROM analytics.events WHERE event_date >= today()-1 AND merchant_id=42"
Expression ((Projection + Before ORDER BY))
ReadFromMergeTree (analytics.events)
Indexes:
MinMax
Keys: event_date
Condition: (event_date in [today() - 1, +Inf))
PrimaryKey
Keys: (event_date, merchant_id)
Condition: (event_date in [today() - 1, +Inf)) AND (merchant_id in [42, 42])
Значення: застосовуються і MinMax, і умови первинного ключа; pruning має бути пристойним.
Рішення: якщо ви не бачите корисних умов індексів, перегляньте ORDER BY і ключ партиціювання.
Завдання 12: Підтвердити, що панель не DDoS-ить вашу БД коротким опитуванням
cr0x@server:~$ sudo ss -tnp | awk '$4 ~ /:3306$/ {print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head
422 10.22.8.14
38 10.22.8.15
21 10.22.3.7
Значення: один хост панелі має 422 TCP-з’єднання до MySQL.
Рішення: додайте пулінг підключень, введіть обмеження max connections, кешуйте результати і припиніть «оновлювати кожну 1s» біля джерела.
Завдання 13: Перевірити host-level I/O wait на MySQL-box (швидка перевірка реальності)
cr0x@server:~$ iostat -x 1 3
Linux 6.8.0 (mysql-primary) 12/30/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
18.12 0.00 6.33 24.51 0.00 51.04
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s w_await aqu-sz %util
nvme0n1 812.0 98304.0 0.0 0.00 8.12 121.1 244.0 16384.0 3.91 6.41 92.3
Значення: високий iowait і ~92% використання диска; MySQL обмежений I/O під поточним навантаженням.
Рішення: припиніть скани на primary, потім перегляньте дискову/IOPS-частину; кидати швидші диски на змішані навантаження — дорогий спосіб залишатися неправим.
Завдання 14: Перевірити використання диска ClickHouse і чи merges блокуються через місце
cr0x@server:~$ df -h /var/lib/clickhouse
Filesystem Size Used Avail Use% Mounted on
/dev/nvme1n1p1 3.5T 3.3T 140G 96% /var/lib/clickhouse
Значення: ClickHouse на 96% зайнятий; merges можуть падати або уповільнюватися, що збільшує кількість parts і гальмує систему.
Рішення: звільніть місце зараз (ретеншн/TTL), додайте диск або перемістіть холодні партиції. Не чекайте «до вихідних»; саме тоді merges зазвичай панікують.
Три корпоративні міні-історії з практики
Міні-історія 1: Інцидент через хибне припущення
Середній рітейлер захотів «живі» дашборди під час святкової хвилі. Команда розумно припустила, що read replica ізолює аналітику від оформлення. Вони створили репліку, направили на неї панель і заспокоїлися.
Перша проблема — свіжість. Під першим великим трафіком лаг реплікації виріс зі секунд до хвилин. Керівництво побачило «зависання» на панелі і вимагало виправити. Хтось з дивовижною самовпевненістю запропонував читати з primary «просто для кампанії».
Через п’ять хвилин латентність оформлення стрибнула. Не тому, що SELECT драматично блокують, а тому що ці скани перегортали буферний пул і насичували I/O. Замовлення все ще комітяться, але tail latency стала огидною і почалися таймаути.
Хибне припущення було тонким: «читання — безпечні». Читання не безпечні, коли вони великі, часті і некешовані. Читання може вбити систему з інтенсивними записами, відголоднюючи її від ресурсів.
Виправлення не стало героїчним тюнінгом запитів. Виправлення було архітектурним: вони повністю зняли панелі з MySQL, погодилися на 60–120 секунд свіжості під час кампанії і після цього запустили CDC→ClickHouse пайплайн. Інцидент закінчився політикою: панелі не роблять запити до primary, навіть якщо хтось старший кричить.
Міні-історія 2: Оптимізація, що відшкодувала назад
SaaS-компанія перенесла аналітику в ClickHouse і була в захваті. Запити були швидкі. Усі були щасливі. Потім хтось оптимізував інгест «щоб зменшити латентність», відправляючи кожну подію як окрему вставку. Це працювало в тестах. Але в проді це спричинило вибух parts.
За кілька днів фонові merges працювали безперервно. На перший погляд CPU виглядав «нормально», але диск був гарячий, і латентність запитів стала спорадично стрибати. Панелі були швидкі, потім раптом повільні. Команда почала додавати ноди. Це допомогло недовго, потім merges «наздогнали», як податковий аудитор.
Вони пробували тонувати merges і навіть думали про зміну движка таблиць. Справжня причина була вгорі: занадто багато дрібних вставок. ClickHouse може багато ingest-ити, але він хоче батчі. Маленькі parts коштують метаданих, накладних мерджів і витрат на запити. Це смерть від тисяч комітів.
Виправлення було нудним: буферизувати події кілька секунд і вставляти пакетами. Латентність трохи зросла, але система стала стабільною і дешевшою. Оптимізація, що відшкодувала назад, була гонитвою за секундами свіжості ціною стійкої продуктивності.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Інша команда працювала і з MySQL, і з ClickHouse для панелей. У них була рідкісна річ: письмове SLO щодо свіжості, метрика ingest-lag на самій панелі і онкалл-рукбук, що починався з «перевірити затримку, перш ніж відлагоджувати запити».
В один день панелі стали «пласкими». Продажі явно йшли, але графік виглядав мертвим. Slack наповнився панікою. Онкалл виконав рукбук і перевірив max event time у ClickHouse проти now. Лаг становив 18 хвилин. Це вже не проблема запиту; це проблема інгесту.
Вони перевірили CDC-консьюмера і виявили, що він застряг на одному поганому рядку через несподівану зміну схеми: колонка стала non-nullable, і мапінг інгесту відкидав записи. Завдяки явним метрикам вони не витратили годину на тюнінг запитів, які вже були швидкі.
Вони застосували фікс мапінгу схеми, перемотали беклог і панелі відновилися. Ніхто не складав поему про рукбук. Ніхто не прославляє «лаг-аліяти». Але нудна практика врятувала день плутанини і запобігла тому, щоб хтось «виправив» проблему, направивши панель на MySQL.
Жарт №2: панель, що запитує production MySQL — це по суті тест продуктивності, тільки оцінюють результати сердиті клієнти.
Типові помилки: симптом → корінна причина → виправлення
1) Підскоки p95 латентності оформлення при запуску панелі
- Симптом: CPU MySQL і iowait зростають; у повільних запитах з’являються SELECT типу аналітики.
- Корінна причина: панелі запитують primary або спільну репліку, що також обслуговує застосунок; churn буферного пулу і насичення I/O.
- Виправлення: ізолювати панелі в ClickHouse; обмежити привілеї користувачів; додати ліміти і таймаути на запити; кешувати відповіді панелей.
2) Панелі «в реальному часі», але числа не збігаються з MySQL
- Симптом: ClickHouse показує менше замовлень за останні хвилини, ніж MySQL; бекфіли «виправляють» минуле.
- Корінна причина: lag CDC, переплутаний порядок подій або відсутня семантика update/delete.
- Виправлення: відстежувати ingest lag; проектувати ідемпотентні ключі подій; модель state-change явно; додати роботи з примирення для критичних метрик.
3) ClickHouse запити раптово гальмують, хоча залізо в порядку
- Симптом: та сама панель іноді 200ms, іноді 8s; бачимо активність merges.
- Корінна причина: надто багато parts, борг merges або диск майже заповнений, що викликає тротлінг.
- Виправлення: батчувати вставки; моніторити кількість parts; збільшити вільний простір на диску; тюнити партиціювання; додавати ноди тільки після зменшення фрагментації інгесту.
4) Панелі на основі реплік показують застарілі дані під час піків
- Симптом: «Seconds_Behind_Source» стрибає; свіжість панелей непослідовна.
- Корінна причина: репліка не встигає застосовувати relay log під читальним навантаженням; реплікація — не безкоштовна аналітична смуга.
- Виправлення: перемістити панелі в ClickHouse; додати виділену аналітичну репліку; зменшити навантаження запитів; масштабувати I/O і CPU репліки.
5) Зростання сховища ClickHouse швидше, ніж очікували
- Симптом: використання диска росте; політики ретеншну не працюють; merges крутяться.
- Корінна причина: відсутні TTL/видалення партицій; зберігаються надто детальні колонки; дублювання подій через неідемпотентний інгест.
- Виправлення: включити TTL; компактити схему; додати ключі дедупа і забезпечити принаймні ефективно-once через ідемпотентність.
6) «Ми просто додаємо індекс» стає щотижневою ритуалом
- Симптом: MySQL має десятки індексів; записи сповільнюються; і все одно панелі не виконуються.
- Корінна причина: намагання вирішити OLAP-скани індексацією OLTP; індекси допомагають для пошуку, але не для необмеженого багатовимірного аналізу.
- Виправлення: припиніть індексувати шляхом до депресії; перемістіть аналітику в ClickHouse і моделлюйте під нього.
Чеклісти / поетапний план
Покроковий план: будувати панелі в реальному часі, не знищуючи оформлення замовлення
-
Визначте SLO свіжості.
- Запишіть p95 і p99 цілі ingest lag.
- Визначте, що відбувається при їх порушенні (банер, fallback кеш, деградований режим).
-
Класифікуйте запити панелей за формою.
- Scan + group-by + часове вікно → ClickHouse.
- Point lookups для drill-down → можуть залишатися в MySQL (або в окремому сервісі).
-
Встановіть обмеження на MySQL прямо зараз.
- Панелям заборонено підключатися до primary.
- Встановіть таймаути і max execution time для аналітичних користувачів.
- Додайте rate limiting на рівні API.
-
Обрати підхід інгесту.
- CDC для низької латентності і відносної простоти.
- Батч для простоти, коли свіжість може бути хвилинами.
- Уникайте dual-write, якщо немає сильної експлуатаційної зрілості.
-
Спроєктуйте факт-таблицю в ClickHouse.
- Віддавайте перевагу append-only подіям зі стабільними id.
- Обирайте ORDER BY на основі часу + поширених вимірів.
-
Впроваджуйте попередні агрегації лише для підтверджених «гарячих» запитів.
- Почніть з сирих фактів; виміряйте вартість запитів; потім робіть rollup, де це виправдано.
- Визначте пізньоприбуваючі події і поведінку корекцій.
-
Операціоналізуйте це.
- Налаштуйте алерти на ingest lag, кількість parts, вільне місце на диску і латентність запитів.
- Створіть онкалл-рукбуки, що починаються з «дані запізнюються?»
-
Тестуйте режими відмов.
- CDC consumer впав.
- Диск ClickHouse майже заповнений.
- Запит панелі випадково розширив часовий діапазон до 365 днів.
Чекліст безпеки: захистіть оформлення замовлення від амбіцій панелей
- Панелі не можуть підключатися до MySQL primary (мережеві ACL + суворе керування обліковими даними).
- Користувачі MySQL для аналітики мають суворі привілеї і низькі ресурсні ліміти.
- API панелі має кешування і rate limiting.
- За замовчуванням часове вікно панелі невелике; розширення вимагає явної дії користувача.
- ClickHouse має політику вільного місця на диску (ціль < 80–85% використання).
- Інжест лаг відображається і є налаштоване оповіщення.
Питання та відповіді (FAQ)
1) Чи можу я лишити панелі в MySQL, якщо оптимізую запити?
Для невеликого набору даних і стабільних панелей — так. Але щойно панелі стають дослідницькими (багато фільтрів, довші вікна), ви боретеся з дизайном рушія. Якщо оформлення важливе — ізолюйте аналітику раніше.
2) Чи «безпечно» використовувати репліку MySQL для панелей?
Це безпечніше, ніж бити primary, але не «безпечно» за замовчуванням. Великі скани можуть наситити репліку, збільшити лаг і все одно викликати операційні проблеми. Виділена аналітична репліка з суворим контролем запитів може бути проміжним кроком.
3) Наскільки realtime може бути ClickHouse?
Від секунд до хвилин залежно від батчування інгесту, тиску merge і шаблонів запитів. Якщо ви намагаєтесь примусити субсекундний інгест з дрібними вставками, ви заплатите в інших місцях (parts, merges, диск).
4) Яка найбільша помилка моделювання ClickHouse для панелей?
Вибір ORDER BY без думки про поширені фільтри, особливо час. Якщо панелі фільтрують за часом і merchant, ваш ORDER BY має це відображати.
5) Чи потрібні мені materialized views в ClickHouse?
Не в перший день. Почніть з сирих фактів і виміряйте. Використовуйте materialized views, коли невелика кількість запитів домінує за вартістю і семантика метрики стабільна.
6) Як обробляти оновлення і видалення з MySQL у ClickHouse?
Віддавайте перевагу моделюванню подій (append «state change» events) замість частих мутацій. Якщо потрібно дзеркалити стан рядка, використовуйте версіонування і ключі дедупації, і приймайте, що важкі мутації дорогі.
7) Чому числа відрізняються між MySQL і ClickHouse під час інцидентів?
Зазвичай через ingest lag, переплутаний порядок подій або дублювання інгесту. Ставте «свіжість даних» як метрику, а не як відчуття, і показуйте її на панелі.
8) На які метрики варто налаштувати алерти для цього стеку?
MySQL: латентність запитів (p95/p99), активні треди, hit rate буферного пулу, використання диска/iowait, лаг реплікації.
ClickHouse: латентність запитів, кількість parts, backlog merges, використання диска, швидкість вставок, ingest lag.
9) Чи варто кешувати результати панелей?
Так, якщо ваша панель не критична для кожної секунди. Кешуйте на рівні API з коротким TTL і правилами інвалідизації. Це дешевше, ніж «ще одна база».
10) Яка розумна дефініція «реального часу» для бізнес-панелей?
Та, яку ваші оператори здатні підтримувати надійно. Багато організацій комфортно працюють зі свіжістю 30–120 секунд. Зробіть це явним, вимірюйте і показуйте.
Висновок: що робити в понеділок вранці
Якщо запити панелі торкаються MySQL primary — виправте це насамперед. Не завтра. Сьогодні. Додайте виділений шлях: або репліка з суворими лімітами як тимчасовий захід, або (переважно) ClickHouse як аналітичну площину.
Потім зробіть роботу, яка справді запобігає повторним інцидентам:
- Визначте SLO свіжості і показуйте ingest lag у UI панелі.
- Моделюйте факти для ClickHouse з ORDER BY, що відображає реальні фільтри.
- Батчуйте інгест, щоб уникнути вибуху parts і боргу merges.
- Операціоналізуйте: налаштуйте алерти на lag, merges, вільний простір і найгірші запити.
- Напишіть рукбук і зробіть його першим ресурсом у стресі.
Мета не просто «швидкі панелі». Мета — «швидкі панелі без перетворення оформлення замовлення на науковий експеримент». Розділіть навантаження, вимірюйте затримки і дайте кожній базі даних робити справу, для якої її створили.