MySQL vs TiDB: сумісність MySQL проти операційної складності — за що ви підписуєтесь

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

Ви не обираєте базу даних у вакуумі. Ви обираєте її о 2:13 ранку, коли дзвонить on-call телефон, черга росте,
і хтось питає, чому «база даних» повільна, ніби це одна коробка, яку можна перезавантажити.

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

Рішення, яке справді має значення

Більшість дискусій «MySQL проти TiDB» прикидають, що питання стосується фіч. Це не так. Питання в тому,
хто виконує роботу складності.

  • MySQL піднімає складність вгору у ваш застосунок і команду: стратегія шардінгу, запити через шарди,
    подвійні записи, генерація id, зміни схеми з мінімальним простоєм, керування топологією реплікації,
    логіка розподілу читань/записів і радість від усвідомлення, що ваш «простий» JOIN тепер — розподілений JOIN через шарди.
  • TiDB опускає складність вниз у платформу бази даних: правила розміщення, реплікація через Raft,
    розподіл/злиття Regions, планування PD, координація розподілених транзакцій, hotspot-и та оновлення багатьох компонентів.
    Ваш код застосунку стає простішим. Ваші runbook-и on-call товстіші.

Ось практичний евристичний підхід, який я використовую:

  • Якщо ви можете масштабувати MySQL репліками, кешуванням, ретельною індексацією та інколи вертикальним апгрейдом — і ваш
    найбільший біль — це операційна дисципліна — залишайтесь на MySQL. Він нудний, перевірений, і ваша команда вже знає, як він ламається.
  • Якщо ви досягли моменту, коли шардінг неминучий (або вже є хаос), і вам потрібна сильна консистентність над великою
    базою з еластичним горизонтальним масштабуванням, TiDB — розумний вибір — але лише якщо ви можете знайти і підтримувати команду, яка запустить його як платформу, а не як «ще один вузол БД з друзями».

І так, TiDB говорить MySQL-протоколом. Це початок сумісності, а не фінішна пряма.

Факти й історичний контекст, що змінюють інтуїцію

Кілька якорів, щоб тримати розум чесним. Це не дрібниці; вони пояснюють, чому системи поводяться так, як поводяться.

  1. Реплікація MySQL починалася як асинхронна реплікація на рівні операторів (statement-based). Та спадщина пояснює, чому
    «відставання репліки» і хаки «читати свої ж записи» стали загальноприйнятою практикою для команд MySQL.
  2. InnoDB став рушієм за замовчуванням у MySQL 5.5. До того поширеним був MyISAM, і та епоха закарбувала в колективній пам’яті інтернету думку «MySQL швидкий, але не безпечний».
  3. Percona і MariaDB з’явилися тому, що оператори хотіли кращої спостережуваності та операційних важелів.
    Історія екосистеми MySQL — це фактично «SRE вимагали інструменти й отримали індустрію».
  4. Google Spanner (2012) встановив очікування: «SQL + горизонтальне масштабування + сильна консистентність».
    TiDB — у родині систем, які зробили цю мрію доступнішою без потреби в атомних годинах.
  5. Слой зберігання TiDB (TiKV) побудований на RocksDB та реплікації Raft. Це означає, що продуктивність і режими відмов більше схожі на розподілене KV-сховище, ніж на одновузловий B-tree движок.
  6. TiDB розбиває дані на Regions (діапазони) і планує їх через PD. Це розбалансування Regions — і суперсила, і джерело моментів «чому у мене підскочила латентність?».
  7. GTID і semi-sync реплікація в MySQL були реакцією на реальні болі. Вони підвищили надійність операцій, але не перетворили MySQL на розподілену SQL-базу; вони лише зменшили радіус помилок людини.
  8. Інструменти для онлайн-змін схеми (gh-ost, pt-online-schema-change) стали мейнстрімом через історично руйнівні DDL у MySQL. У TiDB багато змін схеми онлайн, але ви все одно платите ресурсами кластера і побічними ефектами.
  9. «Сумісність з MySQL» — рухома ціль. Сам MySQL має кілька великих версій, форків від вендорів і відмінності в поведінці. Сумісність — не бінарна, а матрична.

MySQL у продакшені: що ламається, що масштабується, які міфи

За що MySQL справді хороший

MySQL — сильний вибір, коли навантаження підходить під одного primary (або невелику кількість primary) з репліками.
Оперувати ним просто, якщо ви тримаєте все просто: один записувач, багато читачів; передбачувані запити; стабільна схема;
консервативні конфіги; і команда, яка поважає базу як спільний ресурс, а не як нескіненний автомат.

Для багатьох компаній «стеля» MySQL значно вища, ніж вони думають — бо реальним вузьким місцем були відсутні індекси,
гігантські транзакції, балакучі ORM-и або робоча черга, яка робить повні скани таблиць наче платять за рядок.

За що з вас беруть плату у MySQL

Історія масштабування MySQL для write-heavy навантажень проста: масштабувати вгору, напружено налаштовувати, а потім шардувати.
Шардінг працює, але це зобов’язання. Ви перепишете припущення:

  • Крос-шардні транзакції стають координацією на рівні застосунку або eventual consistency.
  • Глобальна унікальність потребує id, що не будуть конфліктувати між шардами.
  • Звітні запити стають ETL-джобами або аналітичними сховищами.
  • Операційна робота переходить у управління багатьма дрібнішими базами, бекапами та міграціями схем.

Режими відмов MySQL добре відомі: відставання реплікації, contention блокувань, тиск на buffer pool, зависання дискового IO
і «один запит зруйнував усім день». Плюс у тому, що більшість інженерів бачили це раніше. Мінус у тому, що команди часто нормалізують ці явища і перестають ставити під сумнів архітектурні рішення.

Одна жартівлива аналогія, як і обіцяно: MySQL як кухонний ніж — відмінний, поки хтось не вирішить ним ще й викручувати гвинти.

TiDB у продакшені: що ви отримуєте, що успадковуєте

За що TiDB справді хороший

Пропозиція TiDB приваблива, бо вона вирішує класичний біль MySQL: масштабування без ручного шардінгу, зберігаючи SQL і транзакції.
На практиці виграші реальні, коли:

  • Ваш набір даних і write throughput виростають понад можливості одного primary і ви хочете горизонтальне масштабування.
  • Вам потрібна сильна консистентність без побудови власної логіки крос-шардних транзакцій.
  • Ви готові терпіти операційні витрати запуску розподіленої системи.
  • Ви хочете збільшувати потужність додаванням вузлів, а не планувати «великий коробковий» апгрейд.

За що з вас бере плату TiDB

TiDB — це не «MySQL, але швидше». Це розподілена база з MySQL-совісним інтерфейсом. Ви тепер запускаєте:

  • TiDB — безстанський SQL-слой (scale-out).
  • TiKV — stateful шар зберігання (реплікований Raft, region-based).
  • PD — placement driver (мозок кластера для планування і метаданих).
  • Часто TiFlash для прискорення аналітики та HTAP-навантажень.

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

Ви також погоджуєтесь на інший тип налаштувань продуктивності: менше «розміру buffer pool і ностальгії за query cache», більше «hotspot-ів регіонів, coprocessor завдань і затримок raftstore».

Сумісність з MySQL: дрібний шрифт, який кусає

Сумісність протоколу — не те саме, що сумісність поведінки

Багато міграцій починаються з «наш застосунок говорить MySQL, отже TiDB має бути drop-in». Це правда доти, доки ваш застосунок
не залежить від крайових випадків поведінки. Більшість застосунків так робить, вони просто ще цього не знають.

Прогалини сумісності проявляються в чотирьох практичних місцях:

  • SQL граматика та функції: деякі функції відрізняються, деяких бракує, і деякі поводяться тонко відмінно в краєвих випадках.
  • Транзакції та семантика блокувань: розподілені транзакції не поводяться точно як одновузлові блокування під навантаженням.
  • DDL та метадані: «онлайн DDL» чудовий, поки він не створює фонового навантаження, на яке ви не заклали ресурси.
  • Операційні припущення: у MySQL інколи можна «просто перезапустити mysqld», щоб очистити завислий стан.
    У TiDB перезапуск компонента може спровокувати переселення або вибори лідера — це нормально, але не безкоштовно.

Консистентність: простота MySQL проти координації TiDB

MySQL з одним primary дає ясну ментальну модель: primary приймає рішення. Репліки слідують. Сильна консистентність локальна для primary;
читання з реплік — «можливо застаріле», і всі звикають до цього.

TiDB прагне сильної консистентності в розподіленому кластері. Це означає:

  • Записи мають досягати кворуму реплік для Raft-груп (Regions).
  • Транзакції можуть зачіпати кілька Regions, потенційно на різних вузлах.
  • Мережеві стрибки та повільні вузли зберігання стають затримкою запиту в нових і креативних формах.

Сюрпризи з продуктивністю: «податок розподілу»

Деякі запити в TiDB стають швидшими, бо можна масштабувати обчислення і зберігання. Інші стають повільнішими, бо запит, який
був локальним зчитуванням індексу в MySQL, стає розподіленою операцією, що зачіпає кілька Regions.

Тут потрібно бути безжально чесним щодо форми навантаження:

  • Однорядкові пошуки за первинним ключем можуть бути відмінними, але можуть утворюватися hotspot-и, якщо багато клієнтів
    б’ють по тому самому діапазону ключів.
  • Великі range-скани можуть бути прийнятні, якщо їх паралелізувати, але вони також можуть викликати велике навантаження coprocessor.
  • JOIN-и — тут треба бути уважним. Планування розподілених JOIN-ів і якість статистики мають значно більшу вагу.

Операційна складність: що TiDB додає до вашого пейджера

Ви оперуєте платформою бази даних

У MySQL одиницею операції є сервер (або кластер серверів з одним primary). У TiDB одиницею операції є система. Ви будете керувати:

  • покроковими оновленнями (rolling upgrades) через кілька компонентів
  • керуванням ємністю по обчисленню, зберіганню та мережі
  • доменами відмов (racks/AZs), правилами розміщення і кількістю реплік
  • виявленням і пом’якшенням hotspot-ів
  • бекап/відновленням, що поважає розподілену консистентність
  • змінами схеми, які спричиняють фонову реорганізацію даних

Спостережуваність — не опція

У MySQL можна пройти далеко зі slow query log, performance_schema і базовими метриками хосту. У TiDB потрібна спостережуваність на рівні кластера: стан PD, латентність TiKV, баланс Regions, тривалість raft apply і насичення SQL-слою.

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

Одна цитата (перефразована ідея)

Перефразована ідея: Якщо ви не вимірюєте — ви не можете надійно покращувати. — приписується W. Edwards Deming

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

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

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

Завдання 1: Перевірити відставання реплікації MySQL (чи надійні читання?)

cr0x@server:~$ mysql -h mysql-replica-01 -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: 37

Значення: репліка здорова, але відстає ~37 с. Будь-яке «читання після запису» із цієї репліки може бути застарілим.

Рішення: направляйте критичні читання на primary (або на репліку з малим відставанням), або виправляйте лаг перед використанням реплік для шляхів, чутливих до коректності.

Завдання 2: Знайти верхні події wait у MySQL (lock, IO чи CPU?)

cr0x@server:~$ mysql -e "SELECT EVENT_NAME, SUM_TIMER_WAIT/1e12 AS seconds_waited FROM performance_schema.events_waits_summary_global_by_event_name ORDER BY SUM_TIMER_WAIT DESC LIMIT 5;"
EVENT_NAME	seconds_waited
wait/io/file/innodb/innodb_data_file	1843.22
wait/synch/mutex/innodb/buf_pool_mutex	912.45
wait/lock/table/sql/handler	211.10
wait/io/file/sql/binlog	199.08
wait/synch/cond/sql/MYSQL_BIN_LOG::COND_done	133.57

Значення: інтенсивний IO InnoDB і contention буфера. Ви не CPU-bound; ви чекаєте.

Рішення: перевірте латентність зберігання, розмір buffer pool і гарячі таблиці. Якщо це хронічно на вашому масштабі — TiDB може допомогти, якщо навантаження підходить під шардинг-подібну модель.

Завдання 3: Виявити MySQL-запити з найгіршою 95-ю перцентиллю латентності

cr0x@server:~$ pt-query-digest /var/log/mysql/mysql-slow.log --limit 3
#  1.2s user time, 60ms system time, 22.18M rss, 221.20M vsz
# Rank Query ID           Response time Calls R/Call V/M   Item
# ==== ================== ============= ===== ====== ===== ====
#    1 0x8D7A...          521.7340  38.4%  120 4.3478  0.02 SELECT orders
#    2 0x44B1...          301.0092  22.1%  560 0.5375  0.01 UPDATE inventory
#    3 0xA1E3...          198.5521  14.6%   45 4.4123  0.00 SELECT users

Значення: два повільні SELECT-и домінують у часі відповіді; UPDATE також дорогий і частий.

Рішення: спочатку налаштуйте схему та запити. Міграція повільного запиту в TiDB лише зробить його повільним на більшій кількості серверів.

Завдання 4: Перевірити розмір таблиці/індексу MySQL (чи IO-застій через блоут?)

cr0x@server:~$ mysql -e "SELECT table_schema, table_name, ROUND((data_length+index_length)/1024/1024/1024,2) AS gb FROM information_schema.tables WHERE table_schema='app' ORDER BY (data_length+index_length) DESC LIMIT 5;"
table_schema	table_name	gb
app	events	412.77
app	orders	188.34
app	order_items	141.02
app	users	62.11
app	inventory	51.89

Значення: у вас кілька дуже великих таблиць. Вони домінують у IO і створюють тиск на buffer pool.

Рішення: розгляньте партиціювання, архівацію або вторинне сховище для холодних даних. Якщо доступ по ширині і write-heavy — горизонтальне зберігання TiDB може допомогти.

Завдання 5: Перевірити стан кластера TiDB (чи платформа стабільна?)

cr0x@server:~$ tiup cluster display tidb-prod
Cluster type:       tidb
Cluster name:       tidb-prod
Cluster version:    v7.5.1
ID                   Role          Host            Ports        OS/Arch       Status  Data Dir
--                   ----          ----            -----        -------       ------  --------
tidb-01              tidb          10.0.2.11        4000/10080   linux/x86_64   Up      /tidb-data/tidb-4000
pd-01                pd            10.0.2.21        2379/2380    linux/x86_64   Up      /tidb-data/pd-2379
tikv-01              tikv          10.0.3.31        20160/20180  linux/x86_64   Up      /tidb-data/tikv-20160
tikv-02              tikv          10.0.3.32        20160/20180  linux/x86_64   Up      /tidb-data/tikv-20160
tikv-03              tikv          10.0.3.33        20160/20180  linux/x86_64   Up      /tidb-data/tikv-20160

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

Рішення: якщо будь-який TiKV або PD впав — припиніть налаштування SQL і спочатку виправте стан кластера.

Завдання 6: Перевірити стан PD і лідера (чи мозок не збитий?)

cr0x@server:~$ tiup ctl:v7.5.1 pd -u http://10.0.2.21:2379 member
{
  "members": [
    {
      "name": "pd-01",
      "member_id": 1234567890,
      "client_urls": ["http://10.0.2.21:2379"],
      "peer_urls": ["http://10.0.2.21:2380"]
    }
  ],
  "leader": {
    "name": "pd-01",
    "member_id": 1234567890
  }
}

Значення: PD тут одновузловий; він — лідер. У продакшені зазвичай хочуть кілька PD-нодів для HA.

Рішення: якщо PD не HA — виправте архітектуру перед тим, як ставити компанію на кластер.

Завдання 7: Перевірити ємність і баланс TiKV (ви на межі?)

cr0x@server:~$ tiup ctl:v7.5.1 pd -u http://10.0.2.21:2379 store
{
  "count": 3,
  "stores": [
    {"store": {"id": 1, "address": "10.0.3.31:20160"}, "status": {"capacity": "3.6TiB", "available": "0.4TiB"}},
    {"store": {"id": 2, "address": "10.0.3.32:20160"}, "status": {"capacity": "3.6TiB", "available": "0.3TiB"}},
    {"store": {"id": 3, "address": "10.0.3.33:20160"}, "status": {"capacity": "3.6TiB", "available": "0.2TiB"}}
  ]
}

Значення: усі сторі працюють майже без вільного місця. Компаґація, снапшоти та ребалансинг стають ризикованими при майже порожніх дисках.

Рішення: додайте ємності перед оптимізацією продуктивності. «Майже повний» у RocksDB — як розкладати власний простій.

Завдання 8: Швидко знайти повільні запити в TiDB (чи маємо регрес плану?)

cr0x@server:~$ mysql -h tidb-01 -P 4000 -e "SELECT time, query_time, digest, left(query,120) AS sample FROM information_schema.cluster_slow_query ORDER BY query_time DESC LIMIT 3;"
time	query_time	digest	sample
2025-12-30T09:40:11Z	8.214	9f2d...	SELECT * FROM orders WHERE user_id=... ORDER BY created_at DESC LIMIT 50
2025-12-30T09:41:02Z	6.882	2a11...	SELECT COUNT(*) FROM events WHERE created_at BETWEEN ... AND ...
2025-12-30T09:41:33Z	5.477	0bd3...	UPDATE inventory SET qty=qty-1 WHERE sku=...

Значення: у вас повільні запити, схожі на MySQL, але тепер треба дослідити розподілене виконання (cop tasks, зачеплені regions тощо).

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

Завдання 9: Explain запиту в TiDB і пошук розподіленого болю (індекс? coprocessor? scatter?)

cr0x@server:~$ mysql -h tidb-01 -P 4000 -e "EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id=42 ORDER BY created_at DESC LIMIT 50\G"
*************************** 1. row ***************************
id: TopN_10
actRows: 50
task: root
execution info: time:1.2s, loops:2
operator info: order by:created_at, offset:0, count:50
*************************** 2. row ***************************
id: IndexLookUp_21
actRows: 5000
task: root
execution info: time:1.1s, loops:5
operator info: index:idx_user_created(user_id,created_at), table:orders
*************************** 3. row ***************************
id: IndexRangeScan_19
actRows: 5000
task: cop[tikv]
execution info: time:980ms, loops:8
operator info: range:[42,42], keep order:true

Значення: більшість часу витрачається на cop tasks у TiKV. Це вказує на латентність зберігання, hotspot або занадто багато залучених regions.

Рішення: дослідіть латентність TiKV, розподіл regions для того індексного діапазону і чи створює навантаження hotspot на ключі типу user_id=42.

Завдання 10: Перевірити hotspot-и регіонів (чи один діапазон топить стор)

cr0x@server:~$ tiup ctl:v7.5.1 pd -u http://10.0.2.21:2379 hot read
{
  "as_peer": {
    "stats": [
      {"store_id": 3, "region_id": 918273, "hot_degree": 97, "flow_bytes": 125829120}
    ]
  }
}

Значення: store 3 обслуговує дуже гарячий region для читань. Це може домінувати у хвості латентності.

Рішення: пом’якшіть hotspot: додайте TiDB-вузли для масштабування SQL, подумайте про розділення регіонів, змініть патерни доступу застосунку або використайте кеш для цього гарячого діапазону ключів.

Завдання 11: Перевірити латентність TiKV через Prometheus text endpoint (чи зберігання — вузьке місце?)

cr0x@server:~$ curl -s http://10.0.3.33:20180/metrics | egrep "tikv_engine_write_stall|rocksdb_compaction_pending_bytes" | head
tikv_engine_write_stall 1
rocksdb_compaction_pending_bytes 2.184532e+10

Значення: активний write stall і величезний накопичений обсяг для компактації. RocksDB відстає; записи і читання постраждають.

Рішення: припиніть звинувачувати SQL. Виправляйте дискову пропускну здатність, зменшуйте write amplification, перегляньте опції TiKV і розгляньте додавання сторів для розподілу навантаження компактації.

Завдання 12: Перевірити синхронізацію часу (розподілені транзакції не люблять неточні годинники)

cr0x@server:~$ chronyc tracking
Reference ID    : C0A80101 (ntp-01)
Stratum         : 3
System time     : 0.000143219 seconds slow of NTP time
Last offset     : -0.000012034 seconds
RMS offset      : 0.000084321 seconds
Frequency       : 10.123 ppm slow
Residual freq   : -0.002 ppm
Skew            : 0.090 ppm
Root delay      : 0.001234 seconds
Root dispersion : 0.001901 seconds
Update interval : 64.0 seconds
Leap status     : Normal

Значення: годинник добре синхронізований (мікро- чи субмілісекундні відхилення). Добре. Погана синхронізація годинників створює дивну поведінку транзакцій і таймаутів у розподілених системах.

Рішення: якщо відхилення великі або leap status проблемний — виправте NTP/chrony перш ніж шукати фантомні сплески латентності.

Завдання 13: Перевірити конфлікти транзакцій TiDB (чи ви боретесь з contention?)

cr0x@server:~$ mysql -h tidb-01 -P 4000 -e "SHOW STATUS LIKE 'tikv_txn_mvcc_%conflict%';"
Variable_name	Value
tikv_txn_mvcc_conflict_counter	18422
tikv_txn_mvcc_write_conflict_counter	12011

Значення: конфлікти відбуваються. Під гарячо-рядковими навантаженнями TiDB поводиться як будь-яка консистентна система: десь примусово відбувається сериалізація.

Рішення: переробіть патерни гарячих рядків (лічильники, оновлення «останній побачений»), батчуйте записи або перенесіть конкурентоємні примітиви в інше сховище.

Завдання 14: Перевірити дискову латентність MySQL (чи хост не бреше?)

cr0x@server:~$ iostat -x 1 3 | egrep "Device|nvme0n1"
Device            r/s     w/s   r_await   w_await  aqu-sz  %util
nvme0n1         210.4   980.2     3.12    18.44    9.87  99.10
nvme0n1         198.1  1050.6     2.88    21.77   11.22  99.54
nvme0n1         205.7  1012.3     3.05    19.90   10.45  99.33

Значення: %util зашпильований; write latency ~20ms. Це достатньо, щоб зробити нещасними і MySQL, і TiKV.

Рішення: перед будь-якою міграцією виправте зберігання: ізолюйте навантаження, апгрейдніть диски, перевірте налаштування RAID/контролера і зменшіть write amplification.

Завдання 15: Перевірити повторні передачі мережі (розподілені системи посилюють мережеві гріхи)

cr0x@server:~$ netstat -s | egrep "segments retransmited|packet receive errors" | head
    13245 segments retransmited
    0 packet receive errors

Значення: є повторні передачі; наскільки це «погано» залежить від базової лінії і вікна часу, але в TiDB це може проявлятися як raft-латентність і сплески p99.

Рішення: зіставте з вікном інциденту. Якщо retransmits підскочили — перевірте NIC drops, oversubscription або «шумних сусідів».

Завдання 16: Підтвердити насичення TiDB-вузла (масштабування SQL-слою — важіль)

cr0x@server:~$ top -b -n 1 | head -n 12
top - 09:45:11 up 34 days,  3:21,  1 user,  load average: 24.12, 21.88, 19.05
Tasks: 312 total,   2 running, 310 sleeping,   0 stopped,   0 zombie
%Cpu(s): 92.1 us,  3.4 sy,  0.0 ni,  3.9 id,  0.0 wa,  0.0 hi,  0.6 si,  0.0 st
MiB Mem :  64384.0 total,   1200.3 free,  45210.8 used,  17972.9 buff/cache
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
22411 tidb      20   0 14.1g  3.2g  112m S  860.2   5.1  12:11.43 tidb-server

Значення: SQL-вузол TiDB сильно навантажений CPU. Оскільки TiDB безстансний на SQL-слої, часто можна горизонтально масштабувати цей шар.

Рішення: додайте TiDB-вузли або зменште дорогі запити; не звинувачуйте одразу TiKV, якщо TiDB вже «плавиться».

Швидкий план діагностики (перші/другі/треті перевірки)

Коли падає латентність або пропускна здатність, ви хочете знайти вузьке місце без винаходу теорії розподілених систем на дошці під час інциденту.

Перше: підтвердити радіус ураження і який шар гарячий

  • Це всі запити чи конкретні кінцеві точки? Якщо це один endpoint, підозрюйте план запиту, статистику або hotspot однієї таблиці — не «весь кластер».
  • Перевірте насичення SQL-слою: CPU TiDB, кількість з’єднань, threadpool. У MySQL перевірте CPU primary і активні потоки.
  • Перевірте рівні помилок/таймаутів: таймаути часто передують видимим метрикам насичення.

Друге: перевірити латентність зберігання і compaction/backpressure

  • MySQL: iostat, InnoDB history list length, buffer pool hit rate, fsync latency.
  • TiDB/TiKV: RocksDB compaction pending bytes, write stalls, raftstore apply duration, утилізація стору.

Третє: перевірити патології розподілу

  • Hot regions: один region домінує в читаннях/записах.
  • Leader imbalance: один стор має занадто багато лідерів.
  • Мережа: retransmits, втрата пакетів або несподівані затримки між AZ.
  • Статистика і регреси планів: раптові зміни плану після деплой/DDL/analyze.

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

Три міні-історії з практики

Міні-історія 1: Інцидент через неправильне припущення

Середня SaaS-компанія мігрувала сервіс, пов’язаний з платежами, з MySQL на TiDB. Застосунок використовував MySQL-протокол і проходив інтеграційні тести.
Всі вітали. Вони запланували cutover на вівторок, бо «вівторок не зайнятий», що люди кажуть перед тим, як вівторок стає зайнятим.

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

У TiDB той самий патерн створив скупчення блокувань і конфліктів транзакцій під час сплеску трафіку. Координація розподілених транзакцій підсилила те, що колись було м’яким contention-ом, до p99 латентності і таймаутів повторів, що ще більше посилило contention. Шторм повторів — це самоопікуюче пророцтво з кращим логуванням.

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

Міні-історія 2: Оптимізація, що відбилася бумерангом

Інша компанія запускала MySQL на швидких NVMe і під час рев’ю продуктивності хтось запропонував «додамо більше індексів, щоб швидше читалось».
Вони додали кілька композитних індексів на велику таблицю подій. Читання на dashboard покращились. Всі тихо кивнули. Потім шлях запису почав просідати.

Відбій був класичним: pipeline інжесту був write-heavy, і кожен індекс перетворював кожен insert у додаткову write amplification.
InnoDB почав витрачати більше часу на підтримку вторинних індексів, ніж на основну роботу. Binlog зростав, реплікація відставала, і команда почала тимчасово направляти більше читань на primary, що ще більше навантажувало primary. «Тимчасово» — як виходять аварії у звичку.

Вони намагались «вирішити» це, переїхавши на TiDB, очікуючи, що горизонтальне масштабування знищить проблему. Ні. TiDB розподілив write amplification по TiKV-нодах, але також збільшив тиск на компактацію. Кластер тримався, але хвостова латентність під час пікових інжестів пішла вбік.

Остаточне рішення було нудним: прибрати індекси з низькою цінністю, ввести rollup таблиці для dashboard-запитів і перенести частину аналітики в TiFlash. Урок не в тому, що індекси погані. Урок у тому, що оптимізація без розуміння write-path — це диверсія з добрими намірами.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Третя організація запускала TiDB для клієнтського навантаження і MySQL для кількох внутрішніх інструментів. Їхній кластер TiDB мав сувору практику: квартальні game days і щомісячні відпрацювання відновлення.
Не «ми одного разу відновлювали в staging», а «ми можемо відновити консистентну снапшот і підняти застосунок проти неї».

Одного ранку деплой вніс тонку помилку: фоновий job записував некоректні дані в критичну таблицю. Помилка не була очевидною. Вона проходила unit-тести і проявлялась лише при певному розподілі даних. Коли її помітили, неправильні записи вже йшли кілька годин.

Оскільки вони практикувалися, команда не стала панічно мутувати продукшн. Вони зупинили job, зробили інцидентний снапшот для судової експертизи і відновили з відомої доброї точки в паралельний кластер. Потім відтворили відфільтрований набір коректних записів. Сервіс залишався частково доступним, а вплив на клієнтів був обмежений.

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

Поширені помилки: симптом → корінь → вирішення

1) Симптом: «TiDB повільний» тільки для одного tenant або одного user_id

Корінь: hotspot на вузькому діапазоні ключів (один Region, один лідер) через монотонні ID або шаблони доступу за tenant-ключем.

Вирішення: переробити ключі (hash prefix, scatter), увімкнути стратегію розділення регіонів, додати кеш, або виділити гарячих клієнтів. Підтверджуйте через PD hot region outputs.

2) Симптом: p99 сплески латентності під час пікових записів; CPU виглядає нормальним

Корінь: backlog компактації RocksDB у TiKV і write stalls; зберігання не встигає за write amplification.

Вирішення: збільшити дискову пропускну здатність, додати TiKV-нодів, зменшити кількість індексів, батчувати записи, переглянути налаштування компактації і вибір компресії.

3) Симптом: primary MySQL виглядає здоровим, але репліки відстають непередбачувано

Корінь: великі транзакції, сильні сплески записів або репліки, обмежені IO; інколи однопотокове застосування SQL стає обмеженням.

Вирішення: розбити транзакції, налаштувати паралельність реплікації, зменшити churn binlog (індекси, формат рядків) і перевірити латентність диска на репліках.

4) Симптом: deadlock-и або timeouts по блокуваннях зростають після переходу на TiDB

Корінь: довгі транзакції і рядки з високою contention стають дорожчими в розподілених шляхах коміту; ретраї підсилюють навантаження.

Вирішення: скоротіть транзакції, уникайте патерну «select then update», використовуйте оптимістичні concurrency-патерни і робіть ретраї з джитером і обмеженням.

5) Симптом: зміна схеми спричиняє латентність по всьому кластеру і підвищений IO

Корінь: онлайн DDL все ще викликає фонову роботу (backfill/reorg) і підвищує читально-записне навантаження по TiKV.

Вирішення: плануйте DDL під час низького трафіку, застосовуйте тротлінг, якщо доступний, забезпечте запас потужності і слідкуйте за метриками TiKV компактації/raft apply під час DDL.

6) Симптом: після міграції деякі запити повертають інші результати

Корінь: залежність від MySQL-специфічної поведінки: неявні приведення типів, відмінності колацій, недетермінований GROUP BY або відсутність ORDER BY.

Вирішення: зробіть SQL явним: коректні ORDER BY, жорсткі SQL-режими там, де можливо, явні приведення типів, перевірка колацій і тести на крайові запити.

7) Симптом: кластер TiDB «здоровий», але пропускна здатність падає після відмови вузла

Корінь: переобрання лідерів і ребаланс змінюють розподіл лідерів regions; що залишилися сторі перевантажуються.

Вирішення: забезпечте запас потужності, використовуйте правила розміщення узгоджені з зонами відмов, і зробіть ребаланс лідерів. Перевіряйте через PD store і hot region outputs.

8) Симптом: MySQL «в порядку», поки запускаються бекапи — потім усе повільне

Корінь: IO-конфлікт бекапу і накладні витрати снапшоту; інструменти бекапу конкурують з продакшн IO і кешем.

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

Чеклісти / покроковий план

Чекліст A: Коли варто залишитись на MySQL (і спати краще)

  • Ваш primary поміщається в одному сильному вузлі і write QPS не вибухає.
  • Ви можете використовувати репліки, кешування і налаштування запитів/індексів, щоб виконувати SLO.
  • Вам не потрібна сильна консистентність між кількома primaries.
  • Ваша команда не укомплектована для 24/7 запуску розподіленої платформи баз даних.
  • Ви готові приймати застарілість читань реплік для некритичних читань.

Чекліст B: Коли TiDB — раціональний крок (не для резюме)

  • Ви вже шардите MySQL або ось-ось почнете, і це стає ризиком продукту.
  • Вам потрібне горизонтальне масштабування з транзакційною семантикою.
  • Ви готові до спостережуваності, навчання on-call і дисципліни оновлень.
  • Ви можете надати достатньо вузлів, щоб мати запас потужності (compute і storage).
  • У вас є план для hotspot-ів, а не лише надія.

Покроковий план міграції (практичний варіант)

  1. Інвентаризуйте «MySQL-ізми» у вашому застосунку: SQL-моди, неявні приведення, залежність від невизначеного порядку, нестандартні функції.
  2. Базова продуктивність: топ-запити за латентністю і загальним часом; розміри транзакцій; піковий write throughput.
  3. Проєкт для contention: визначте гарячі рядки, лічильники і патерни «останнє оновлення»; переробіть до міграції.
  4. Забезпечте TiDB з запасом потужності: не починайте з «ледь достатньо». Розподілені системи карають за малі запаси.
  5. Вирішіть розміщення і домени відмов: репліки по AZ/rack; PD quorum; кількість TiKV.
  6. Тестуйте навантаження з даними, що нагадують продакшен і skew: рівномірне синтетичне навантаження — це як пропустити hotspot-и.
  7. Запускайте dual reads обережно: порівнюйте результати для критичних запитів; слідкуйте за відмінностями колацій/приведень.
  8. Плануйте cutover з можливістю відкату: або dual-write з валідацією, або контрольований вікно freeze.
  9. Проводьте відпрацювання відновлення до go-live: бекап без відновлення — театр.
  10. Навчіть on-call: «що таке hotspot region» не має бути відкриттям під час інциденту.
  11. Після cutover заморозьте DDL і налаштування: спостерігайте стабільну поведінку перед оптимізацією.
  12. Налаштуйте алерти за SLO: спочатку латентність і рівні помилок, потім сигнали насичення (CPU, compaction, баланс лідерів).

Другий і останній жарт: Розподілена база даних — це просто звичайна база даних, тільки вона може зламатися в більшій кількості місць одночасно.

Поширені запитання

1) Чи справді TiDB «drop-in» для MySQL-застосунків?

Drop-in для протоколу і багатьох SQL-патернів — так. Drop-in для поведінки — ні. Припускайте, що ви знайдете крайові випадки:
колації, неявні приведення, патерни обсягу транзакцій і відмінності продуктивності при skew-ових навантаженнях.

2) Якщо я зараз не шарджу, чи варто переходити на TiDB «на всякий випадок»?

Зазвичай ні. Якщо MySQL задовольняє ваші SLO при розумних операційних практиках, залишайтесь. TiDB — стратегічний хід, коли шардінг або потреба в multi-primary стають неминучим ризиком продукту.

3) Чи виправить TiDB автоматично мої повільні запити?

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

4) Який найпоширеніший режим відмов TiDB у реальному житті?

Hotspot-и і backpressure з боку зберігання. Hotspot-и концентрують навантаження на кількох regions/stores. Backpressure проявляється як backlog компактації RocksDB і write stalls, що каскадують у латентність запитів.

5) Який найпоширеніший режим відмов MySQL у реальному житті?

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

6) Чим відрізняються бекапи операційно?

Бекапи MySQL концептуально простіші: один primary датасет плюс репліки. Бекапи TiDB мають захопити розподілену консистентність по стору. У обох випадках єдиний змістовний тест — це відновлення і запуск застосунку проти нього.

7) Чи може TiDB замінити репліки для читань і розподіл чит/запис?

TiDB може масштабувати читання додаванням TiDB-вузлів, і він також може використовувати followers у деяких конфігураціях. Але вам все одно треба думати, куди падає навантаження: CPU SQL-слою, hotspot-и TiKV і мережа. Це не «без розподілу», це «інший розподіл».

8) Чи легше робити зміни схеми в TiDB?

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

9) Чи потрібна виділена команда платформи, щоб запускати TiDB?

Вам не потрібна величезна команда, але потрібна чітка відповідальність, компетенція on-call і культура репетицій. Якщо ваша організація ставиться до баз даних як до «петів», TiDB стане дорогим «питомцем з поглядами».

10) Який розумний підхід для пілота?

Почніть із сервісу з чіткими патернами доступу до ключів, хорошим тестуванням і обмеженою «хитрою SQL». Дзеркальте трафік для читань, валідуйте результати, потім поступово переносіть шляхи записів з планом відкату.

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

Якщо ви запам’ятаєте одну річ: MySQL і TiDB — обидва серйозні СУБД. Різниця в тому, де живе складність і як вона ламається під навантаженням.

  • Якщо ви на MySQL: зробіть нудну роботу спочатку — гігієна індексів, дисципліна обсягу транзакцій, моніторинг відставання реплік і відпрацьовані відновлення. Ви можете купити роки відстрочки без міграції.
  • Якщо ви оцінюєте TiDB: запустіть пілот, формований під ваше навантаження. Перевірте не лише функціональну коректність, а й хвостову латентність при skew, поведінку hotspot-ів і що відбувається під час втрати вузла і покрокових оновлень.
  • Якщо ви вже мігруєте: припускайте, що прогалини сумісності існують, і шукайте їх цілеспрямовано. Приберіть довгі транзакції, зробіть SQL явним і побудуйте інцидентний playbook до того, як продакшен зробить його за вас.

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

← Попередня
ZFS iSCSI: Налаштування ZVOL без періодичних затримок під навантаженням
Наступна →
Ubuntu 24.04: забутий IPv6‑файрвол — закрийте реальну діру (не лише IPv4) (case #72)

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