MySQL vs MongoDB: помилка «NoSQL, бо модно», що вбиває продуктивність VPS

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

Ви орендували скромний VPS. Два vCPU, кілька гігабайтів оперативної пам’яті, «SSD»-накопичувач, який виявляється «гучним соседом», і продуктивне навантаження без надмірностей — просто користувачі, замовлення, сесії та кілька фонових задач. Потім хтось каже: «Давайте використаємо MongoDB, це NoSQL, воно масштабовується». Ви деплоїте. Воно працює.
Тиждень. Потім латентність стрибає, load average росте, а графіки нагадують сеїзмограф під час невеликого апокаліпсису.

Цей сценарій на диво поширений: вибір бази даних, зроблений за відчуттями замість фізики. Фізика — це ОЗП, дискові операції і те, як ваш рушій поводиться, коли він не вміщується в пам’ять. На малих VPS саме це вирішує все.

Що насправді вбиває продуктивність VPS

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

1) Промахи кешу стають читаннями з диска, а читання з диска — латентністю

MySQL (InnoDB) і MongoDB (WiredTiger) обидва сильно покладаються на кешування. Коли кеш замалий, вони постійно підвантажують дані зі сховища. На розділеному VPS
це сховище може бути «SSD» з чергою вводу-виводу, захопленою іншим орендарем. Ваш додаток бачить це як випадкові затримки 200–800 мс і починає повторювати запити. Повторні запити збільшують
паралелізм. Паралелізм збільшує тиск на диск. Тепер у вас зворотний цикл з особистістю дереводробарки.

2) Overcommit і OOM killer не домовляються

На малих машинах «просто дайте базі даних більше кешу» часто означає «дозвольте Linux потім її вбити». MongoDB особливо може виглядати стабільною, поки робочий набір трохи не виросте,
потім воно починає конкурувати з OS page cache та іншими процесами. Коли ядро вирішує, що пам’яті немає, воно ніяких ввічливих попереджень не надсилає. Воно обирає процес і завершує його.

3) Write amplification — тихий вбивця бюджету

Бази даних не записують те, що ви уявляєте. Вони пишуть WAL/журнал, брудні сторінки, роблять компактування/злиття, fsync, оновлення метаданих.
«Оновлення одного документу», яке ви собі уявляли, може бути багатьма дрібними випадковими записами плюс фонове обслуговування. На VPS-сховищі випадкові записи дорогі, а тривалі
випадкові записи — це те, як ви дізнаєтесь, що ваш «SSD» насправді «RAID-контролер 2013 року, розділений на 40 чужих».

4) Неправильна модель даних перетворює CPU на тепло, а I/O — на сльози

Найдорожчий запит — той, який ви не проіндексували, тому що вважали «NoSQL означає відсутність схеми й планування». Другий за вартістю — той, який неможливо ефективно індексувати,
бо модель даних заохочує змінні форми документів, глибоке вкладення або «просто зберігати масиви й фільтрувати в коді додатка».
Тут MongoDB використовують погано: не тому що MongoDB поганий, а тому що він поблажливий, доки раптом перестає бути.

Цитата, яка має бути в кожному on-call: «Надія — не стратегія.» — генерал Гордон Р. Салліван.

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

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

Спочатку: CPU, пам’ять чи диск?

  • Заблоковано диском: високий iowait, повільні fsync, зростання черги, стрибки латентності, що корелюють з хвилями записів.
  • Заблоковано пам’яттю: активність swap, major page faults, OOM kills, пропуски кешу бази даних, раптова стіна продуктивності при збільшенні набору даних.
  • Заблоковано CPU: високий користувацький CPU, повільні запити, що вимагають обчислень, regex-скани, парсинг JSON, компресія, накладні витрати шифрування.

По-друге: база даних — це вузьке місце чи додаток?

  • Насичення пулу з’єднань і скупчення потоків часто виглядає як «база даних повільна», але це «додаток неправильно масштабований».
  • Контенція блокувань виглядає як повільні запити, але часто це гаряча рядок/документ, через який всі чекають у черзі.

По-третє: визначте топ‑1–3 запити/операції, що завдають шкоди

  • У MySQL: slow query log + EXPLAIN + InnoDB status.
  • У MongoDB: профайлер + explain() + currentOp.

По-четверте: перевірте поведінку кешу

  • InnoDB: hit rate buffer pool, скидання брудних сторінок, тиск на redo log.
  • WiredTiger: використання кешу, тиск на евікшн, поведінка чекпойнтів.

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

MySQL vs MongoDB: реальні торгові компроміси

MySQL: передбачуваний, нудний і безжально ефективний при хорошій моделі

MySQL з InnoDB — універсальний OLTP-трудяга. Він любить структуровані дані, стабільні шаблони запитів і вдало вибрані індекси. Це не модно.
І це — перевага.

На маленькому VPS MySQL часто перемагає, тому що:

  • Кешування InnoDB просте для розуміння: ви встановлюєте розмір buffer pool і зазвичай можете логічно оцінити hit rate.
  • Оптимізатор запитів + індекси справляються з багатьма шаблонами доступу без того, щоб ви мусили вкладати все в один запис.
  • Операційні інструменти зрілі: slow logs, performance_schema, звичні дашборди та передбачувані backup/restore-процедури.
  • Накладні витрати на дані часто нижчі ніж у JSON‑важких моделей документів, особливо якщо нормалізувати повторювані поля.

MongoDB: потужний, гнучкий і легко перетворюється на машину, що трясе диск

MongoDB блищить, коли ваші дані природно мають документну форму, потрібна еволюція схеми, денормалізація спрощує читання, і ви приймаєте операційні компроміси. Але на малих VPS часто його обирають з неправильної причини: «NoSQL масштабовується». Це не план; це гороскоп.

MongoDB може бути цілком швидким на VPS, якщо:

  • Ваш робочий набір вміщується в ОЗП або у вас швидке, стабільне сховище.
  • Ви проектуєте індекси так, ніби ваша робота від них залежить (так воно й є).
  • Ви контролюєте ріст документів і уникаєте патологічних оновлень.
  • Ви розумієте евікшн WiredTiger і розмір кешу.

Реальність VPS: малі машини карають накладні витрати

Документні бази часто використовують більші документи, ніж потрібно. Більші документи означають, що менше вміщується в кеш. Менше в кеші — більше читань з диска.
На обмеженому VPS кожен miss кешу — податок, який ви платите з відсотками.

Тим часом «нудні таблиці» MySQL зазвичай компактні, проіндексовані і доступні через передбачувані плани запитів. Це не магія; це просто менше накладних витрат на одиницю корисних даних.

Транзакції, обмеження і коректність під навантаженням

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

MongoDB має солідну підтримку транзакцій у сучасних версіях, але мультідокументні транзакції додають накладні витрати і можуть зрушити вузьке місце на блокування, тиск на oplog або відставання реплікації.
Якщо ваш додаток часто потребує міжсутнісних інваріантів, ви або агресивно вкладете (що створює великі гарячі документи), або побудуєте реляційну систему поверх документного сховища. Це рідко найдешевший шлях на VPS.

Реплікація та витрати на надійність — це реальні витрати

Якщо ви правильно запускаєте MongoDB, ви запускаєте replica set. Якщо правильно — MySQL з реплікацією (або хоча б бекапами і binary logs). Обоє мають накладні витрати.
Але «за замовчуванням» MongoDB часто штовхає команди запускати три ноди, навіть якщо бюджетували одну. На малому розгортанні це не лише вартість — це операційна складність і більше точок відмови.

Історичний контекст та цікаві факти

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

  1. MySQL з’явився в середині 1990-х і став базою даних раннього вебу через швидкість, простоту і легкість розгортання.
  2. InnoDB став рушієм за замовчуванням у MySQL 5.5 (2010), принісши відновлення після аварій і блокування на рівні рядка як стандартний досвід.
  3. MongoDB з’явився у 2009 році, народившись у епоху, коли розробники боролися з жорсткими схемами, а горизонтальне масштабування SQL вважалося «важким».
  4. Термін «NoSQL» став популярним близько 2009 року як прапор альтернатив реляційним базам, а не як гарантія продуктивності.
  5. WiredTiger став рушієм за замовчуванням у MongoDB 3.2 (2016), замінивши MMAPv1 і радикально змінивши поведінку пам’яті та диска.
  6. JSON‑тип у MySQL з’явився в MySQL 5.7 (2015), тихе визнання того, що напівструктуровані дані — нормальність, і реляційні рушії можуть це обробляти.
  7. Документні сховища часто платять посиленими записами за зручність читання через денормалізацію; на повільних дисках ці записи проявляються як латентність і тиск на чекпойнти.
  8. Replica set і консенсус (наприклад, вибори) вводять операційні стани, яких у однонодових розгортаннях немає, як-от відкат і поведінка запобігання split‑brain.

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

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

Середня SaaS-команда запустила нову функцію: таймлайни активності користувачів. Хтось стверджував, що документне сховище «очевидно підходить», бо записи таймлайну виглядають як JSON-події. Вони обрали MongoDB і зберігали таймлайн кожного користувача як масив всередині одного документа: один документ на користувача, додавати події в масив.

У staging було красиво. Читання були швидкими: взяти один документ, відрендерити таймлайн. Записи теж «швидкі», бо набір даних був крихітний і все жило в пам’яті.
VPS здавався достатнім.

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

Неправильне припущення було просте: «Таймлайн користувача — один документ.» Це не так. Це колекція з природними межами пагінації. Виправлення теж було простим:
зберігати події як окремі документи з індексом на (user_id, created_at), пагінувати читання і обмежити зберігання. Постмортем не був про те, що MongoDB повільний.
Він був про те, що вони використовували MongoDB як зручне blob‑сховище, а потім були шоковані, коли blob виріс.

Міні‑історія №2: Оптимізація, що обернулась проти

Інша компанія працювала на MySQL на VPS і втомилася від витрат на диск. Доброзичливий інженер вирішив більше стиснути: більший row format, компактніші поля і агресивне використання JSON, щоб «уникнути JOIN». Здавалося, перемога: дані зменшились, бекапи стали меншими, дашборди показували зменшення росту сховища.

Потім CPU зріс, p95 латентності подвоївся, і додаток почав тайм-аутитися під навантаженням. Команда спочатку звинуватила мережу.
Це не була мережа.

«Оптимізація» обернулась проти, бо вона змістила роботу з диска на CPU в найгірший момент. Витяг JSON та функціональні запити перешкоджали використанню індексів.
Стиснення зменшило місце на диску, але збільшило навантаження на CPU і зробило читання менш дружніми до кешу. Їхній buffer pool виглядав нормально, але запити все одно горіли ядрами.

Вони відкотили JSON‑важкий дизайн, повернули реляційні колонки для гарячих шляхів і використовували стиснення лише там, де це не блокувало індексацію.
Урок не в «ніколи не стискайте». Урок у тому: не оптимізуйте для зберігання, не вимірявши CPU і плани запитів. На малому VPS у вас немає запасного CPU, щоб його марнувати.

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

Команда, яка поруч із платіжними сервісами, використовувала MongoDB для внутрішнього event store і MySQL для білінгу. У них не було великого ops‑бюджету, тому вони регулярно робили одну непоказну річ:
тестували відновлення щомісяця. Не «маємо бекапи». Реальні відновлення на чисту машину, з чеклістом і хронометром.

Одного вечора у провайдера VPS трапилась проблема на хості. VM перезавантажилась у файлову систему, що змонтувалась, але файли бази даних були неконсистентні. База не стартувала.
Паніка тривала близько десяти хвилин — особистий рекорд.

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

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

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

Ось перевірки, які я справді запускаю на малих продакшн‑серверах. Кожне завдання містить: команду, що типовий вивід означає і яке рішення приймати далі.
Запускайте їх під час інциденту або краще — зараз, коли система спокійна, щоб зафіксувати базові показники.

Завдання 1: Підтвердити, чи заблоковано CPU чи I/O

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0  41200  98200 981200    0    0    12    34  180  420 12  4 80  4  0
 4  2      0  19800  62100 712300    0    0   220   540  260  880 15  6 44 35  0
 3  1      0  20500  62100 710900    0    0   180   610  250  840 14  6 48 32  0

Значення: wa (iowait) підскочив до ~35%. Це класичний підпис «диск — вузьке місце», особливо якщо скарги на затримки співпадають.

Рішення: Перестаньте здогадуватись про запити; перевірте дискову латентність і тиск кешу бази даних далі.

Завдання 2: Перевірити реальну дискову латентність і чергування

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

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          14.22    0.00    5.11   31.67    0.40   48.60

Device            r/s     w/s   rKB/s   wKB/s  avgrq-sz avgqu-sz   await  r_await  w_await  %util
vda              35.0   120.0   980.0  6200.0     86.2     5.90   42.10    18.40    49.20  96.50

Значення: await близько 42ms і %util майже 100% означає, що диск насичений. Записи особливо повільні.

Рішення: Зменшити тиск на запис (пакетування, індекси, налаштування чекпойнтів) або підвищити рівень сховища. На VPS іноді правильне рішення — «заплатити за кращий I/O».

Завдання 3: Подивитись, чи є swap (тихий вбивця)

cr0x@server:~$ free -m
               total        used        free      shared  buff/cache   available
Mem:            3940        3605          45          60         289         110
Swap:           2047        1380         667

Значення: Swap активно використовується і available пам’яті мало. Якщо БД свопить, кожен запит перетворюється на операцію сховища.

Рішення: Зменшити розмір кешу бази даних, зменшити паралелізм додатка або додати ОЗП. Якщо нічого не робити, ваша «налаштування БД» перетвориться на «налагодження Linux paging».

Завдання 4: Перевірити OOM kills

cr0x@server:~$ journalctl -k --since "2 hours ago" | tail -n 20
Dec 30 09:41:12 server kernel: Out of memory: Killed process 2145 (mongod) total-vm:5064820kB, anon-rss:2860100kB, file-rss:0kB, shmem-rss:0kB, UID:110 pgtables:7012kB oom_score_adj:0
Dec 30 09:41:12 server kernel: oom_reaper: reaped process 2145 (mongod), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Значення: Ядро вбило mongod. Це не «помилка MongoDB». Це помилка ємності і розмірів.

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

Завдання 5: Підтвердити, який процес їсть пам’ять

cr0x@server:~$ ps -eo pid,comm,rss,pcpu --sort=-rss | head
 2145 mongod   2923400  88.2
 1320 node      420800  12.1
  901 mysqld    210500   6.8
  755 redis-server 80400  1.2

Значення: MongoDB домінує в RSS. На малому боксі це може позбавити ресурсів усе інше.

Рішення: Якщо MongoDB потрібен, обмежте кеш WiredTiger. Якщо необов’язковий — перегляньте архітектуру замість того, щоб «боротись із фізикою».

Завдання 6: Перевірка MongoDB: тиск кешу WiredTiger

cr0x@server:~$ mongosh --quiet --eval 'db.serverStatus().wiredTiger.cache'
{
  "bytes currently in the cache" : 1702453248,
  "maximum bytes configured" : 2147483648,
  "tracked dirty bytes in the cache" : 392154112,
  "pages evicted by application threads" : 18342,
  "pages queued for eviction" : 742,
  "eviction server candidate queue empty when topping up" : 0
}

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

Рішення: Якщо робочий набір не вміщується, або додавайте ОЗП, або зменшуйте документи/індекси, або погоджуйтеся на повільніший диск. Налаштування не перетворить 4GB VPS на 64GB кеш.

Завдання 7: MongoDB: виявлення повільних операцій через профайлер

cr0x@server:~$ mongosh --quiet --eval 'db.setProfilingLevel(1, { slowms: 50 })'
{ "was" : 0, "slowms" : 50, "sampleRate" : 1, "ok" : 1 }
cr0x@server:~$ mongosh --quiet --eval 'db.system.profile.find().sort({ts:-1}).limit(3).pretty()'
{
  "op" : "query",
  "ns" : "app.events",
  "command" : { "find" : "events", "filter" : { "userId" : "u_123" }, "sort" : { "ts" : -1 }, "limit" : 50 },
  "keysExamined" : 0,
  "docsExamined" : 51234,
  "millis" : 231,
  "planSummary" : "COLLSCAN"
}

Значення: COLLSCAN разом із великим docsExamined — це відсутній або неправильний індекс. На VPS колекційні скани — як зігрівати датацентр.

Рішення: Додайте індекс, що відповідає фільтру+сорту (наприклад, { userId: 1, ts: -1 }) і перевірте через explain(). Якщо індекси виростуть за рамки ОЗП, ви все одно програєте — плануйте ємність.

Завдання 8: MongoDB: поточні операції та очікування блокувань

cr0x@server:~$ mongosh --quiet --eval 'db.currentOp({ "secs_running": { "$gte": 2 } }).inprog.map(o => ({op:o.op, ns:o.ns, secs:o.secs_running, waiting:o.waitingForLock, desc:o.desc}))'
[
  {
    "op" : "command",
    "ns" : "app.events",
    "secs" : 18,
    "waiting" : true,
    "desc" : "conn1421"
  }
]

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

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

Завдання 9: MySQL: глобальний статус для buffer pool і читань

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';"
+---------------------------------------+-----------+
| Variable_name                         | Value     |
+---------------------------------------+-----------+
| Innodb_buffer_pool_pages_total        | 131072    |
| Innodb_buffer_pool_pages_free         | 128       |
| Innodb_buffer_pool_read_requests      | 983420112 |
| Innodb_buffer_pool_reads              | 8420132   |
+---------------------------------------+-----------+

Значення: Співвідношення read_requests до reads вказує на пристойний hit rate, але 8.4M читань з диска може бути болючим на слабому сховищі.

Рішення: Якщо читання корелюють з латентністю, збільшіть buffer pool (якщо є пам’ять) або зменшіть розмір набору даних/індексів. Якщо пам’ять напружена — спочатку виправляйте запити й індекси.

Завдання 10: MySQL: знайти топи очікувань і гарячі запити

cr0x@server:~$ mysql -e "SELECT event_name, count_star, sum_timer_wait/1000000000000 AS total_seconds FROM performance_schema.events_waits_summary_global_by_event_name ORDER BY sum_timer_wait DESC LIMIT 5;"
+--------------------------------------+------------+--------------+
| event_name                           | count_star | total_seconds|
+--------------------------------------+------------+--------------+
| wait/io/file/innodb/innodb_log_file  |     182341 |        812.3 |
| wait/io/file/innodb/innodb_data_file |     491020 |        504.1 |
| wait/synch/mutex/innodb/buf_pool     |   12034011 |        220.7 |
| wait/io/file/sql/binlog              |     320114 |        118.9 |
| wait/lock/table/sql/handler          |      80012 |         62.2 |
+--------------------------------------+------------+--------------+

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

Рішення: Розгляньте пакетування записів, налаштування innodb_log_file_size і компроміси innodb_flush_log_at_trx_commit. Не «оптимізуйте» надійність без рішення бізнес‑стейкхолдера.

Завдання 11: MySQL: визначити повільні запити

cr0x@server:~$ sudo tail -n 5 /var/log/mysql/mysql-slow.log
# Time: 2025-12-30T09:52:41.123456Z
# Query_time: 2.184  Lock_time: 0.000  Rows_sent: 50  Rows_examined: 981230
SELECT * FROM events WHERE user_id='u_123' ORDER BY ts DESC LIMIT 50;

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

Рішення: Додайте або виправте індекс (наприклад, (user_id, ts)), потім перевірте через EXPLAIN.

Завдання 12: MySQL: перевірити план через EXPLAIN

cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM events WHERE user_id='u_123' ORDER BY ts DESC LIMIT 50\G"
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: events
   partitions: NULL
         type: ALL
possible_keys: idx_user_ts
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 980000
     filtered: 10.00
        Extra: Using where; Using filesort

Значення: type: ALL означає повний скан; Using filesort додає додаткову роботу сортування. MySQL каже вам, чому повільно.

Рішення: Створіть правильний композитний індекс і підтвердіть, що key використовується. Якщо все одно сканує — ваші типи/колації або форма запиту можуть блокувати використання індексу.

Завдання 13: Перевірити скупчення з’єднань (додаток може DoSити БД)

cr0x@server:~$ ss -s
Total: 941 (kernel 0)
TCP:   811 (estab 640, closed 121, orphaned 0, synrecv 0, timewait 121/0), ports 0

Transport Total     IP        IPv6
RAW       0         0         0
UDP       7         6         1
TCP       690       651       39
INET      697       657       40
FRAG      0         0         0

Значення: Сотні встановлених TCP‑з’єднань можуть вказувати на неправильні розміри пулу з’єднань або «thundering herd» під час повторів.

Рішення: Обмежте паралелізм додатка, використовуйте розумний розмір пулу і розгляньте проксі (для MySQL), якщо churn з’єднань високий.

Завдання 14: Перевірити простір файлової системи і тиск інодів

cr0x@server:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        60G   55G  2.1G  97% /
cr0x@server:~$ df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/vda1      3932160 3919000  13160  100% /

Значення: Диск майже заповнений і іноди вичерпані. Бази даних це ненавидять. Як і логи. Як і все інше.

Рішення: Звільніть простір негайно (ротейт логів, видалити старі бекапи), потім додайте моніторинг і політики збереження, щоб більше не потрапляти на 97%.

Завдання 15: Знищити «таємну латентність», вимірявши fsync

cr0x@server:~$ sudo dd if=/dev/zero of=/var/lib/db/fsync-test bs=4k count=4096 conv=fdatasync
4096+0 records in
4096+0 records out
16777216 bytes (17 MB, 16 MiB) copied, 1.84213 s, 9.1 MB/s

Значення: 16MiB з fdatasync, що займає ~1.8s — тривожний сигнал для робочих навантажень з гарантією запису. База відчує це як латентність комміту.

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

Завдання 16: Перевірити налаштування надійності MySQL перед «оптимізацією»

cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('innodb_flush_log_at_trx_commit','sync_binlog','innodb_doublewrite');"
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| innodb_doublewrite           | ON    |
| innodb_flush_log_at_trx_commit | 1   |
| sync_binlog                  | 1     |
+------------------------------+-------+

Значення: Ці налаштування віддають пріоритет надійності. Це добре — поки ваш диск не витримує.

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

Жарт №2: «Ми вибрали NoSQL заради продуктивності» — це як купити пікап, щоб виграти велоперегони.

Поширені помилки

Це не теорія. Це патерни, які з’являються на реальних VPS о 3-й ночі.

1) Симптом: p95 латентності стрибає кожні кілька хвилин

Корінь: Сторм чекпойнтів/флашів. Чекпойнти MongoDB або скидання брудних сторінок MySQL вражають насичений диск.

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

2) Симптом: MongoDB «швидкий у dev, повільний у prod»

Корінь: Dev‑набір даних вміщується в ОЗП. Prod — ні. Частота промахів кешу зростає; індекси не вміщуються; диск стає базою даних.

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

3) Симптом: MySQL CPU високий, але диск виглядає нормально

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

Виправлення: Використовуйте EXPLAIN, додайте правильні індекси, переносьте обчислені поля в збережені колонки, і припиніть сортування великих наборів без індексів.

4) Симптом: OOM kills або випадкові перезапуски бази

Корінь: Розміри кешу припускають більше ОЗП, ніж у вас є; фрагментація пам’яті; додаткові сервіси на тому самому VPS; спіраль смерті swap.

Виправлення: Обмежте кеші (WiredTiger і InnoDB), зарезервуйте ОЗП для OS і додатка, і зменште co‑located навантаження. Якщо база потрібна — дайте їй окремий сервер.

5) Симптом: «Додали індекс і стало повільніше»

Корінь: Індекс збільшує write amplification; фонові побудови індексів конкурують за I/O; індекс не відповідає формі запиту і не використовується.

Виправлення: Підтвердіть використання (EXPLAIN або MongoDB explain()). Будуйте індекси в безпіковий час. Використовуйте мінімальний набір індексів, що підтримує реальні запити.

6) Симптом: Відставання реплікації росте під час піків трафіку

Корінь: Первинний вузол насичений; вторинки не встигають застосувати опи; диск — вузьке місце; забагато дрібних транзакцій.

Виправлення: Поліпшіть I/O первинного, пакетування записів, переконайтеся, що вторинки мають схоже сховище, та уникайте довготривалих транзакцій, що затримують apply.

7) Симптом: «MongoDB використовує всю пам’ять; отже, це погано»

Корінь: Плутання використання кешу з утечею пам’яті. Бази даних хочуть пам’яті; проблема виникає, коли вони відбирають її в OS і викликають swap/OOM.

Виправлення: Встановіть явні ліміти кешу і залиште місце для filesystem cache та інших процесів. Пам’ять без запасу — просто майбутній простій.

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

Чекліст рішення: чи повинно це навантаження бути на MySQL або MongoDB на VPS?

  • Обирайте MySQL, якщо потрібні JOIN, строгі обмеження, стабільна схема, транзакційні оновлення між сутностями або передбачувана продуктивність на невеликій ОЗП.
  • Обирайте MongoDB, якщо базовий патерн доступу — документний, документи залишаються обмеженими, індекси вміщуються в пам’ять, і ви зможете запускати replica set дисципліновано.
  • Не обирайте нічого всліпу: змоделюйте гарячі шляхи і виміряйте робочий набір відносно ОЗП. Якщо ви цього не робите — ви граєтеся в рулетку.

Покроково: стабілізувати проблемний VPS з БД за 60–120 хвилин

  1. Зупиніть кровотечу: обмежте швидкість важких записів, призупиніть неважливі задачі, тимчасово знизьте паралелізм запитів.
  2. Підтвердіть вузьке місце: vmstat + iostat + перевірки пам’яті.
  3. Знайдіть головних порушників: MySQL slow log / performance_schema; MongoDB profiler/currentOp.
  4. Виправіть найгірший запит першими: додайте правильний індекс або перепишіть запит, щоб використовувати існуючий.
  5. Обмежте кеші безпечно: залиште ОЗП для OS і додатка; уникайте swap.
  6. Перевірте стан диска і простір: заповнені диски поводяться погано; вичерпання інодів — прихована аварія.
  7. Повторно тестуйте під навантаженням: підтвердіть, що p95 і дисковий await покращилися; слідкуйте за новими вузькими місцями.
  8. Запишіть базу: hit rate, дискова латентність, кількість з’єднань і типовий ops/sec. Це стане вашим майбутнім «швидким діагнозом».

Покроково: запобігти помилці «NoSQL бо модно»

  1. Запишіть 5 реальних запитів, які ваш додаток виконуватиме кожну секунду і кожну хвилину.
  2. Для MongoDB: визначте межі документів і правила максимального росту документів.
  3. Для MySQL: визначте індекси для гарячих шляхів і тримайте гарячі колонки типізованими та проіндексованими (не захованими в JSON).
  4. Завантажте реалістичні дані в staging і змусьте сценарій miss кешу, обмеживши ОЗП або використавши менший інстанс.
  5. Виміряйте fsync латентність диска і перевірте, чи відповідає вона вашим очікуванням щодо надійності.
  6. Практикуйте відновлення за графіком. Ваше майбутнє «я» не матиме часу вчитися під час аварії.

FAQ

1) Чи MongoDB завжди повільніший за MySQL на VPS?

Ні. MongoDB може бути дуже швидким, коли ваш патерн доступу документно-центричний і робочий набір вміщується в ОЗП. Звична VPS‑помилка — «набір даних виріс, кеш ні».

2) Чому продуктивність MongoDB різко падає при рості даних?

Бо промахи кешу перетворюються на читання з диска, а наклад на документи/індекси може збільшити робочий набір. Коли тиск евікшн наростає на повільному сховищі, латентність стрибає.

3) Чи можна просто збільшити WiredTiger cache і завершити справу?

Не безпечно на малому VPS. Якщо ви позбавите OS і інших сервісів пам’яті, ви обміняєте «повільні запити» на swap storms і OOM kills. Обмежуйте його навмисно і залишайте запас.

4) Чи MySQL «кращий», бо реляційний?

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

5) Чому люди кажуть «NoSQL масштабовується»?

Історично багато NoSQL систем проектувалися з горизонтальним розподілом як пріоритетом. Але масштабування не автоматичне, і на одному малому VPS ви нічого горизонтально не масштабуєте.

6) Яка найбільша помилка схеми MongoDB на VPS?

Необмежені документи (масиви, що ростуть вічно, глибоко вкладені структури або «зберігати всю історію в одному doc»). Це виглядає елегантно, поки не стане великим гарячим об’єктом, що руйнує локальність кешу і поведінку запису.

7) Яка найбільша помилка MySQL на VPS?

Запуск зі стандартними конфігураціями, що припускають більше ОЗП і I/O, ніж у вас є, плюс відсутні композитні індекси. Потім ви намагаєтесь «виправити» це додаванням CPU, що не лікує дискові затримки.

8) Чи варто вимикати fsync/налаштування надійності для отримання продуктивності?

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

9) Якщо мій додаток багато використовує JSON, чи означає це, що MongoDB очевидний вибір?

Ні. JSON як формат обміну даними не означає, що документна база оптимальна. MySQL може зберігати і індексувати структуровані колонки і одночасно тримати JSON для холодних полів.

10) Яка найпростіша безпечна стратегія для крихітного VPS?

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

Висновок: наступні кроки, які ви справді можете зробити

Помилка «NoSQL бо модно» — не в тому, що обрали MongoDB. Вона в тому, що обрали його, не розуміючи, за що VPS змушений платити: промахи кешу, fsync, компактування і евікшн кешу.
MySQL часто — нудний переможець на малих боксах, бо він компактний, передбачуваний і простіший для налаштування під звичні OLTP‑шляхи. MongoDB може бути чудовим вибором, коли ваші документи обмежені і індекси вміщуються — особливо якщо ви проектуєте під реальні запити, а не мрії.

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

  • Пройдіть швидкий план діагностики один раз у спокійний період і зафіксуйте базові iostat, пам’ять і статистику кешу БД.
  • Виберіть топ‑5 запитів і доведіть, що вони використовують індекси (EXPLAIN або MongoDB explain()).
  • Встановіть явні ліміти кешу, щоб OS ніколи не «вела переговори» зі swap.
  • Протестуйте відновлення. Не «ми маємо бекапи». Справжнє відновлення на чисту машину.
  • Якщо диск await постійно високий — припиніть хитрувати навколо цього і підвищуйте сховище або виносьте базу з VPS.
← Попередня
Таймінги RAM без болю: МГц проти CL і що купувати
Наступна →
WordPress 404 на записах: виправлення пермалінків без шкоди для SEO

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