MySQL vs ClickHouse: зупиніть, щоб аналітика не вбивала OLTP (План чистого розділення)

Було корисно?

Десь у вашій компанії доброзичливий аналітик щойно оновив інформаційну панель. Тепер оформлення замовлення повільне, API таймаутиться, а канал чергових перетворився на групову терапію.

Це не проблема «поганого запиту». Це архітектурна проблема: OLTP і аналітика — різні тварини, і тримати їх в одному вольєрі має передбачувані наслідки. Вирішення — чіткий розділ: MySQL виконує транзакції, ClickHouse — аналітику, і ви припиняєте дозволяти цікавості DDoS-ити шлях доходу.

Фактична проблема: OLTP і аналітика конфліктують на рівні зберігання

OLTP про затримки, коректність і передбачувану конкуренцію. Ви оптимізуєтеся під тисячі невеликих читань/записів на секунду, щільні індекси та гарячі робочі набори, що вміщаються в пам’ять. Вартість одного повільного запиту сплачується відразу — у вигляді поганого досвіду клієнта, таймаутів і повторних спроб, що підсилюють навантаження.

Аналітика про пропродажність, широкі скани і агрегацію. Ви оптимізуєте під читання великої кількості даних, їхнє стиснення й векторизоване виконання, щоб перетворити CPU на відповіді. Аналітичні запити часто «сором’язливо паралельні» і не проти кілька секунд затримки — поки їх не спрямувати на транзакційну базу і вони не перетворяться на denial-of-service з поворотною таблицею у вкладенні.

Основний висновок: OLTP і аналітика змагаються за одні й ті ж обмежені ресурси — CPU-цикли, дискове I/O, page cache, buffer pool, блокування/затримки і фонове обслуговування (флаші, контрольні точки, злиття). Навіть якщо ви додаєте репліку для читання, ви все одно часто ділитеся тим самим фундаментальним болем: лаг реплікації, насичення I/O і непередбачувана продуктивність через скани.

Де ножа встромлює: конфлікт ресурсів у MySQL

  • Забруднення buffer pool: великий звітний запит читає холодний зріз історії, витісняє гарячі сторінки й раптово ваш основний робочий потік стає залежним від диска.
  • Фоновий тиск InnoDB: довгі скани + тимчасові таблиці + сорти можуть збільшити кількість «брудних» сторінок і навантаження на redo. Флеш-шторм — неприємний гість.
  • Блокування й MDL: деякі шаблони звітності викликають неприємні взаємодії (уявіть «ALTER TABLE у робочий час» зустрічає «SELECT …», що утримує MDL).
  • Реплікаційний лаг: важкі запити на репліці крадуть I/O і CPU у SQL-потоку, що застосовує зміни.

Куди пасує ClickHouse

ClickHouse створений для аналітики: колонкове зберігання, стиснення, векторизоване виконання і агресивна паралельність. Він очікує, що ви читаєте багато рядків, але лише кілька колонок, і винагороджує за групування роботи в партиції й сортувальні ключі.

Дисципліна проста: вважайте MySQL системою запису для транзакцій. Вважайте ClickHouse системою істини для аналітики — істини в сенсі «виведено з запису, відтворювано і запитувано в масштабі».

Перефразована ідея Вернера Фогельса: «Усе ламається; проектуйте на випадок відмов». Це стосується й даних: проєктуйте під відмови, як-от хвилі запитів, лаг і бекфіли.

MySQL vs ClickHouse: реальні відмінності, що важать в продакшні

Розташування зберігання: рядок проти стовпця

MySQL/InnoDB орієнтований на рядки. Чудово підбирати рядок за первинним ключем, оновлювати кілька колонок, підтримувати вторинні індекси і забезпечувати обмеження. Але сканувати мільярд рядків для обчислення агрегатів означає переносити через движок цілі рядки, торкатися сторінок, які вам не потрібні, і спалювати кеш.

ClickHouse орієнтований на колонки. Він читає лише потрібні колонки, добре їх стискає (часто драматично) і обробляє векторами. Ви платите на початку іншими обмеженнями моделювання — денормалізацією, уважним вибором ключів порядку й процесом злиття, якого потрібно дотримуватись.

Модель конкуренції: транзакційна проти аналітичної паралельності

MySQL добре обробляє багато коротких транзакцій одночасно — до меж вашої схеми, індексів і обладнання. ClickHouse теж справляється з великою кількістю читань, але магія в ефективному паралелізуванні великих читань і агрегацій. Якщо ви вкажете BI-інструменту ClickHouse і дозволите необмежену конкуренцію без лімітів запитів, він постарається підпалити ваш CPU. Ви можете і повинні обмежувати це.

Послідовність і коректність

MySQL — ACID (з відомими нюансами, але так, це ваш транзакційний анклав). ClickHouse зазвичай врешті-решт узгоджений для інжекції даних і орієнтований на додавання. Ви можете моделювати оновлення/видалення, але робите це на умовах ClickHouse (ReplacingMergeTree, CollapsingMergeTree, стовпці версій або асинхронні видалення). Це нормально: аналітика зазвичай хоче поточну істину і часову послідовність змін, а не транзакційні семантики на рівні рядка.

Індексація та шаблони запитів

Індекси MySQL — B-дерева, що підтримують точкові пошуки і діапазонні скани. ClickHouse використовує впорядкування первинного ключа й розріджені індекси, а також data skipping індекси (наприклад bloom-фільтри), де це допомагає. Найкращий запит для ClickHouse — той, що може пропустити великі шматки даних, бо ваше партиціювання й ORDER BY відповідають шаблонам доступу.

Операційна позиція

Операції MySQL крутяться навколо здоров’я реплікації, резервних копій, міграцій схем і стабільності запитів. Операції ClickHouse — навколо злиттів, використання диска, кількості частин, TTL і управління запитами. Іншими словами: ви міняєте одного дракона на іншого. Угода все одно варта того, бо ви припиняєте дозволяти аналітиці псувати оформлення замовлення.

Жарт №1: Оновлення панелі — єдиний вид «залучення користувачів», що одночасно може підвищувати рівень помилок і відтік.

Факти та історичний контекст (корисно, не тривіально)

  1. InnoDB став за замовчуванням у MySQL 5.5 (приблизно 2010 рік), закріпивши row-store OLTP-поведінку для більшості розгортань.
  2. ClickHouse починався в Yandex для забезпечення аналітики в масштабі; він виріс у світі, де швидкий скан великої кількості даних — головне завдання.
  3. Колонкові сховища набули популярності, бо CPU став швидшим за диски, і стиснення + векторизоване виконання дозволили витрачати CPU, щоб уникати I/O.
  4. «Забруднення» InnoDB buffer pool — класичний режим відмови, коли довгі скани знищують гарячі сторінки; движок не «ламався», він робив те, що його просили.
  5. Аналітика через реплікацію існує десятиліттями: люди передавали OLTP-зміни в сховища даних ще до того, як «data lake» потрапив в резюме.
  6. Кеш запитів MySQL видалили в MySQL 8.0, бо він створював контенцію і не масштабувався; кешування не безкоштовне, а глобальні блоки дорогі.
  7. Сімейство MergeTree у ClickHouse зберігає дані в частинах і зливає їх у фоновому режимі — добре для записів і стиснення, але створює операційні сигнали (кількість частин, backlog злиттів), які треба моніторити.
  8. «Зіркова схема» і вимірна модель передували сучасним інструментам; ClickHouse часто штовхає команди назад до денормалізованих, дружніх до запитів форм, бо джойни в масштабі мають реальну вартість.

План чистого розділення: підходи, що не плавлять продакшн

Принцип 1: MySQL — для обслуговування користувачів, не цікавості

Зробіть політику: продукційний MySQL не є базою для звітності. Не «зазвичай». Не «окрім швидкого запиту». Ніколи. Якщо комусь потрібен одноразовий запит, запускайте його проти ClickHouse або контрольованого снапшоту.

Ви отримаєте заперечення. Це нормально. Хитрість — замінити «ні» на «ось безпечний шлях». Забезпечте безпечний шлях: доступ до ClickHouse, підготовлені набори даних і робочий процес, що не вимагає благати чергового дозволу на JOIN за рік замовлень.

Принцип 2: Оберіть стратегію переміщення даних відповідно до терпимості до відмов

Є три загальні способи підживлювати ClickHouse з MySQL. У кожного є свої гострі кути.

Варіант A: пакетний ETL (дамп і завантаження)

Ви витягуєте щогодини/щодня снапшоти (mysqldump, CSV-експорти, Spark‑джоби), завантажуєте в ClickHouse і приймаєте застарілість. Це найпростіше в операційному плані, але болісне, коли потрібні майже реальні метрики, а бекфіли важкі.

Варіант B: інжекція через реплікацію (CDC)

Фіксуєте зміни з binlog MySQL і стримуєте їх у ClickHouse. Це дає майже реальну аналітику, ізолюючи MySQL від навантаження запитів. Але це вводить здоров’я пайплайну як першокласну виробничу проблему: лаг, дрейф схем і перепроцесування стають вашим новим хобі.

Варіант C: подвійний запис (додаток пише в обидва)

Не робіть цього. Або, якщо ви зовсім мусите, робіть лише з надійною ідемпотентністю, асинхронною доставкою та завданням зі звірки, яке припускає, що подвійний запис іноді бреше.

План чистого розділення зазвичай означає CDC плюс кураторські моделі даних в ClickHouse. Пакетний ETL прийнятний, коли ви можете терпіти застарілість. Подвійний запис — пастка, якщо вам не подобається пояснювати невідповідності даних під час постмортемів інцидентів.

Принцип 3: Модель ClickHouse під ваші запитання, не під схему

Більшість OLTP‑схем нормалізовані. Аналітика хоче менше джойнів, стабільні ключі і таблиці подій. Ваше завдання — побудувати аналітичне представлення, яке легко запитувати і важко зловживати.

  • Віддавайте перевагу таблицям подій: orders_events, sessions, payments, shipments, support_tickets. Додавайте події. Виводьте факти.
  • Партиціюйте за часом: зазвичай по днях або місяцях. Це дає передбачуване відсікання і керований TTL.
  • ORDER BY за вимірами запитів: розміщуйте найпоширеніші ключі фільтрації/групування раніше в ORDER BY (після ключа часу, якщо ви завжди фільтруєте за часом).
  • Попередньо агрегуйте там, де це стабільно: materialized views можуть виробляти зведення, щоб панелі не сканували сирі дані повторно.

Принцип 4: Управління краще за героїзм

ClickHouse може відповідати на запитання досить швидко, що люди почнуть задавати гірші запитання частіше. Вам потрібні огорожі:

  • Розділіть користувачів і квоти: BI‑користувачі отримують таймаути і ліміт пам’яті. ETL має інший профіль.
  • Встановіть max threads і конкуренцію: уникайте «грому стад» паралельних запитів.
  • Використовуйте виділені «gold» набори даних: стабільні view або таблиці, від яких залежать панелі, версіоновані за потреби.
  • Визначте SLO: SLO на латентність MySQL святе. SLO на свіжість ClickHouse переговорний, але вимірюваний.

Практичні завдання (команди, виводи, рішення)

Це рухи, які ви фактично робите о 02:13. Кожне завдання містить команду, приклад виводу, що це означає, і рішення, яке потрібно ухвалити.

Завдання 1: Підтвердити, що MySQL страждає через аналітичні скани (top digests)

cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT/1e12 AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN ? AND ? GROUP BY customer_id
COUNT_STAR: 9421
total_s: 18873.214
*************************** 2. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE created_at > ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 110233
total_s: 8211.532

Що це означає: Ваш найгірший час походить від класичного звітного агрегату по діапазону дат. Це не «один повільний запит», це повторюваний біль.

Рішення: Заблокуйте або перенаправте цей шаблон аналітичного запиту. Не перетворюйте MySQL на OLAP‑движок. Почніть з переміщення цієї панелі в ClickHouse або в таблицю-зведення.

Завдання 2: Перевірити поточну активність потоків MySQL (чи це натовп?)

cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head
Id	User	Host	db	Command	Time	State	Info
31	app	10.0.2.14:51234	prod	Query	2	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
44	app	10.0.2.14:51239	prod	Query	2	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
57	app	10.0.2.14:51241	prod	Query	1	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id

Що це означає: Багато однакових запитів виконуються одночасно. Це панель або флот працівників, що робить одну й ту саму важку роботу.

Рішення: Тротлінг на рівні додатка/BI і введення кешування або попередньої агрегації в ClickHouse. Також розгляньте ліміти з’єднань MySQL і керування ресурсами на користувача.

Завдання 3: Перевірити тиск на InnoDB buffer pool (гарячі сторінки витісняються)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
Variable_name	Value
Innodb_buffer_pool_read_requests	987654321
Innodb_buffer_pool_reads	12345678

Що це означає: Велика кількість фізичних читань (Innodb_buffer_pool_reads) відносно логічних читань вказує, що ваш робочий набір не тримається в пам’яті — часто через великі скани.

Рішення: Зупиніть скани (переносьте аналітику), і лише потім розгляньте збільшення buffer pool або налаштування навантаження. Обладнання не зможе замінити погану комбінацію навантажень назавжди.

Завдання 4: Виявити насичення дискового I/O на хості MySQL

cr0x@server:~$ iostat -xz 1 3
Linux 6.2.0 (mysql01) 	12/30/2025 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.12    0.00    6.44   31.55    0.00   43.89

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await aqu-sz  %util
nvme0n1         820.0  64200.0     0.0    0.0   12.4    78.3     410.0  18800.0    9.8   18.2   98.7

Що це означає: %util близько 100% і високий iowait означають, що диск — вузьке місце. Скани аналітики обожнюють такий результат.

Рішення: Негайно: зменшіть конкуренцію запитів, вбийте найгірших виконавців, перемістіть аналітику в ClickHouse. Довгостроково: розділіть зберігання і навантаження; не розраховуйте лише на «швидший NVMe» як стратегію.

Завдання 5: Визначити лаг реплікації MySQL (ваша «репліка для читання» не допомагає)

cr0x@server:~$ mysql -h mysql-replica01 -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_SQL_Running|Slave_IO_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 487

Що це означає: Репліка відстає приблизно на 8 хвилин. Панелі, що читають її, брешуть. Гірше: якщо ви переключитесь на неї під час відмови, можете втратити недавні транзакції.

Рішення: Не використовуйте репліку як аналітичний стік. Використовуйте CDC у ClickHouse або принаймні виділіть репліку під відмову з контрольованим доступом до запитів і гарантованими ресурсами.

Завдання 6: Показати фактичний план дорогого запиту (перестаньте гадати)

cr0x@server:~$ mysql -e "EXPLAIN SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: orders
type: range
possible_keys: idx_created_at
key: idx_created_at
rows: 98234123
Extra: Using where; Using temporary; Using filesort

Що це означає: Навіть з індексом ви скануєте ~98M рядків і використовуєте temporary/filesort. Це не OLTP‑запит; це OLAP‑задача.

Рішення: Перенесіть його. Якщо деякі агрегати мають залишитись в MySQL, використовуйте summary tables, що оновлюються інкрементально, а не ad hoc GROUP BY по сирим фактам.

Завдання 7: Підтвердити базове здоров’я ClickHouse (злиття або диск — проблема?)

cr0x@server:~$ clickhouse-client -q "SELECT hostName(), uptime()"
ch01
345678

Що це означає: Ви можете підключитися та сервер живий достатньо довго, щоб бути корисним.

Рішення: Переходьте до глибшої перевірки: частини/злиття, навантаження запитів і диск.

Завдання 8: Перевірити активні запити ClickHouse і їхнє використання ресурсів

cr0x@server:~$ clickhouse-client -q "SELECT user, query_id, elapsed, read_rows, formatReadableSize(memory_usage) AS mem, left(query, 80) AS q FROM system.processes ORDER BY memory_usage DESC LIMIT 5 FORMAT TabSeparated"
bi_user	0f2a...	12.4	184001234	6.31 GiB	SELECT customer_id, sum(total) FROM orders_events WHERE event_date >= toDate('2025-12-01')
etl	9b10...	3.1	0	512.00 MiB	INSERT INTO orders_events FORMAT JSONEachRow

Що це означає: BI споживає пам’ять. Це нормально, якщо це бюджетовано. Це проблема, якщо це позбавляє ресурси злиттів або викликає OOM.

Рішення: Встановіть max_memory_usage на користувача, max_threads і, можливо, max_concurrent_queries. Тримайте ETL надійним.

Завдання 9: Перевірити backlog злиттів ClickHouse (частини ростуть як бур’яни)

cr0x@server:~$ clickhouse-client -q "SELECT database, table, sum(parts) AS parts, formatReadableSize(sum(bytes_on_disk)) AS disk FROM system.parts WHERE active GROUP BY database, table ORDER BY sum(parts) DESC LIMIT 10 FORMAT TabSeparated"
analytics	orders_events	1842	1.27 TiB
analytics	sessions	936	640.12 GiB

Що це означає: Тисячі частин можуть означати сильну фрагментацію вставок або що злиття відстають. Продуктивність запитів погіршиться, і метадані стануть важчими.

Рішення: Налаштуйте батчування вставок, акуратно підберіть параметри злиття і перегляньте стратегію партиціонування. Якщо кількість частин продовжує рости, розглядайте це як повільно розгортаючийся інцидент.

Завдання 10: Перевірити відсікання за партицією (якщо сканує все — ви неправильно змоделювали)

cr0x@server:~$ clickhouse-client -q "EXPLAIN indexes=1 SELECT customer_id, sum(total) FROM analytics.orders_events WHERE event_date BETWEEN toDate('2025-12-01') AND toDate('2025-12-30') GROUP BY customer_id"
Expression ((Projection + Before ORDER BY))
  Aggregating
    Filter (WHERE)
      ReadFromMergeTree (analytics.orders_events)
        Indexes:
          MinMax
            Keys: event_date
            Condition: (event_date in [2025-12-01, 2025-12-30])
            Parts: 30/365
            Granules: 8123/104220

Що це означає: Воно читає 30/365 частин завдяки фільтру за датою. Ось як виглядає «працює як задумано».

Рішення: Якщо кількість прочитаних частин близька до загальної, змініть партиціювання і/або вимагайте тимчасових фільтрів у панелях.

Завдання 11: Моніторити використання диска ClickHouse і передбачити проблеми з ємністю

cr0x@server:~$ df -h /var/lib/clickhouse
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme1n1    3.5T  3.1T  330G  91% /var/lib/clickhouse

Що це означає: 91% заповнення. Ви в одному бекфілі від поганого дня, а злиттям потрібен вільний простір.

Рішення: Зупиніть несуттєві бекфіли, розширте зберігання, застосуйте TTL і оптимізуйте модель даних. ClickHouse під тиском диска стає непередбачувано повільним і ризикованим.

Завдання 12: Перевірити лаг CDC на стороні споживача (чи аналітика застаріла?)

cr0x@server:~$ clickhouse-client -q "SELECT max(ingested_at) AS last_ingest, now() AS now, dateDiff('second', max(ingested_at), now()) AS lag_s FROM analytics.orders_events"
2025-12-30 19:03:12	2025-12-30 19:03:29	17

Що це означає: ~17 секунд лаг. Це здорово для «майже реальної» аналітики.

Рішення: Якщо лаг росте, призупиніть важкі запити, перевірте пропускну здатність пайплайну і вирішіть, чи деградувати панелі, чи ризикнути OLTP.

Завдання 13: Перевірити формат binlog MySQL для коректності CDC

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'binlog_format';"
Variable_name	Value
binlog_format	ROW

Що це означає: ROW-формат зазвичай потрібен CDC-інструментам для коректності. STATEMENT може бути неоднозначним для недетермінованих запитів.

Рішення: Якщо ви не на ROW, заплануйте вікно змін. Коректність CDC — не те, на що можна «сподіватися».

Завдання 14: Підтвердити, що MySQL має адекватний лог повільних запитів (щоб довести причинність)

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
Variable_name	Value
slow_query_log	ON
slow_query_log_file	/var/log/mysql/mysql-slow.log
Variable_name	Value
long_query_time	0.500000

Що це означає: Ви будете фіксувати запити повільніші за 500 мс. Це агресивно, але корисно під час шумного періоду.

Рішення: Під час інцидентів тимчасово знизьте long_query_time і робіть вибірки. Після — встановіть стабільний поріг і використовуйте digest‑зведення.

Завдання 15: Перевірити ліміти користувачів ClickHouse (щоб запобігти «паралелізму» BI)

cr0x@server:~$ clickhouse-client -q "SHOW CREATE USER bi_user"
CREATE USER bi_user IDENTIFIED WITH sha256_password SETTINGS max_memory_usage = 4000000000, max_threads = 8, max_execution_time = 60, max_concurrent_queries = 5

Що це означає: BI відгороджений: 4GB пам’яті, 8 потоків, 60s виконання, 5 одночасних запитів. Це різниця між панеллю і стрес‑тестом.

Рішення: Якщо ви не можете встановити ліміти через «потреби бізнесу», ви не керуєте аналітикою — ви граєте в рулетку.

Покроковий план швидкої діагностики

Порядок, що знаходить вузьке місце швидко, без перетворення інциденту на філософську дискусію.

Перше: чи MySQL перевантажений читаннями, записами, блокуваннями чи I/O?

  1. Top query digests (performance_schema digests або slow log): виявіть сімейства запитів, що їдять час.
  2. Стан потоків (SHOW PROCESSLIST): «Sending data» вказує на скан/агрегацію; «Locked» — на контенцію; «Waiting for table metadata lock» — на DDL‑колізію.
  3. Дискове I/O (iostat): якщо iowait високий і %util диска запеклий, зупиніть скани перш ніж щось інше налаштовувати.

Друге: чи «рішення» (репліка) насправді не погіршує ситуацію?

  1. Лаг реплікації (SHOW SLAVE STATUS): якщо лаг — хвилини, користувачі аналітики приймають рішення на застарілих даних і звинувачують вас.
  2. Контенція ресурсів на репліці: важкі запити можуть позбавляти SQL‑потік ресурсів і ще більше збільшувати лаг.

Третє: якщо ClickHouse існує, чи він здоровий і під управлінням?

  1. system.processes: виявіть запити BI‑втікачі й хелпери пам’яті.
  2. Частини й злиття (system.parts): занадто багато частин — проблема форми інжекції або backlog злиттів.
  3. Дисковий запас (df): злиттям і TTL потрібен простір; 90% заповнення — операційний борг з відсотками.

Четверте: чи скарга насправді про свіжість даних?

  1. Лаг CDC (max ingested_at): кількісно оцініть застарілість.
  2. Повідомте про запасний план: якщо свіжість деградує, деградуйте панелі — не оформлення замовлення.

Три корпоративні міні‑історії з фронту

Інцидент через хибне припущення: «Репліки для звітності»

Середня підпискова компанія мала первинний кластер MySQL і дві репліки. Їхній BI‑інструмент дивився на репліку, бо «читання не впливає на записи». Ця фраза спричинила більше інцидентів, ніж кава запобігла.

Під кінець місяця фінанси запустили ряд когортних і доходних звітів. Диск репліки досяг насичення: важкі скани плюс тимчасові таблиці. Лаг реплікації виріс від секунд до десятків хвилин. Спочатку ніхто не помітив, бо трафік додатка був у нормі; первинний сервер прямо не постраждав.

Потім хтось припустив: «Якщо первинний впаде, ми зможемо переключитись на репліку». Саме коли лаг був максимальний, первинний мав unrelated проблему хоста і став нездоровим. Автоматика спробувала просунути «кращу» репліку — але «краща» була на 20 хвилин позаду.

Вони не втратили всю базу. Вони втратили достатньо недавніх транзакцій, щоб створити кошмар для підтримки клієнтів: платежі, що «пішли» зовні, але не існували внутрішньо, і сесії, що не відповідали біллінгу. Відновлення стало ретельним поєднанням дослідження binlog і зіставлення з платіжним провайдером.

Виправлення не було героїчним. Вони розділили обов’язки: виділена репліка для failover з жорсткою блокадою запитів, а аналітика перейшла в ClickHouse через CDC. Звітність стала швидкою, а failover — надійним, бо репліка більше не була мішенню для аналітики.

Оптимізація, що обернулася проти: «Додамо індекс»

Команда електронної торгівлі мала повільний звітний запит по orders: фільтр за часом і group-by. Хтось додав індекс на created_at і складений індекс на (created_at, customer_id). Запит став швидшим у ізоляції, тому вони випустили це і святкували.

Через два тижні латентність записів почала рости. Вставки в orders уповільнились, а фонова швидкість флашів підскочила. Нові індекси збільшили write amplification — кожна вставка тепер підтримувала більше B‑дерев. На піку трафіку вони платили індексну ціну за кожну транзакцію, щоб зробити кілька звітів дешевшими.

Потім BI додав нову панель, що запускала той самий запит кожну хвилину. Запит став швидшим, тож конкуренція зросла (люди люблять оновлювати, коли оновлення швидке). Система поміняла один повільний запит на багато середньошвидких і все одно опинилася I/O‑зв’язаною.

Рішенням було прибрати індексний надлишок, зберегти OLTP легким і побудувати таблицю rollup у ClickHouse, що оновлюється безперервно. Панелі зверталися до ClickHouse. Транзакції лишилися плавними. Команда важко засвоїла: індекс — не «безкоштовне прискорення», це рахунок за запис, який ви платите вічно.

Сумна, але правильна практика, що врятувала день: квоти і поетапні бекфіли

B2B SaaS компанія використовувала ClickHouse для аналітики з жорсткими профілями користувачів. BI‑користувачі мали max_execution_time, max_memory_usage і ліміти конкуренції. ETL мав інші ліміти і працював у контрольованій черзі. Ніхто не любив ті обмеження. Усі від них виграли.

Одного дня аналітик спробував запустити широкий запит за два роки сирих подій без фільтру за датою. ClickHouse почав сканувати, досягнув ліміту часу виконання і вбив запит. Аналітик поскаржився. Черговий не отримав сигналу. Це хороший обмін.

Пізніше того місяця команда даних мала бекфіл через зміну схеми в CDC. Вони робили це поетапно: по одному дню, перевіряючи кількість частин, диск‑хедрум і лаг після кожного шматка. Повільно, обережно, вимірно. Бекфіл закінчився без загрози для продуктивних панелей.

Нудна практика не була якимось складним алгоритмом. Це було управління і операційна дисципліна: ліміти, черги і інкрементні бекфіли. Це їх врятувало, бо система поводилася передбачувано, коли люди поводилися непередбачувано.

Жарт №2: Єдина річ більш постійна за тимчасову панель — це інцидентний канал, який вона створює.

Типові помилки: симптом → корінна причина → виправлення

  • Симптом: p95 латентності MySQL зростає під час «годин звітності»
    Корінна причина: довгі скани і GROUP BY конкурують з OLTP за buffer pool і I/O
    Виправлення: Перенесіть звітність в ClickHouse; введіть політику; додайте кураторські rollup‑таблиці; заблокуйте доступ BI до MySQL.
  • Симптом: Лаг репліки зростає, коли аналітики запускають звіти
    Корінна причина: Репліка I/O і CPU насичені; SQL‑потік не встигає застосувати binlog швидко
    Виправлення: Заберіть аналітичний доступ з реплік для failover; використовуйте CDC у ClickHouse; обмежуйте конкуренцію запитів.
  • Симптом: Запити ClickHouse повільнішають з часом без зміни обсягу даних
    Корінна причина: Вибух кількості частин; злиття відстають через фрагментовані вставки або тиск на диск
    Виправлення: Батчуйте вставки; тонко налаштуйте параметри злиття; моніторте частини; забезпечте диск‑запас; розгляньте переразбиття.
  • Симптом: Панелі «швидкі інколи» і таймаутяться випадково в ClickHouse
    Корінна причина: Незв’язаність BI‑конкуренції; тиск пам’яті; «шумні сусіди»
    Виправлення: Встановіть ліміти на користувача (пам’ять, потоки, час виконання, одночасні запити); створіть попередньо агреговані таблиці; додайте маршрутизацію запитів.
  • Симптом: Дані аналітики мають дублікати або «неправильний останній стан»
    Корінна причина: CDC застосовано як append-only без дедуплікації/версіонування; оновлення/видалення не змоделювані правильно
    Виправлення: Використовуйте стовпці версій і ReplacingMergeTree де потрібно; зберігайте події і виводьте поточний стан через materialized views.
  • Симптом: Диск ClickHouse постійно росте до аварійного стану
    Корінна причина: Немає TTL; зберігання сирого вічно; важкі бекфіли; відсутні обмеження ємності
    Виправлення: Застосуйте TTL для холодних даних; даунсемплуйте; архівуйте; стискайте; встановіть квоти і процедури бекфілу.
  • Симптом: «Ми перейшли на ClickHouse, але MySQL все ще повільний»
    Корінна причина: CDC‑пайплайн все ще читає MySQL важко (повні екстракти, часті снапшоти) або додаток все ще запускає звіти на MySQL
    Виправлення: Використовуйте binlog‑базований CDC; перегляньте джерела запитів MySQL; закрийте акаунти для звітності; валідируйте за допомогою digest‑даних.
  • Симптом: Свіжість ClickHouse відстає в піки
    Корінна причина: Вузьке місце інжесту (пропускна здатність пайплайну), злиття або тиск на диск; іноді занадто багато маленьких вставок
    Виправлення: Батчуйте вставки; масштабируйте інжест; моніторьте лаг; тимчасово зменшіть конкуренцію BI; пріоритезуйте ресурси ETL.

Контрольні списки / покроковий план

Покроково: план впровадження чистого розділення

  1. Оголосіть межу: продукційний MySQL — тільки OLTP. Запишіть це. Забезпечте через акаунти й мережеву політику.
  2. Інвентаризуйте аналітичні запити: використайте digest‑таблиці MySQL і зведення slow log для переліку топ‑20 сімейств запитів.
  3. Оберіть метод інжесту: CDC для майже реального часу; пакет для щоденних/годинних оновлень; уникайте подвійного запису.
  4. Опишіть аналітичні таблиці в ClickHouse: почніть з таблиць подій, партиціювання за часом і ORDER BY ключів, узгоджених з фільтрами.
  5. Побудуйте «gold» набори даних: materialized views або rollup‑таблиці для панелей; зберігайте сирі дані для глибокого аналізу.
  6. Встановіть управління з першого дня: профілі користувачів, квоти, max_execution_time, max_memory_usage, max_concurrent_queries.
  7. Вимірюйте свіжість: відслідковуйте лаг інжесту і публікуйте SLO зацікавленим сторонам. Люди терплять застарілість, коли вона явна.
  8. Перенесіть панелі: мігруйте найвпливовіші панелі першими (ті, що непрямо викликають виклики чергових).
  9. Заблокуйте старий шлях: приберіть BI‑креденшіали з MySQL; якщо потрібно — фаєрвол; запобігайте регресії.
  10. Бекфіл безпечно: інкрементально, вимірювано, з перевіркою дискового запасу; без фантазій «запустимо вночі».
  11. Навантажувальне тестування аналітики: симулюйте конкуренцію панелей. ClickHouse охоче прийме ваш оптимізм, а потім покарає його.
  12. Операціоналізуйте: алерти за кількістю частин ClickHouse, використанням диска, помилками запитів, лагом інжесту; а також за латентністю/IO MySQL.

Чеклист релізу: переміщення однієї панелі з MySQL в ClickHouse

  • Чи включає запит панелі фільтр за часом, що відповідає партиціюванню?
  • Чи є таблиця rollup/materialized view, щоб уникнути повторних сканів сирих подій?
  • Чи обмежено користувача ClickHouse (пам’ять, потоки, час виконання, конкуренція)?
  • Чи метрика лагу CDC видима для користувачів панелі?
  • Чи старий MySQL‑запит заблоковано або принаймні вилучено з додатка/BI?
  • Чи ви перевірили результати для відомого вікна часу (спот‑чек сум і кількостей)?

Операційний чеклист: щотижнева гігієна, що запобігає повільним аваріям

  • Перегляньте активні частини ClickHouse по таблицях; дослідіть швидкий ріст.
  • Перегляньте дисковий запас ClickHouse; тримайте достатньо вільного місця для злиттів і бекфілів.
  • Перегляньте топ‑BI‑запити за read_rows і використанням пам’яті; оптимізуйте або попередньо агрегуйте.
  • Перегляньте топ digests MySQL, щоб переконатися, що аналітика не підкралась назад.
  • Перевіряйте шляхи відновлення: бекапи MySQL, очікування відновлення метаданих і даних ClickHouse.

Питання й відповіді

1) Хіба не можна просто вертикально масштабувати MySQL і вирішити проблему?

Можна, і ви отримаєте тимчасове полегшення. Режим відмови повернеться, коли з’явиться наступна панель або когортний запит. Проблема — у невідповідності навантаження, а не лише у потужності.

2) Якщо у мене вже є репліки MySQL — чи слід вказати BI на них?

Тільки якщо ви комфортно ставитеся до лагу і не використовуєте ті репліки для failover. Навіть тоді обмежте конкуренцію і розглядайте це як тимчасовий міст, а не кінцевий стан.

3) Чи достатньо ClickHouse «реального часу» для операційних панелей?

Часто так, з CDC. Вимірюйте лаг інжесту явно і проектуйте панелі з урахуванням невеликої затримки. Якщо вам потрібна субсекундна транзакційна істина — це зона MySQL.

4) Як обробляти оновлення і видалення з MySQL у ClickHouse?

Краще моделювання подій (append changes). Якщо потрібен «поточний стан», використовуйте версійні рядки з движками на кшталт ReplacingMergeTree і проектуйте запити/materialized views відповідно.

5) Чи замінить ClickHouse мій data warehouse?

Іноді. Для багатьох компаній він стає основним аналітичним сховищем. Але якщо потрібні важкі трансформації, сильне управління або крос‑системне моделювання, ви можете і далі тримати шар сховища даних. Не намагайтеся змусити релігійне звернення.

6) Який найшвидший виграш, якщо ми зараз у вогні?

Припиніть виконувати аналітику на MySQL негайно: вбийте найгірші запити, приберіть доступ BI і відправте панель в ClickHouse або кеш‑роллап. Потім зробіть це правильно.

7) Який найбільший операційний сюрприз ClickHouse для команд MySQL?

Злиття й частини. Люди з row‑store очікують «я вставив — і все». ClickHouse продовжує працювати в тлі, і цю роботу потрібно моніторити.

8) Як запобігти аналітикам від написання дорогих запитів у ClickHouse?

Використовуйте профілі користувачів з квотами і таймаутами, надавайте кураторські «gold» таблиці і навчіть людей, що відсутність фільтрів за часом — не «дослідження», а підпал.

9) Чи вирішують materialized views усі проблеми?

Ні. Вони чудові для стабільних rollup‑ів і поширених агрегатів. Але можуть додавати складність і витрати на зберігання. Використовуйте їх там, де вони обґрунтовано зменшують повторну роботу.

10) Що робити, якщо мої аналітичні запити вимагають складних джойнів через багато таблиць?

Денормалізуйте для типових шляхів, попередньо обчисліть виміри і обмежуйте джойни. ClickHouse може виконувати джойни, але найкращі продакшн‑системи аналітики уникають їх повторного виконання під час запиту.

Висновок: практичні наступні кроки

Якщо ви зробите одну дію цього тижня — зробіть ось це: видаліть навантаження аналітики з MySQL. Не благаючи користувачів «бути обережними», а надаючи краще місце для досліджень.

  1. Заблокуйте MySQL: окремі акаунти, блокування BI‑мереж і забезпечення, що продукційний MySQL обслуговує користувачів у першу чергу.
  2. Встановіть управління ClickHouse: ліміти, квоти і кураторські набори даних перед тим, як запрошувати всю компанію.
  3. Перенесіть топ‑5 найгірших запитів: реплікуйте потрібні дані через CDC або пакетно, потім побудуйте rollup‑и, щоб панелі залишалися дешевими.
  4. Операціоналізуйте свіжість: публікуйте лаг інжесту і ставте його як продуктове вимогу. Краще чесно відставати на 60 секунд, ніж бути непомітно неправим.
  5. Практикуйте бекфіли: поетапні, вимірювані, зворотні. Ваш майбутній я оцінить стриманість сьогоднішнього.

Чистий розділ не гламурний. Це просто різниця між базою, що обслуговує клієнтів, і базою, що влаштовує щоденний аналітичний поєдинок. Оберіть спокійніше життя.

← Попередня
SLI/CrossFire: чому мульти‑GPU були мрією — і чому вона померла
Наступна →
MySQL чи PostgreSQL на VPS з 4 ГБ ОЗП: що налаштувати насамперед для вебсайтів

Залишити коментар