MySQL проти MariaDB на VPS з 2 ГБ RAM: профілі налаштування, що не приводять до краху

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

Ви купили VPS з 2 ГБ, бо «достатньо для невеликого додатка». Потім база даних почала поводитися як крихкий кімнатний вазон: трохи більше з’єднань, важкий запит — і вона падає, іноді забираючи з собою весь вузол. Вам не потрібні героїчні заходи. Потрібні здорові бюджети пам’яті, передбачуваний I/O і налаштування, що відмовляють поступово, а не вибухово.

Це польовий посібник для запуску або MySQL, або MariaDB на маленькій машині без перетворення OOM-killer ядра на вашого головного DBA. Я висловлюю думку прямо, бо продакшн сам по собі прямолінійний: йому байдуже на ваш бенчмарк, якщо сервер перезавантажується о 3 ранку.

Вибір MySQL чи MariaDB на 2 ГБ: справжні критерії рішення

На VPS з 2 ГБ питання «MySQL чи MariaDB» менше про ідеологію й більше про оперативну зручність: дефолти, пакети та які підводні міни ви готові обслуговувати.

Моя рекомендація (з думкою й застереженнями)

  • Якщо вам потрібна передбачувана поведінка в різних хмарах і керованих сервісах: обирайте MySQL 8.0. Він відповідає тому, що запускають більшість хостингів, і операційна експертиза широка.
  • Якщо ви самостійно хостите і хочете органи керування, корисні для маленьких машин: MariaDB може бути дружнішим, особливо з thread pool (в деяких збірках) і більш раціональним пакуванням у багатьох дистрибутивах.
  • Якщо ви покладаєтесь на фічі MySQL 8 (окремі JSON- та реплікаційні поведінки, новіший оптимізатор, офіційні інструменти): обирайте MySQL, налаштуйте консервативно й живіть далі.
  • Якщо ви хочете мінімізувати сюрпризи під час оновлень: беріть те, що найкраще підтримує ваш дистрибутив, і тримайте це зафіксованим. На 2 ГБ сюрприз дорого обходиться.

За продуктивністю будь-який двигун заповнить вашу маленьку CPU і повільний диск перш ніж здобуде призи. Справжній ризик — пам’ять: база даних професійно витрачає все, що ви їй даєте, плюс те, що ви забули, що вона може взяти.

Жарт №1: VPS з 2 ГБ і нетюнованою базою даних — як ручний багаж: здається, що все влізе, аж поки блискавка на блискавці не скаже, що ви її образили.

Цікаві факти й контекст, що мають значення

Це не факти для вікторини. Це ті, що пояснюють, чому налаштування різняться, чому існують дефолти і чому «просто скопіювати цей my.cnf» — пастка.

  1. MariaDB відгалузилася від MySQL після придбання Sun компанією Oracle (2010): це відгалуження було не тільки політичним — воно привело до відмінних дефолтів, фіч і іноді різного трактування «сумісності».
  2. MySQL 5.6 зробив InnoDB головним вибором для серйозних навантажень: перехід від налаштувань ери MyISAM до InnoDB-центричної оптики змінив уявлення про те, що «безпечне».
  3. MySQL 8.0 повністю прибрав query cache: старі «гайди по продуктивності» все ще радять його крутити; у MySQL 8 цих ручок немає, а в MariaDB вони можуть ще й нашкодити.
  4. Інструмент продуктивності — буферний пул InnoDB: правило «дайте 80% RAM» поширилось на виділені бази — на 2 ГБ VPS з іншими процесами це необачно.
  5. Thread pooling пішов різними шляхами: реалізації MariaDB широко використовуються; історія MySQL залежить від видання та версії. На малих ядрах обробка потоків впливає на хвостову латентність більше, ніж ви думаєте.
  6. Логи InnoDB / redo еволюціонували: налаштування ємності redo та поведінки flush важливі, бо маленькі VPS-диски часто мережеві та стрибкоподібні.
  7. Дефолти пакування Ubuntu/Debian змінювались з часом: у різних версіях ви побачите різні базові конфіги, отже «стоковий MySQL» — це не одна і та сама річ.
  8. OOM killer Linux не дбає про ваше SLA: він просто вбиває щось. Часто mysqld. Іноді ваш додаток. У будь-якому випадку ваш інцидент-канал загориться.

Ментальна модель для 2 ГБ VPS: куди насправді йде пам’ять

На маленьких хостах питання не «яке налаштування найшвидше?» — а «яку максимальну шкоду це налаштування може зробити при піковій паралельності?» Це зрушення у мисленні запобігає крахам.

Плануйте RAM так само, як плануєте сон під час чергувань

З 2 ГБ RAM у вас немає повних 2 ГБ для MySQL/MariaDB. У вас є:

  • Ядро + сторінкова кеш-пам’ять + slab: зазвичай кілька сотень МБ, більше під навантаженням I/O.
  • Системні сервіси (sshd, journald, cron, агент моніторингу): 50–200 МБ.
  • Ваше виконання додатка (PHP-FPM, Node, Java, Python workers): від «керовано» до «чому він займає 900 МБ».
  • База даних: все, що залишилось, і вона намагатиметься взяти все.

На виділеному DB-only 2 ГБ VPS (без додатка) можна сильніше завантажувати БД. На спільному app+DB вузлі (часто у дешевих стеків) треба резервувати простір, щоб ядро не панікувало й не починало вбивати процеси.

Два види пам’яті бази: глобальна та на з’єднання

Більшість гайдів говорять про глобальні буфери (buffer pool, log buffers). Справжній тихий вбивця — пам’ять на одне з’єднання:

  • sort buffers
  • join buffers
  • read buffers
  • tmp tables (в пам’яті поки не викинуться на диск)
  • thread stacks

Кожен виглядає дрібним. Помножте на 200 з’єднань — і ви маєте RAM-санту. Саме тому «просто збільшити max_connections» — одне з найдорожчих рішень, яке можна зробити.

Диск — інша вісь: маленьке сховище часто — «театральний I/O»

Ваш диск VPS може бути мережевим, зберігати бурсти, тротлитись або бути на overcommitted сховищі. Бази даних безжально виявляють ці обмеження. Якщо ви налаштуєте пам’ять, але ігноруєте I/O (частота fsync, очищення брудних сторінок), отримаєте класичні симптоми:

  • швидко — поки не стало повільно
  • випадкові стрибки латентності
  • зависання під час контрольних точок
  • реплікаційні лаги, що з’являються «без причини»

Коротка цитата про надійність (перефразована думка)

«Надія — не стратегія.» — генерал Г. Норман Шварцкопф (перефразована думка, часто використовувана в інженерії й операціях)

Це стосується й тут: не можна сподіватися, що VPS не зіштовхнеться з піковою паралельністю; треба задати межі, щоб пік не вбив систему.

Безпечні профілі налаштування (копіюйте/вставляйте) для 2 ГБ RAM

Ці профілі орієнтовані на «не падає» перш за все, «достатньо швидко» по-друге. Вони припускають InnoDB (ви повинні бути на InnoDB, якщо у вас немає дуже конкретної причини інакше).

Перед копіюванням/вставкою: визначте тип розгортання

  • DB-only VPS: база може використовувати більшу частину пам’яті, але все одно залиште запас для ОС.
  • App + DB на одному VPS: база має бути скромною, а ліміти з’єднань — суворими.

Також визначте характеристики сховища:

  • SSD-подібне зі стабільним fsync: можна дозволити більш агресивні налаштування flushing.
  • Мережеве/дешеве блочне сховище: очікуйте проблем з fsync; налаштовуйте, щоб уникнути стрибків.

Профіль A: «Додаток + БД разом» (найпоширеніший дешевий VPS стек)

Мета: MySQL 8.0 або MariaDB 10.6+ на 2 ГБ VPS, де також працює сервер додатка. Це профіль для випадків, коли ви краще приглушите продуктивність, ніж викличете відсіювання всього інженерного складу.

cr0x@server:~$ sudo tee /etc/mysql/conf.d/99-vps-2gb-safe.cnf >/dev/null <<'EOF'
[mysqld]
# Core safety limits
max_connections                 = 60
skip_name_resolve               = ON
wait_timeout                    = 60
interactive_timeout             = 300

# InnoDB: keep it small and stable
innodb_buffer_pool_size         = 512M
innodb_buffer_pool_instances    = 1
innodb_log_buffer_size          = 16M
innodb_redo_log_capacity        = 256M
innodb_flush_method             = O_DIRECT
innodb_flush_log_at_trx_commit  = 1
innodb_io_capacity              = 200
innodb_io_capacity_max          = 400

# Reduce per-connection memory blowups
tmp_table_size                  = 32M
max_heap_table_size             = 32M
sort_buffer_size                = 2M
join_buffer_size                = 2M
read_buffer_size                = 1M
read_rnd_buffer_size            = 1M
thread_stack                    = 256K

# Keep table cache reasonable
table_open_cache                = 800
open_files_limit                = 65535

# Observability that pays rent
slow_query_log                  = ON
slow_query_log_file             = /var/log/mysql/slow.log
long_query_time                 = 1
log_queries_not_using_indexes   = OFF
EOF

Чому це працює: buffer pool 512M не виграє бенчмарки, але не задушить ваш додаток. Суворі таймаути зменшують утримання простих з’єднань. Помірні tmp/sort/join буфери обмежують пам’ять на з’єднання. Ви обираєте контрольований збиток.

Профіль B: «Тільки БД, 2 ГБ VPS» (більше простору, але все ще не іграшка)

Якщо на машині працює тільки база (плюс моніторинг), можна дати InnoDB трохи більше ресурсів.

cr0x@server:~$ sudo tee /etc/mysql/conf.d/99-vps-2gb-dbonly.cnf >/dev/null <<'EOF'
[mysqld]
max_connections                 = 120
skip_name_resolve               = ON
wait_timeout                    = 120

innodb_buffer_pool_size         = 1G
innodb_buffer_pool_instances    = 1
innodb_log_buffer_size          = 32M
innodb_redo_log_capacity        = 512M
innodb_flush_method             = O_DIRECT
innodb_flush_log_at_trx_commit  = 1
innodb_io_capacity              = 300
innodb_io_capacity_max          = 600

tmp_table_size                  = 64M
max_heap_table_size             = 64M
sort_buffer_size                = 2M
join_buffer_size                = 2M
read_buffer_size                = 1M
read_rnd_buffer_size            = 1M
thread_stack                    = 256K

table_open_cache                = 1200
open_files_limit                = 65535

slow_query_log                  = ON
slow_query_log_file             = /var/log/mysql/slow.log
long_query_time                 = 0.5
EOF

Жорстке правило: не ставте buffer pool 1.4G на 2 ГБ VPS і не дивуйтесь, коли воно почне свопитись. Ви будете на краю, а край — це місце інцидентів.

Специфічні для MariaDB ручки, що варто розглянути на малих машинах

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

  • Thread pool може зменшити треш потоків при високій кількості з’єднань, покращуючи хвостову латентність. Якщо його вмикаєте, тримайте max_connections реалістичним.
  • Aria та старі движки існують; не використовуйте їх випадково для таблиць з великими записами без розуміння режимів відмов.

Реалії MySQL на малих машинах

  • Дефолти MySQL 8 загалом розумні, але «розумні» передбачають, що ви не запускаєте 200 з’єднань на 2 ГБ.
  • Не гнатесь за видаленими фічами типу query cache. Якщо гайд каже налаштувати його для MySQL 8 — закрийте вкладку.

Про налаштування надійності (аргумент fsync)

innodb_flush_log_at_trx_commit=1 — налаштування за замовчуванням для надійності. На поганому сховищі воно може бути болючим. Встановлення в 2 зменшує частоту fsync і підвищує пропускну здатність, але ви погоджуєтесь втратити до ~1 секунди транзакцій при краші. Якщо ви на одному VPS без реплікації і цінуєте дані — не «оптимізуйте» надійність лише тому, що блог порадив. Якщо навантаження може це терпіти (сесії, кеші, похідні дані) — це бізнес-рішення. Запишіть його.

Жарт №2: Вимикати durability, щоб «покращити продуктивність», — це як зняти пожежний датчик, бо він дратує.

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

Ці завдання призначені для моменту, коли ви дивитесь на 2 ГБ VPS і задаєтеся питанням, чому він повільний, зависає або перезавантажується. Кожне завдання містить: команду, реалістичний вивід, що він означає, і рішення, яке ви приймаєте.

Завдання 1: Підтвердіть, що ви справді встановили (і не налаштовуєте неправильний демон)

cr0x@server:~$ mysql --version
mysql  Ver 8.0.36-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))

Значення: Версія показує, які фічі доступні і які міфи по налаштуванню ігнорувати. У MySQL 8 нема query cache; у MariaDB інші дефолти.

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

Завдання 2: Підтвердіть flavor сервера зсередини SQL

cr0x@server:~$ mysql -e "SELECT VERSION(), @@version_comment;"
+-----------+------------------------------+
| VERSION() | @@version_comment            |
+-----------+------------------------------+
| 8.0.36    | (Ubuntu)                     |
+-----------+------------------------------+

Значення: Це підтверджує, що ви під’єднані до того сервера, який вважаєте за справжній. Звучить очевидно. Перестає бути очевидним під час міграцій, failover і «тимчасових» контейнерів налагодження.

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

Завдання 3: Перевірте RAM, swap та чи ви вже живете ризиковано

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           1.9Gi       1.2Gi       140Mi        42Mi       620Mi       420Mi
Swap:          1.0Gi       180Mi       820Mi

Значення: Низьке «available» разом із використанням swap означає реальний тиск. Свопінг на DB-хості часто корелює зі стрибками латентності та зависаннями.

Рішення: Якщо swap ненульовий і латентність БД погана — зменшіть споживання пам’яті БД (buffer pool, кількість з’єднань, буфери на з’єднання) перед тим, як «оптимізувати запити».

Завдання 4: Доведіть участь OOM killer (або виключіть її)

cr0x@server:~$ sudo dmesg -T | tail -n 20
[Mon Dec 30 09:11:02 2025] Out of memory: Killed process 1421 (mysqld) total-vm:3074824kB, anon-rss:1320440kB, file-rss:0kB, shmem-rss:0kB, UID:110 pgtables:3140kB oom_score_adj:0
[Mon Dec 30 09:11:02 2025] oom_reaper: reaped process 1421 (mysqld), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Значення: Це не «MySQL впав». Це «Linux його вбив». Виправлення — це додаткові ресурси або обмеження пам’яті, а не реіндексація.

Рішення: Встановіть жорсткі межі: зменшіть max_connections, зменшіть буфери на з’єднання і підберіть innodb_buffer_pool_size. Розгляньте додавання swap лише як подушку останньої надії, а не як план.

Завдання 5: Знайдіть, хто прямо зараз поглинає пам’ять

cr0x@server:~$ ps -eo pid,comm,rss,pmem --sort=-rss | head
  PID COMMAND           RSS %MEM
 1421 mysqld        1184500 60.2
  911 php-fpm8.1     210300 10.7
  947 php-fpm8.1     185120  9.4
  612 node            121800  6.1

Значення: RSS показує поточну resident-пам’ять. Це знімок, але він каже, чи БД справді джерело тиску.

Рішення: Якщо mysqld значно найбільший — налаштуйте пам’ять БД. Якщо додаток більше — виправте модель процесів додатка (кількість воркерів, витік пам’яті, кешування).

Завдання 6: Підтвердіть innodb_buffer_pool_size і ключові змінні пам’яті

cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('innodb_buffer_pool_size','max_connections','tmp_table_size','max_heap_table_size','sort_buffer_size','join_buffer_size');"
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 536870912 |
| join_buffer_size        | 2097152   |
| max_connections         | 60        |
| max_heap_table_size     | 33554432  |
| sort_buffer_size        | 2097152   |
| tmp_table_size          | 33554432  |
+-------------------------+-----------+

Значення: Багато змінних мають значення в байтах. Buffer pool на 512MB відповідає Профілю A.

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

Завдання 7: Оціни реальну паралельність з’єднань і чи max_connections — фантазія

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Max_used_connections';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 18    |
+-------------------+-------+
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| Max_used_connections| 54    |
+---------------------+-------+

Значення: Історично ви досягали 54 паралельних з’єднань. З max_connections=60 ви близькі до стелі.

Рішення: Якщо max_used близький до ліміту, або (a) підніміть його трохи й зменшіть буфери на з’єднання, або (b) виправте пулінг у додатку, щоб не потрібно було 60 реальних з’єднань.

Завдання 8: Перевірте переливи тимчасових таблиць (класичний тихий вбивця продуктивності)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';"
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 1842  |
| Created_tmp_files       | 211   |
| Created_tmp_tables      | 9321  |
+-------------------------+-------+

Значення: Тимчасові таблиці на диск відбуваються. Частково це нормально; багато — свідчить про сорти/групування, що не вміщаються в пам’ять або відсутність індексів.

Рішення: Якщо кількість Created_tmp_disk_tables велика відносно Created_tmp_tables — не просто підвищуйте tmp_table_size на 2 ГБ. Виправте запит, додайте індекси або прийміть disk spills і налаштуйте I/O замість цього.

Завдання 9: Перевірте, чи buffer pool надто малий (або якраз той)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+---------+
| Variable_name                         | Value   |
+---------------------------------------+---------+
| Innodb_buffer_pool_read_requests      | 9284432 |
| Innodb_buffer_pool_reads              | 188322  |
+---------------------------------------+---------+

Значення: Reads vs read_requests дає уявлення про cache misses. Деякі промахи очікувані. Дуже високе співвідношення промахів свідчить про замалий buffer pool або про навантаження, що погано кешується.

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

Завдання 10: Швидко знайдіть головні класи очікувань (знімок статусу InnoDB)

cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
2025-12-30 09:22:11 0x7f2d2c1ff700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 6 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 2199 srv_active, 0 srv_shutdown, 12195 srv_idle
srv_master_thread log flush and writes: 14394
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 31212
OS WAIT ARRAY INFO: signal count 30009
RW-shared spins 0, rounds 0, OS waits 12
RW-excl spins 0, rounds 0, OS waits 48
------------
TRANSACTIONS
------------
Trx id counter 1149091
History list length 188

Значення: Це швидка перевірка реальності. Високі OS waits в семафорах можуть вказувати на контеншн; великий history list length — на відставання purge (часто через довгі транзакції).

Рішення: Якщо history list length росте й тримається високим — шукайте довгі транзакції. Якщо semaphore waits стрибають під навантаженням — зменшіть паралельність, налаштуйте запити і подумайте про thread pooling (особливо в MariaDB).

Завдання 11: Знайдіть довгі транзакції, що тримають undo/purge зайнятими

cr0x@server:~$ mysql -e "SELECT trx_id, trx_started, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx ORDER BY trx_started;"
+--------+---------------------+---------------------+----------------------------------+
| trx_id | trx_started         | trx_mysql_thread_id | trx_query                        |
+--------+---------------------+---------------------+----------------------------------+
| 1148802| 2025-12-30 09:04:01 | 312                 | SELECT * FROM orders WHERE ...   |
+--------+---------------------+---------------------+----------------------------------+

Значення: Одна транзакція, що працює з 09:04, може утримувати undo сегменти і заважати purge, впливаючи на записи і призводячи до роздування.

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

Завдання 12: Переконайтеся, що slow query logging увімкнено і корисне

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'long_query_time';"
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| slow_query_log | ON    |
+----------------+-------+
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| long_query_time | 1.000 |
+-----------------+-------+

Значення: Slow logs — ваш бюджетний профайлер. На 2 ГБ VPS ви не маєте права гадати.

Рішення: Якщо slow log вимкнений — увімкніть. Якщо він занадто галасливий — тимчасово підвищіть long_query_time, але не вимикайте лог.

Завдання 13: Читайте slow log як оператор, а не турист

cr0x@server:~$ sudo tail -n 25 /var/log/mysql/slow.log
# Time: 2025-12-30T09:27:14.123456Z
# User@Host: app[app] @ 10.0.0.15 []
# Query_time: 3.214  Lock_time: 0.002 Rows_sent: 50  Rows_examined: 982341
SET timestamp=1735550834;
SELECT * FROM events WHERE user_id=123 ORDER BY created_at DESC LIMIT 50;

Значення: Rows_examined ~ 1M для LIMIT 50 — класичний випадок відсутнього/поганого індексу. Lock_time малий; це не «блокування», а «робота».

Рішення: Додайте/підправте індекс (ймовірно (user_id, created_at)) або перепишіть запит, щоб уникнути сканування. Не чіпайте розміри буферів, щоб «виправити» це.

Завдання 14: Перевірте, що ви випадково не на подвійній кеші (погано з O_DIRECT mismatches)

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'innodb_flush_method';"
+---------------------+----------+
| Variable_name       | Value    |
+---------------------+----------+
| innodb_flush_method | O_DIRECT |
+---------------------+----------+

Значення: O_DIRECT зменшує подвійне буферування між InnoDB buffer pool і сторінковою кеш-пам’яттю ОС. На малій RAM це допомагає робити використання пам’яті передбачуваним.

Рішення: Якщо це не O_DIRECT і у вас є тиск пам’яті — подумайте про зміну (спочатку тестуйте; деякі сторижі поводяться дивно).

Завдання 15: Перевірте відкриті таблиці і тиск на дескриптори файлів

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Open_tables'; SHOW VARIABLES LIKE 'table_open_cache';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Open_tables   | 790   |
+---------------+-------+
+------------------+------+
| Variable_name    | Value|
+------------------+------+
| table_open_cache | 800  |
+------------------+------+

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

Рішення: Якщо у вас ще є дескрипторів файлів і пам’яті в запасі — помірно підніміть table_open_cache. На 2 ГБ не ставте його 10 000 як шведський стіл.

Завдання 16: Перевірте симптоми I/O з боку ОС

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

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.21    0.00    6.44   22.12    0.00   53.23

Device            r/s     w/s   rkB/s   wkB/s  await  %util
vda              42.0    58.0   912.0  2310.0  48.30  92.10

Значення: await ~48ms і %util ~92% вказують, що диск — вузьке місце. Великий iowait підтверджує це.

Рішення: Якщо диск завантажений — припиніть крутити CPU-ручки. Зменшуйте write amplification (індекси, розмір транзакцій), налаштуйте redo capacity розумно і подумайте про апгрейд сховища або перенесення БД з вузла.

Швидкий план діагностики

Це «у вас 15 хвилин, щоб виглядати компетентно» робочий процес. Він упорядкований так, щоб швидко знайти вузьке місце на 2 ГБ VPS.

Перше: чи воно вмирає (OOM, рестарти або свопи)?

  • Перевірте dmesg на OOM killing.
  • Перевірте free -h на активність swap і низьке available.
  • Перевірте, чи mysqld перезапускається (systemd журнали).
cr0x@server:~$ sudo journalctl -u mysql --since "1 hour ago" | tail -n 30
Dec 30 09:11:04 server systemd[1]: mysql.service: Main process exited, code=killed, status=9/KILL
Dec 30 09:11:04 server systemd[1]: mysql.service: Failed with result 'signal'.
Dec 30 09:11:06 server systemd[1]: mysql.service: Scheduled restart job, restart counter is at 3.

Рішення: Якщо є OOM/рестарти — на першому місці поставте обмеження пам’яті та ліміти з’єднань перед будь-якою мікрооптимізацією.

Друге: чи це диск (iowait, fsync-зависи, переливи тимчасових таблиць)?

  • iostat -xz для await/%util.
  • Перевірте переливи тимчасових таблиць на диск.
  • Перевірте тиск на redo/log flush.
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs'; SHOW GLOBAL STATUS LIKE 'Innodb_os_log_written';"
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| Innodb_os_log_fsyncs | 192113 |
+----------------------+--------+
+-----------------------+-----------+
| Variable_name         | Value     |
+-----------------------+-----------+
| Innodb_os_log_written | 987654321 |
+-----------------------+-----------+

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

Третє: чи це CPU/блокування (занадто багато потоків, contention)?

  • top / pidstat для перегляду завантаженості CPU.
  • InnoDB статус на предмет semaphore waits і блокувань рядків.
  • Processlist на предмет багатьох одночасних важких запитів.
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;" | head -n 15
Id	User	Host	db	Command	Time	State	Info
311	app	10.0.0.15:41012	prod	Query	12	Sending data	SELECT * FROM events WHERE user_id=...
312	app	10.0.0.15:41014	prod	Query	520	Updating	UPDATE inventory SET ...
313	app	10.0.0.15:41015	prod	Sleep	58		NULL

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

Три корпоративні міні-історії (бо невдачі вчать)

1) Інцидент через хибне припущення: «max_connections — просто ліміт, не налаштування пам’яті»

Вони тримали невеликий портал клієнтів на 2 ГБ VPS: веб, база, все в одному місці. Під сезонним піком портал не тільки сповільнився. Він почав перезавантажуватися. Команда думала, що то поганий кернел або «шумний сусід» на хості VPS.

Першим їхнім фіксом було класичне: підняти max_connections з 100 до 400, бо клієнти отримували «too many connections». Аутейдж став гіршим. Тепер сайт не тільки падав; він увійшов у цикл перезапусків бази й напівзавершених деплоїв.

Після інциденту ми пройшлися по обліку пам’яті. Кожному з’єднанню дозволяли виділяти кілька буферів. Під спайком з’єднання розгортались, деякі виконували сорти й join-и, кожне потихеньку захоплювало мегабайти. Помножте на сотні — отримаєте пам’ятну скелю. Ядро зробило своє: вбило mysqld.

Нудне, але правильне виправлення: жорстко обмежити з’єднання, примусити пулінг у додатку, зменшити буфери на з’єднання і тримати скромний innodb_buffer_pool_size. Потім, бо люди забувають, додайте алерти на Max_used_connections і використання swap.

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

2) Оптимізація, що вилазить боком: «Зробимо tmp_table_size величезним, щоб позбутися диск-спилів»

Сервіс, що багато генерує звітів, страждав від повільних групувань. Хтось помітив багато Created_tmp_disk_tables і вирішив підняти tmp_table_size і max_heap_table_size драматично. На папері — менше disk temp tables означає менше I/O і швидші запити. Це спокуса.

Зміна працювала в тестах. Потім потрапила в прод. Використання пам’яті під час паралельних звітів пішло вгору. Не лінійно. Раптово. Сервіс не просто уповільнився; він впав. OOM killer став регулярним учасником стендапів.

Тонкий момент: тимчасові таблиці в пам’яті споживають RAM на сесію, а складні запити можуть створювати великі тимчасові структури. При паралельності ця «допомога» перетворюється на непідконтрольну відповідальність. На 2 ГБ VPS «необмежений» — інше слово для «скоро».

Правильний фікс був багаточастинним і трохи дратівливим: додати індекси, переписати найгірші звіти і прийняти, що деякі disk temp tables — нормальне явище. Тримайте tmp_table_size скромним, аби режим відмови був «повільний звіт», а не «мертва база».

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

3) Нудно, але правильно, що врятувало день: «Slow query log + один індекс за інцидент»

Інша команда працювала на MariaDB з жорстким бюджетом 2 ГБ. Нічого особливого. У них була одна практика, що виглядала надто базовою: slow query logging завжди увімкнений, ротований і переглядається після будь-якого інциденту.

Коли вийшов новий фіч-реліз, p95 латентності подвоївся. Вони не почали з зміни buffer pools або flush. Вони витягли slow log і знайшли запит, що переглядав занадто багато рядків для простого дашборда користувача. Це не було навмисно — просто бракувало композитного індексу.

Вони додали індекс, запустили, і латентність повернулась до нормального рівня. Без страхітливих змін конфігурації. Без «тимчасового» зменшення durability. Без тижневої оптимізаційної роботи. Просто дисциплінований фідбек-цикл.

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

Якщо ви працюєте на малому залізі, нудна спостережливість — це не наклад. Це платня за житло.

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

1) Симптом: mysqld вбивають, він рестартує або зникає

Корінь: OOM killer через необмежену пам’ять на з’єднання, надмірний buffer pool або конкуренція додатка і БД за RAM.

Виправлення: Зменшити max_connections, зменшити буфери на з’єднання (sort/join/read), підібрати innodb_buffer_pool_size і переконатися, що додаток використовує пулінг. Підтвердіть через dmesg.

2) Симптом: помилки «Too many connections» під час спайків

Корінь: churn з’єднань і відсутність пулінгу; або max_connections занадто малий для пікової паралельності.

Виправлення: Впровадьте пулінг (на рівні додатка або проксі), скоротіть wait_timeout для очищення неактивних з’єднань і підніміть max_connections тільки якщо одночасно обмежите пам’ять на з’єднання.

3) Симптом: випадкові стрибки латентності, особливо на записах

Корінь: диск стає вузьким (fsync, checkpointing, дешеве сховище), або фонове очищення конфліктує з foreground-запитами.

Виправлення: Перевірте iostat. Налаштуйте redo capacity помірно, зберігайте durability, якщо не готові втратити дані, і зменшуйте write amplification (індекси, батчі).

4) Симптом: CPU високий, load average росте, але запити «не такі вже й великі»

Корінь: занадто багато runnable-потоків через надмірну паралельність; неефективні запити; відсутні індекси, що призводять до сканів.

Виправлення: Зменшіть паралельність (з’єднання), використовуйте slow logs, додавайте індекси і розгляньте thread pool (особливо в MariaDB), щоб зменшити треш планувальника.

5) Симптом: реплікаційний лаг (якщо у вас репліка)

Корінь: I/O репліки не витримує (диск), або SQL-потік заблокований довгими транзакціями або важкими запитами.

Виправлення: Перевірте стан реплікації, зменшіть write load і оптимізуйте найповільніші запити. На 2 ГБ запускати реплікацію плюс додаток плюс БД — амбіційно; будьте чесні щодо ресурсів.

6) Симптом: тимчасові таблиці «таємничо» заповнюють диск

Корінь: великі сорти/групування, що виходять на диск; tmpdir на маленькому файловому розділі; недостатні індекси.

Виправлення: Помістіть tmpdir на файлову систему з місцем, але в першу чергу виправляйте запити/індекси. Не просто підвищуйте tmp_table_size на 2 ГБ.

7) Симптом: стан processlist «Sending data» домінує

Корінь: таблиці/індекси скануються і важке рядкове опрацювання; іноді поганий порядок join-ів.

Виправлення: Використовуйте slow log + EXPLAIN, додайте потрібні індекси, зменште кількість вибраних стовпців і впровадьте пагінацію. Конфіг не врятує вас від сканування мільйонів рядків заради забави.

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

Покроково: стабілізуйте спочатку (уникніть крашів), потім оптимізуйте

  1. Підтвердіть версію і движок: MySQL vs MariaDB, точна версія і що таблиці InnoDB.
  2. Встановіть межі з’єднань: оберіть реалістичний max_connections (60–120 на 2 ГБ залежно від додатка та вартості запиту).
  3. Обмежте пам’ять на з’єднання: тримайте sort/join буфери скромними; не «допомагайте» кожному запиту величезними буферами.
  4. Підібрати buffer pool: 512M для app+DB, ≈1G для DB-only як старт.
  5. Увімкніть slow query logging: завжди, з ротацією логів.
  6. Переконайтесь, що не свопите: своп — не кеш бази даних, це податок на продуктивність.
  7. Виміряйте латентність диска: використовуйте iostat; якщо await поганий — прийміть, що ви обмежені сховищем.
  8. Виправте топ-3 запити: додайте індекси, перепишіть, зменшіть сканування рядків. Не починайте з 40 ручок конфігу.
  9. Налаштуйте алерти: використання swap, Max_used_connections близько до ліміту, %util диска і зміни обсягів slow queries.
  10. Перетестуйте під навантаженням: стейджинг або контрольований прогін у проді. Слідкуйте за пам’яттю і диском.

Санітні ліміти для 2 ГБ VPS (правила великої руки)

  • max_connections: 40–80, якщо app+DB; 80–150, якщо DB-only і навантаження на одне з’єднання легке.
  • innodb_buffer_pool_size: 512M (спільний) до 1G (DB-only). Рідко більше.
  • tmp_table_size / max_heap_table_size: 32M–64M. Більше — пастка при паралельності.
  • sort/join buffers: 1M–4M максимум у більшості випадків на малій RAM. Великі значення для специфічних навантажень з низькою паралельністю.
  • Durability: тримайте innodb_flush_log_at_trx_commit=1, якщо не маєте явної толерантності до втрати даних.

Операційна гігієна, що запобігає «таємній повільності»

  • Ротуйте slow logs і error logs, щоб диск не заповнився.
  • Тримайте таблиці і індекси в порядку; уникайте невикористовуваних індексів, що підсилюють записи.
  • Запускайте ANALYZE TABLE, коли плани запитів дивні після великих змін даних (акуратно, у непіковий час).
  • Плануйте оновлення; раптові великі апгрейди на 2 ГБ VPS — це шлях зустрічі з найгіршим з оптимізатора.

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

1) Чи MariaDB швидша за MySQL на 2 ГБ VPS?

Іноді, для специфічних навантажень, але домінуючий фактор на 2 ГБ зазвичай — тиск пам’яті і латентність диска. Обирайте двигун, який вам простіше експлуатувати, а потім виправляйте запити.

2) Яка найважливіша ручка, щоб уникнути крашів?

max_connections, бо він опосередковано обмежує експлозію пам’яті на з’єднання. За ним — стримані буфери на з’єднання.

3) Який розмір innodb_buffer_pool_size на 2 ГБ?

Почніть з 512M, якщо на машині також працює додаток. Якщо це лише БД — стартуйте з ≈1G. Збільшуйте тільки якщо не свопите і кеш-промахи реалістично шкодять.

4) Чи варто вмикати swap на VPS для бази даних?

Своп може врятувати від негайної смерті OOM, але також може створити довгі латентні паузи. Якщо використовуєте своп — вважайте його аварійною подушкою та тримайте пам’ять БД консервативною. Якщо своп стає звичним — ви недопроvisioned або неправильно налаштовані.

5) Чи прийнятно innodb_flush_log_at_trx_commit=2?

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

6) Чому не робити tmp_table_size величезним, щоб уникнути диск тимчасових таблиць?

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

7) Чи варто запускати додаток і базу даних разом на 2 ГБ?

Можна, але треба суворо дотримуватись правил: обмежити пам’ять БД, капати з’єднання і зменшити кількість воркерів у додатку. Якщо додаток росте — розділяйте раніше; вертикальне масштабування на одній маленькій машині має різкі краї.

8) Як швидко зрозуміти, CPU-bound я чи disk-bound?

Використовуйте iostat -xz. Високе %iowait, велике await і високе %util вказують на диск. Якщо iowait низький, а CPU завантажений — ви CPU/запит/паралельність bound.

9) Чи потрібно налаштовувати table_open_cache на 2 ГБ?

Лише якщо бачите churn (Open_tables близько до ліміту кеша і проблеми з метаданою). Тримайте його помірним; великі кеші споживають пам’ять і дескриптори файлів.

10) Якщо можу виправити лише одну річ в додатку — що це?

Пулінг з’єднань і адекватні таймаути. Це зменшить потребу в високих max_connections і значно знизить волатильність пам’яті.

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

Якщо ви хочете, щоб база на 2 ГБ VPS не падала, перестаньте думати «про найкращу продуктивність» і почніть думати «про обмежену відмову». Обмежте з’єднання. Тримайте буфери на з’єднання маленькими. Дайте InnoDB buffer pool, що відповідає реальності, а не его. Потім використовуйте slow query log, щоб заробити реальні покращення продуктивності чесним шляхом: роблячи менше роботи.

Зробіть це далі, по порядку

  1. Оберіть Профіль A (спільний) або Профіль B (тільки БД) і застосуйте його чисто; перезапустіть сервіс.
  2. Запустіть швидкі діагностики: free, dmesg, iostat, Threads_connected/Max_used_connections.
  3. Перегляньте slow log на найгірші запити; виправте найгірший індексом або переписом.
  4. Встановіть алерти на використання swap і Max_used_connections, що наближається до вашого ліміту.
  5. Якщо все ще досягаєте лімітів: розділіть додаток і БД або апгрейдніть RAM/сховище. На 2 ГБ «масштабування вгору» часто найдешевше оптимізаційне рішення.

MySQL і MariaDB можуть добре поводитися на 2 ГБ VPS. Фокус не в пошуку ідеальних налаштувань. Він у відмові дозволяти одному налаштуванню зробити відмову необмеженою.

← Попередня
Налаштування параметрів ядра Ubuntu 24.04: 5 sysctl, що важливі (і 10, що ні) (випадок #42)
Наступна →
MariaDB проти RDS MariaDB: у кого менше дивних сюрпризів сумісності?

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