У вас є VPS з 4 ГБ ОЗП. Кілька сайтів. База даних. І раптом — пейдж, тікет або лист від клієнта: «Сайт повільний». Нічого не принижує так, як спостереження за коробкою за $10/місяць, яка намагається поводитися як корпоративна платформа, бо хтось увімкнув плагін, що «виконує лише один запит».
Це польовий гід для того, щоб зробити MySQL або PostgreSQL стабільними й достатньо швидкими для навантаження сайтів на маленькому VPS‑обладнанні. Не бенчмарк‑фантазія. Не скидання конфігів. Те, що ви налаштовуєте насамперед, що вимірюєте першим і що припиняєте робити, перш ніж це забере у вас вихідні.
Перше рішення: MySQL чи PostgreSQL для сайтів на 4 ГБ
На VPS з 4 ГБ «найкраща база» — та, яку ви вмієте тримати прогнозованою під тиском пам’яті і спалахоподібним трафіком. Ваш ворог — не теоретична пропускна здатність. Це шторм свопінгу, навали підключень і сплески затримки сховища, які перетворюють «достатньо швидко» на «чому чекає оформлення замовлення?»
Виберіть MySQL (InnoDB), коли:
- Ваш стек уже натівний до MySQL (WordPress, Magento, багато PHP‑додатків) і ви не хочете бути тим, хто переписує все «для забави».
- Вам потрібна відносно проста історія кешування: InnoDB buffer pool — це великий регулятор, і він поводиться як великий регулятор.
- Потрібна реплікація, яку легко експлуатувати звичними інструментами, і ви готові до компромісів eventual consistency у деяких режимах.
Виберіть PostgreSQL, коли:
- Вам важлива коректність запитів і багаті можливості SQL (справжні window‑функції, CTE, кращі обмеження і типи даних), і ви дійсно будете їх використовувати.
- Хочете передбачувані плани виконання, хорошу спостережуваність і розумні значення за замовчуванням для сучасних шаблонів додатків.
- Готові впровадити пулінг підключень (pgBouncer), бо модель процес‑на‑підключення PostgreSQL карає «просто відкрий більше підключень» на малих машинах.
Якщо це переважно CMS‑трафік з плагінами, якими ви не керуєте, я зазвичай консервативний: залишайтеся з MySQL, якщо додаток вже MySQL‑орієнтований. Якщо будуєте нову систему з командою, що пише SQL усвідомлено, PostgreSQL часто кращий довгостроковий вибір. Але на 4 ГБ короткостроковою перемогою буде оперативна простота, а не філософська чистота.
Правило великого пальця: якщо ви не можете описати свої топ‑5 запитів і їх індекси, ви не «вибираєте базу даних», ви обираєте, які режими відмов хочете пережити першими.
Цікавi факти та історичний контекст (що реально впливає на рішення)
- Рання домінація MySQL у вебі зумовлена поширенням LAMP і «достатньою» швидкістю для сайтів із великою кількістю читань. Саме тому багато веб‑додатків досі розраховують на діалектні особливості MySQL.
- InnoDB став дефолтом у MySQL 5.5 (ера близько 2010). Якщо ви все ще мислите в термінах MyISAM (блокування таблиць, відсутність відновлення після краху), ви носите в кишені викопний артефакт.
- MVCC‑модель PostgreSQL — одна з причин, чому він тримає консистентність при конкуренції, але вона створює постійну потребу у vacuum. Ігнорувати vacuum — не означає, що база буде кричати; вона просто поступово деградує.
- Postgres з часом став дружнішим до паралельності (паралельні запити, кращий планувальник). На маленькому VPS це менш критично, ніж на «важкому залізі», але це частина того, чому Postgres «відчувається сучасним» для аналітичних запитів.
- Query cache у MySQL був видалений у MySQL 8.0 через погану масштабованість під конкуренцією. Якщо хтось радить «увімкнути query_cache_size», ви знайшли мандрівника в часі.
- Postgres отримав кредит за стандарти й коректність, бо історично пріоритет віддавався фічам і цілісності над ранньою сирою швидкістю. Сьогодні він теж швидкий, але культурна ДНК видно в дефолтах і інструментах.
- Обидва рушії консервативні щодо надійності за замовчуванням (fsync, WAL/redo). Вимикання налаштувань цілісності робить бенчмарки героїчними, а постмортеми — кримінальними сценами.
- MariaDB відходить від MySQL у значних деталях. Поради з «тонкого налаштування MySQL» іноді погано мапляться на версії MariaDB і її сховища. Перевіряйте, що саме ви запускаєте.
- RDS і керовані сервіси формували фольклор налаштувань: люди копіюють хмарові дефолти на VPS, а потім дивуються, чому 4 ГБ коробка «тоне».
Базова архітектура для VPS з 4 ГБ (і чому це важливо)
На VPS з 4 ГБ у вас немає «зайвої пам’яті». У вас є бюджет. Витрачайте його на кеші, які зменшують I/O, і на резерв пам’яті, щоб уникнути свопінгу. Кеш сторінок ОС також важливий, бо і MySQL, і PostgreSQL врешті читають із файлової системи, і ядро не ваш ворог; воно — останній рубіж захисту.
Реалістичний бюджет пам’яті
- OS + SSH + базові демони: 300–600MB
- Вебсервер + PHP‑FPM: дуже варіюється. Від кількох сотень МБ до кількох ГБ залежно від кількості процесів і поведінки додатка.
- База даних: те, що залишилося, але не все відразу. Якщо віддати БД усе, веб‑шар OOMить або почне свопити під час сплесків трафіку.
Для «сайтів на одному VPS» база даних не ізольована. Це один із небагатьох випадків, коли «налаштував і забув» — не лінь, а виживання.
Думка: Якщо ви запускаєте і веб, і БД на одному VPS з 4 ГБ, плануйте виділити приблизно 1.5–2.5GB для кеш‑рівня бази даних максимум, якщо тільки ви не виміряли споживання пам’яті PHP під навантаженням і воно дійсно мале. Мета — стабільна латентність, а не героїчний розмір buffer pool.
Жарт №1: VPS з 4 ГБ — як студія: технічно вміститься бігова доріжка, але ви ненавидітимете життя, і сусіди теж.
План швидкої діагностики: знайти вузьке місце за 10 хвилин
Ось порядок перевірки, коли «сайт повільний» і підозрюється база даних. Кожен крок підкаже, дивитися на CPU, пам’ять, підключення, блокування чи сховище.
Перше: чи голодна коробка (CPU, RAM, swap)?
- Перевірте load у порівнянні з кількістю CPU.
- Перевірте активність swap і великі page fault‑и.
- Перевірте історію OOM killer.
Друге: чи це затримка сховища (IOPS/fsync/WAL/redo)?
- Високий iowait, повільні fsync, довгі commit‑и або завислі checkpoints.
- Шукайте глибину черги і середні times await.
Третє: чи це тиск підключень?
- Надто багато підключень до БД або потоків.
- Штурм підключень від PHP‑воркерів.
- Кількість потоків/процесів доходить до меж пам’яті.
Четверте: чи це блокування або довгі транзакції?
- MySQL: metadata locks, InnoDB row locks, довгі транзакції.
- Postgres: заблоковані запити, idle‑in‑transaction сесії, vacuum, заблокований старими snapshot‑ами.
П’яте: чи це «погані запити + відсутні індекси»?
- Slow query log / pg_stat_statements показують головних порушників.
- Шукайте повні сканування таблиць і «filesort»/тимчасові таблиці або sequential scans з великим числом рядків.
Ось і все. Не починайте з хаотичних змін регуляторів. Не копіюйте «high performance my.cnf» з 64GB сервера. Вимірюйте, а потім зробіть одну зміну, яку можете пояснити.
Цитата (парафраз ідеї): Ідея Джона Оллспо: продукція — це місце, де припущення вмирають, тому проєктуйте та експлуатуйте для навчання, а не для впевненості.
Практичні завдання: команди, виводи та подальші дії
Це реальні завдання, які ви можете виконати на Linux VPS. Кожне включає: команду, що типовий вивід означає і яке рішення ви приймаєте. Виконуйте їх по порядку під час триажу або встановлення базових налаштувань.
Завдання 1: Підтвердити базовий тиск системи (CPU, RAM, swap)
cr0x@server:~$ uptime
14:22:19 up 36 days, 3:18, 1 user, load average: 5.84, 5.12, 3.90
Що це означає: На VPS з 2 vCPU середні навантаження вище ~2–3 протягом тривалого часу часто означають чергу runnable (CPU) або незнімні очікування I/O.
Рішення: Якщо load високий, негайно перевірте iowait і пам’ять/swap перед тим, як чіпати конфіги бази даних.
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 3.8Gi 3.3Gi 120Mi 90Mi 420Mi 220Mi
Swap: 1.0Gi 860Mi 164Mi
Що це означає: Використання swap з низьким «available» — тривожний знак для затребуваних за латентністю веб‑навантажень.
Рішення: Якщо swap використовується в піку, зменшіть пам’ять бази даних і/або зменшіть кількість PHP‑FPM воркерів. Розгляньте ввімкнення невеликого swap як подушку від краху, а не як шар для продуктивності.
Завдання 2: Перевірити активний свопінг і iowait
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 1 880000 120000 28000 380000 12 45 210 480 520 900 18 8 40 34 0
3 0 880000 118000 28000 381000 0 60 160 510 600 1100 20 9 35 36 0
1 2 880000 115000 28000 379000 20 0 400 200 480 850 15 7 38 40 0
4 0 880000 119000 28000 382000 0 8 90 520 610 1200 22 10 32 36 0
2 1 880000 117000 28000 381500 0 35 200 600 590 1050 19 9 34 38 0
Що це означає: Ненульові si/so вказують на свопінг. Високий wa — це I/O wait.
Рішення: Якщо свопінг активний, припиніть «тонке» налаштування запитів і вирішіть питання пам’яті/кількості підключень. Якщо iowait високий — зосередьтесь на затримці сховища, поведінці checkpoints/flush і зменшенні write amplification.
Завдання 3: Швидко ідентифікувати затримки диска і черги
cr0x@server:~$ iostat -xz 1 3
avg-cpu: %user %nice %system %iowait %steal %idle
18.20 0.00 8.10 34.50 0.00 39.20
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util await aqu-sz
vda 12.0 48.0 900.0 6200.0 0.2 3.1 92.0 21.5 1.8
Що це означає: %util близько 100% і await > 10–20ms на диску VPS зазвичай означають, що сховище — вузьке місце.
Рішення: Зменшити запис (батчування, налаштування autovacuum, InnoDB flush), перенести базу на краще сховище або розділити БД і веб. У деяких планах VPS єдиний «тюнінг», що має значення — платити за більший IOPS.
Завдання 4: Підтвердити, яку СУБД ви запускаєте і її версію
cr0x@server:~$ mysql --version
mysql Ver 8.0.36-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))
Що це означає: Великі версії змінюють дефолти і прибирають регулятори (наприклад query cache). Версія визначає, які поради застосовні.
Рішення: Не застосовуйте рецепти налаштувань, якщо вони не відповідають вашій основній версії і флейвору (MySQL vs MariaDB).
cr0x@server:~$ psql --version
psql (PostgreSQL) 16.1 (Ubuntu 16.1-1.pgdg22.04+1)
Що це означає: Новіші версії Postgres покращують vacuum, WAL і планувальник. Це змінює те, що «болить» на малих коробках.
Рішення: На старому Postgres доведеться більше вручну приглядати. На новому — зосередьтеся на пулінгу підключень і порогах autovacuum.
Завдання 5: Порахуйте підключення до DB (MySQL)
cr0x@server:~$ mysql -e "SHOW STATUS LIKE 'Threads_connected';"
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 185 |
+-------------------+-------+
Що це означає: 185 підключень на VPS з 4 ГБ у режимі PHP часто проблема, навіть до того як запити стануть повільними.
Рішення: Обмежте конкурентність додатка, обережно використовуйте постійні підключення або перейдіть на модель, що обмежує конкуренцію (черги в додатку, кешування або розподіл читань). Якщо не можете контролювати додаток — зменшіть max_connections і прийміть контрольовані відмови замість загального колапсу.
Завдання 6: Порахуйте підключення до DB (PostgreSQL)
cr0x@server:~$ sudo -u postgres psql -c "SELECT count(*) AS connections FROM pg_stat_activity;"
connections
-------------
142
(1 row)
Що це означає: 142 сесії Postgres = 142 бекенд‑процеси. На VPS з 4 ГБ це податок по пам’яті та контекстним перемиканням.
Рішення: Встановіть pgBouncer і зменшіть max_connections. На малих машинах Postgres без пулінгу — це жарт щодо продуктивності, який ви граєте на собі.
Завдання 7: Знайти довгі запити і блокувальники (PostgreSQL)
cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, now()-query_start AS age, state, wait_event_type, wait_event, left(query,80) AS q FROM pg_stat_activity WHERE state <> 'idle' ORDER BY age DESC LIMIT 5;"
pid | age | state | wait_event_type | wait_event | q
------+----------+--------+-----------------+------------+--------------------------------------------------------------------------------
9123 | 00:02:18 | active | Lock | relation | UPDATE orders SET status='paid' WHERE id=$1
9051 | 00:01:44 | active | IO | DataFileRead | SELECT * FROM products WHERE slug=$1
(2 rows)
Що це означає: Очікування на блокування вказує на контенцію; очікування на I/O — на повільне сховище або пропуски кеша.
Рішення: Якщо домінують lock‑waits — виправляйте обсяг транзакцій і індекси. Якщо IO‑waits — збільшуйте ефективний кеш (в міру розумного) і зменшуйте випадкові читання через індекси та форму запитів.
Завдання 8: Знайти очікування блокувань (MySQL)
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;"
Id User Host db Command Time State Info
210 app 10.0.0.12:50344 shop Query 75 Waiting for table metadata lock ALTER TABLE orders ADD COLUMN foo INT
238 app 10.0.0.15:38822 shop Query 12 Sending data SELECT * FROM orders WHERE created_at > NOW() - INTERVAL 1 DAY
Що це означає: Metadata locks можуть заморозити запис і читання за схемних змін, залежно від операції й версії.
Рішення: Не виконувати онлайн‑зміни схеми необачно на одному невеликому VPS. Плануйте техобслуговування або використовуйте інструменти для онлайн‑міграцій, що зменшують блокування.
Завдання 9: Перевірити Innodb buffer pool hit rate і тиск на читання
cr0x@server:~$ mysql -e "SHOW STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+---------+
| Variable_name | Value |
+---------------------------------------+---------+
| Innodb_buffer_pool_read_requests | 9823412 |
| Innodb_buffer_pool_reads | 412390 |
+---------------------------------------+---------+
Що це означає: reads — це фізичні читання; read_requests — логічні. Якщо фізичних читань багато відносно запитів, у вас пропуски кеша.
Рішення: Якщо робочий набір поміщається в RAM, обережно збільшуйте innodb_buffer_pool_size. Якщо не вміщається — пріоритет індексів і зменшення робочого набору (менше колонок, менше сканувань).
Завдання 10: Перевірити кеш Postgres і тимчасові файли
cr0x@server:~$ sudo -u postgres psql -c "SELECT datname, blks_hit, blks_read, temp_files, temp_bytes FROM pg_stat_database ORDER BY temp_bytes DESC LIMIT 5;"
datname | blks_hit | blks_read | temp_files | temp_bytes
-----------+----------+-----------+------------+--------------
appdb | 9201123 | 612332 | 1832 | 2147483648
(1 row)
Що це означає: Багато temp_bytes вказує, що сорти/хеші вивалюються на диск, бо work_mem замалий для цих операцій — або запити роблять забагато роботи.
Рішення: Не підвищуйте work_mem глобально на маленькому VPS. Виправляйте запити й індекси спочатку; потім підвищуйте work_mem для конкретних ролей або сеансів для спеціфічних навантажень.
Завдання 11: Подивитися топ‑запити (Postgres, якщо pg_stat_statements увімкнено)
cr0x@server:~$ sudo -u postgres psql -c "SELECT calls, mean_exec_time, rows, left(query,80) AS q FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"
calls | mean_exec_time | rows | q
-------+----------------+------+--------------------------------------------------------------------------------
82021 | 12.45 | 1 | SELECT id FROM sessions WHERE token=$1
1220 | 210.12 | 300 | SELECT * FROM orders WHERE user_id=$1 ORDER BY created_at DESC LIMIT 50
(2 rows)
Що це означає: Запити з високим загальним часом — це ваші «бюджетні пожирачі». Запити з великою кількістю викликів — «смерть тисячею порізів».
Рішення: Індексуйте «гарячі» шляхи і зменшуйте багаточисельні запити. Якщо запит виконується 80k разів і займає 12ms — це витрата одного повного ядра процесора.
Завдання 12: Увімкнути і читати slow query log у MySQL швидко
cr0x@server:~$ mysql -e "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=0.5; SET GLOBAL log_queries_not_using_indexes=ON;"
...output omitted...
Що це означає: Ви вмикаєте збір доказів. Тримайте пороги розумними, щоб не DOS‑нути себе логами.
Рішення: Збирайте 15–60 хвилин під час піку, потім аналізуйте й виправляйте найгірших винуватців. Вимикайте log_queries_not_using_indexes, якщо воно надто шумить для вашого додатка.
cr0x@server:~$ sudo tail -n 5 /var/log/mysql/mysql-slow.log
# Query_time: 1.204 Lock_time: 0.000 Rows_sent: 50 Rows_examined: 84512
SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 50;
Що це означає: Rows examined значно більше, ніж rows sent: класичний відсутній індекс або неправильний порядок індексів.
Рішення: Додайте/підлаштуйте складні індекси під патерн фільтрації + сортування (наприклад (user_id, created_at)), потім перевірте з EXPLAIN.
Завдання 13: Перевірити місце на файловій системі та інодинаміку
cr0x@server:~$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 80G 74G 2.1G 98% /
Що це означає: Майже повні диски руйнують продуктивність і можуть порушити записи в БД. Postgres може відмовлятися відчекпоінтитись; MySQL може впасти або перейти в режим тільки для читання залежно від обставин.
Рішення: Звільніть місце негайно. Потім налаштуйте ротацію логів і моніторинг із тривогою на 80–85%.
Завдання 14: Перевірити пам’ять процесів ядра і БД
cr0x@server:~$ ps -eo pid,comm,rss,pmem --sort=-rss | head
2311 mysqld 1854320 46.2
1822 php-fpm8.1 412000 10.2
1825 php-fpm8.1 405000 10.1
911 postgres 220000 5.4
Що це означає: RSS показує фактичну резидентну пам’ять. Кілька великих PHP‑воркерів плюс великий DB‑кеш можуть звести коробку в своп.
Рішення: Якщо БД + PHP вже споживають більшість RAM, припиніть збільшувати DB‑буфери. Зменшіть конкурентність і обмежте споживачів пам’яті.
Якщо ви обрали MySQL: що налаштувати насамперед на VPS з 4 ГБ
MySQL на малому VPS зазвичай працює добре, якщо ви не ставите його поводитися як бездонна яма для підключень і пам’яті. InnoDB — ваш дефолтний рушій; налаштовуйте під InnoDB, не за ностальгією.
1) Встановіть innodb_buffer_pool_size по‑дорослому
Мета: Кешувати «гарячі» дані/індекси, зменшити випадкові читання, не відбирати пам’ять у всіх інших.
- Якщо БД на тій же коробці, що й веб: починайте з близько 1.0–1.5GB.
- Якщо БД здебільшого сама: до 2.0–2.5GB може бути прийнятно.
Режим відмови: Перенасичення buffer pool не «використовує вільну пам’ять». Воно конкурує з кешем ОС і веб‑шаром. Потім ви свопите. А потім кожен запит стає бенчмарком сховища.
2) Встановіть max_connections нижче, ніж здається
Потоки MySQL споживають пам’ять. PHP‑додатки люблять відкривати підключення, ніби це безкоштовно. Ні, не безкоштовно.
- Почніть з приблизно 100–200 залежно від додатка та латентності запитів.
- Якщо бачите 300–800 підключень — це не «проблема продуктивності бази даних». Це проблема контролю конкуренції.
3) Зберігайте поведінку redo log і flush розумною
На малому VPS з невпевненим сховищем агресивні flush можуть спричиняти сплески. Але відключати надійність — це шлях до оновлення резюме.
innodb_flush_log_at_trx_commit=1для реальної надійності (за замовчуванням).- Якщо зовсім немає вибору і ви готові втратити до 1 секунди транзакцій при краху: розгляньте
=2. Задокументуйте і додайте в інцидент‑рукбуки. Не прикидайтеся, що це безкоштовно.
4) Вимикайте те, що не потрібно, але не сліпо
Performance Schema корисна; вона також має накладні витрати. На крихітному VPS можна зменшити інструментацію, а не видаляти її повністю.
- Якщо постійно CPU‑зв’язане з низькою латентністю запитів, подумайте про обрізання Performance Schema споживачів.
- Але зберігайте достатню видимість, щоб ловити регресії. Діагностика без метрик — це творче письмо.
5) Розумно встановлюйте ліміти для тимчасових таблиць
Веб‑додатки люблять ORDER BY і GROUP BY, часто з надто широкими наборами результатів.
tmp_table_sizeіmax_heap_table_sizeможуть зменшити дискові тимчасові таблиці, але якщо встановити їх занадто високо — ви «вибухнете» пам’яттю під конкуренцією.
Ескіз початкової конфігурації MySQL (не догма)
Дух цього конфігу для змішаного веб+БД 4 ГБ VPS. Налаштовуйте згідно вимірювань вище.
cr0x@server:~$ sudo cat /etc/mysql/mysql.conf.d/99-vps-tuning.cnf
[mysqld]
innodb_buffer_pool_size = 1G
innodb_buffer_pool_instances = 1
max_connections = 150
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
slow_query_log = ON
long_query_time = 0.5
Що це означає: Менший buffer pool для збереження запасу пам’яті, обмежені підключення, direct I/O щоб зменшити подвійне кешування (залежить від вашої ФС і навантаження) і логування повільних запитів для збору доказів.
Рішення: Застосуйте, рестартуйте в тихий інтервал, потім повторно перевірте swap/iowait і slow‑логи. Якщо латентність покращилась і своп зник — вірний напрям.
Якщо ви обрали PostgreSQL: що налаштувати насамперед на VPS з 4 ГБ
Postgres відмінний для сайтів, але змусить вас звернути увагу на три речі відразу: кількість підключень, vacuum і WAL/checkpoints. Ігнор будь‑якої з них — і ви отримаєте «випадкові» гальмування, що насправді не випадкові.
1) Встановіть пулінг підключень (pgBouncer) раніше, ніж «потрібно»
На 4 ГБ бекенди Postgres не є диспо́забельними. Сплеск трафіку, що відкриває сотні підключень, може перетворитися на тиск пам’яті та контекстні перемикання.
Робіть: запустіть pgBouncer у transaction pooling для типової веб‑роботи.
Не робіть: піднімайте max_connections до 500 і називайте це масштабуванням.
2) Встановіть shared_buffers консервативно
Фольклор каже «25% RAM». На змішаному веб+БД VPS я б починав з:
- 512MB — 1GB для
shared_buffers.
Postgres також виграє від кеша ОС. Віддавати все shared_buffers може задушити ОС і інші процеси.
3) Встановіть work_mem низьким глобально; підвищуйте вибірково
work_mem — на сортування/хеш операцію, на запит, на бекенд. На малому VPS не варто хизуватися.
- Почніть з 4–16MB глобально залежно від конкуренції.
- Підвищуйте для конкретної ролі або сеансу, якщо маєте відомий важкий звітний запит.
4) Підтримуйте autovacuum здоровим
Autovacuum — не опціональне прибирання. Це спосіб Postgres уникати bloat і зберігати можливість index‑only scans.
- Моніторте dead tuples і відставання vacuum.
- Налаштовуйте autovacuum пороги для «гарячих» таблиць при потребі.
5) Зробіть checkpoints менш «піковими»
На повільному VPS‑сховищі spike‑и чекпоінтів виглядають як раптові стрибки латентності. Рівномірніші чекпоінти зменшують біль.
- Збільшіть
checkpoint_timeout(в межах розумного). - Встановіть
checkpoint_completion_targetвисоким, щоб розподілити записи.
Ескіз початкової конфігурації Postgres
cr0x@server:~$ sudo cat /etc/postgresql/16/main/conf.d/99-vps-tuning.conf
shared_buffers = 768MB
effective_cache_size = 2304MB
work_mem = 8MB
maintenance_work_mem = 128MB
checkpoint_completion_target = 0.9
checkpoint_timeout = 10min
wal_compression = on
log_min_duration_statement = 500ms
Що це означає: Консервативні shared_buffers, реалістичне хинтування кеша, помірний work_mem, згладжені чекпоінти і логування повільних запитів.
Рішення: Застосуйте і перезавантажте/перезавантажте служби, потім спостерігайте за ростом тимчасових файлів і таймінгом чекпоінтів. Якщо диск повільний, згладжування чекпоінтів проявиться як менше раптових стрибків латентності.
Підключення: мовчазний вбивця на малих машинах
Якщо ви запускаєте сайти, найпростіший спосіб зіпсувати базу даних — дозволити додатку самим вирішувати конкуренцію. PHP‑FPM воркери + «відкривай підключення в кожному запиті» — це гуркітний рій. На 4 ГБ виживають не через швидкість. Ви виживаєте через спокій.
Як виглядає «занадто багато підключень»
- CPU бази даних високий, але він не виконує корисної роботи (контекстні перемикання, контенція mutex).
- Споживання пам’яті зростає з трафіком до свопу.
- Латентність зростає навіть для простих запитів.
Що робити натомість
- Обмежити конкурентність додатка: менше PHP‑FPM дітей або налаштування менеджера процесів, щоб уникнути спалахів.
- Використовувати пулінг: pgBouncer для Postgres; для MySQL розгляньте пулінг на рівні додатка або налаштування persistent connections розумно.
- Fail fast: іноді нижчий
max_connections— правильний вибір, бо він захищає коробку від повного thrash.
Жарт №2: «Необмежені підключення» — як необмежений шведський стіл з креветками: звучить круто, поки ви не закриваєте ресторан.
Зберігання та реалії файлової системи: IOPS, fsync і чому «швидкий SSD» бреше
На VPS‑платформах «SSD» може означати все: від поважного NVMe до спільного мережевого блоку, що має поганий день. Бази даних більше хвилює затримка, ніж пропускна здатність. Кілька мілісекунд додаткового fsync на комітті стають помітними на сайті.
Як записи б’ють по вас по‑різному в MySQL та PostgreSQL
- MySQL/InnoDB: redo‑логування + doublewrite buffer (залежно від конфіг/версії) + скидання брудних сторінок. Пучкові flush можуть посилити затримки.
- PostgreSQL: WAL‑записи + чекпоінти + background writer. Vacuum також створює I/O, а bloat збільшує майбутні I/O.
Краща практика для малого VPS: зменшіть write amplification перш за все
- Виправте «балакучі» додатки (занадто багато дрібних транзакцій).
- Пакетуйте записи, де це дозволяє правильність.
- Уникайте оновлення «last_seen» на кожному запиті, якщо воно не потрібне.
- Тримайте індекси компактними; кожен індекс — це податок на запис.
Підводні камені файлової системи
- Не кладіть бази даних на ненадійні мережеві файлові системи, якщо платформа не гарантує семантику надійності.
- Слідкуйте за умовою «диск повний»: Postgres і MySQL по‑різному поводяться при цьому, але жоден сценарій не є приємним.
Три корпоративні історії з передової
1) Інцидент через неправильне припущення: «Кеш покриє це»
Маленька команда вела кілька маркетингових сайтів і сервіс оформлення замовлення на одному VPS з 4 ГБ. Там були MySQL, Nginx і PHP‑FPM. Трафік був «переважно статичний», і це було правдою до запуску кампанії, коли сервіс оформлення почав отримувати спалахи авторизованих запитів.
Припускали, що page‑cache і кеш додатка перекриють читання, тож підняли innodb_buffer_pool_size майже до 3GB, щоб «розігнати базу». Це чудово виглядало у спокійні години. Потім стартувала кампанія.
PHP‑FPM розгорнув воркерів на обробку трафіку. Кожен воркер споживав більше пам’яті, ніж хтось пам’ятав. ОС почала свопити. Buffer pool бази був великий, тому ядру залишалося менше місця для всього іншого. Латентність не зростала поступово; вона обрушилася. Чек‑аут почав таймити, ретраї піднімали трафік, і шторм ретраїв перетворив ресурсну проблему на самохостний DoS.
Виправлення не було екзотичним. Зменшили buffer pool, щоб залишити запас, обмежили PHP‑FPM дітей, знизили MySQL max_connections, щоб система падала швидко, а не thrashилася, і поставили явну чергу перед чек‑аутом. Також навчилися різниці між «вільною пам’яттю» і «доступною пам’яттю під сплеск».
2) Оптимізація, що повернулася боком: «Просто підніміть work_mem, це нормально»
Внутрішній додаток працював на PostgreSQL. Користувачі скаржилися на повільні звіти, тому хтось значно підвищив work_mem, бо блог‑пост сказав, що це зменшить I/O тимчасових файлів. Воно і зменшилося. Для одного користувача. В одному сеансі.
Потім настала понеділкова ранкова сесія. Декілька користувачів одночасно запустили звіти. Ці звіти кожен робив кілька сортувань і hash‑join. Postgres правильно виділив work_mem під кожну операцію. Споживання пам’яті різко зросло. VPS не впав відразу; він ставав все повільнішим і повільнішим, поки не ввімкнувся своп. База виглядала «живою», але кожен запит чекав за штормом I/O від свопу.
Команда відкотила work_mem до консервативного значення і замість цього виправила звітний запит. Додали відсутній індекс, звузили вибрані колонки і ввели періодичну таблицю‑зведення. Для по-справжньому важкого запиту створили роль з більшим work_mem і пропускали його через контрольований шлях звітності. Урок: не «ніколи не налаштовувати», а «не налаштовувати глобально для локальної проблеми на маленькій машині».
3) Нудна, але правильна практика, що врятувала день: «Обмежувати підключення і логувати повільні запити»
Інша організація хостила кілька невеликих клієнтських сайтів на спільному VPS з 4 ГБ. Нічого особливого. З першого дня вони робили три нудних речі: обмежували підключення до БД, увімкнули slow query logging з розумним порогом і моніторили дискове місце з тривогою задовго до 90%.
Одного дня апдейт плагіна вніс регресію в запит. Сайт не впав одразу, бо обмеження підключень не дозволили нескінченному навантаженню ввалитися в базу. Натомість деякі запити швидко відхилилися, що зробило проблему видимою без того, щоб зіпсувати всю коробку.
Slow query log вказав на курця: запит, що почав сканувати велику таблицю без корисного індексу. Додали індекс, виправили регресію, і інцидент обмежився коротким вікном. Ніяких таємниць. Ніякого «воно раптом пройшло». Ніякої археології у вихідні.
Ось як виглядає надійність: контрольований відмова, збір доказів і достатній запас, щоб один невдалий деплой не перетворився на катастрофу.
Поширені помилки: симптом → корінь → виправлення
1) Симптом: раптові 10–60с зависання по всьому сайту
Корінь: сплески затримки сховища під час чекпоінтів/flush або своп‑шторм.
Виправлення: підтвердіть за допомогою iostat і vmstat; зменшіть тиск пам’яті (менші DB‑кеші, менше воркерів додатка), згладьте чекпоінти (Postgres) і зменшіть write amplification (обидва).
2) Симптом: CPU бази даних високий, запити «не такі вже й повільні» поодинці
Корінь: занадто багато одночасних підключень; наклад від контенції домінує.
Виправлення: обмежте підключення; додайте пулінг (pgBouncer); зменшіть concurrenсy PHP‑FPM; кешуйте на рівні додатка або реверс‑проксі; вмикайте fail fast замість thrash.
3) Симптом: Postgres росте і росте; продуктивність поступово деградує
Корінь: відставання vacuum і bloat таблиць/індексів через недостатній autovacuum або довгі транзакції.
Виправлення: знайдіть idle‑in‑transaction сесії, налаштуйте autovacuum для гарячих таблиць, і припиніть утримувати транзакції відкритими між запитами.
4) Симптом: MySQL «Waiting for table metadata lock» у processlist
Корінь: зміна схеми або DDL заблокована довгими транзакціями; запити стоять у черзі за metadata locks.
Виправлення: плануйте DDL у вікнах обслуговування; тримайте транзакції короткими; використовуйте online schema change підходи за потреби.
5) Симптом: багато тимчасових файлів або «Using temporary; Using filesort» у MySQL
Корінь: відсутні індекси для ORDER BY/GROUP BY патернів; запити сортують великі набори.
Виправлення: додайте складні індекси, що відповідають filter+sort; зменшіть вибір колонок; робіть пагінацію; уникайте OFFSET пагінації для глибоких сторінок.
6) Симптом: часті помилки «too many connections»
Корінь: витоки підключень у додатку, відсутність пулінгу або стрибки у кількості веб‑воркерів.
Виправлення: пулінуйте підключення; встановіть розумні таймаути; обмежте конкуренцію додатка; встановіть DB max_connections на число, яке ви можете дозволитися.
7) Симптом: після «налаштування» продуктивність погіршилася
Корінь: глобальна настройка (як work_mem або занадто великий buffer pool) збільшила пам’ять на підключення і спричинила своп під конкуренцією.
Виправлення: відкотіть; застосовуйте налаштування на рівні користувача/запиту; вимірюйте пам’ять і конкуренцію явно.
Чеклисти / покроковий план
Крок 0: Визначте, що означає «добре»
- Виберіть SLO‑подібну метрику: наприклад, homepage p95 < 500ms, checkout p95 < 800ms.
- Виберіть вікно вимірювання і зафіксуйте базу (CPU, RAM, swap, iowait, підключення, повільні запити).
Крок 1: Стабілізуйте хост
- Переконайтеся, що на диску є принаймні 15–20% вільного місця.
- Переконайтеся, що ви не свопите під звичайним піковим трафіком.
- Встановіть консервативні ліміти сервісів (systemd limits за потреби), щоб уникнути неконтрольованих процесів.
Крок 2: Обмежте конкуренцію свідомо
- Встановіть PHP‑FPM max children на кількість, яку ви можете дозволитися в RAM.
- Встановіть DB
max_connections, щоб захистити машину. - На Postgres: впровадьте pgBouncer і зменшіть бекенд‑підключення.
Крок 3: Встановіть перші пам’ятні регулятори
- MySQL: встановіть
innodb_buffer_pool_sizeтак, щоб робочий набір поміщався без виснаження ОС. - Postgres: встановіть
shared_buffersконсервативно; тримайтеwork_memнизьким глобально.
Крок 4: Увімкніть збір доказів
- MySQL: slow query log на 0.5–1s під час піку, потім аналізуйте й виправляйте.
- Postgres:
log_min_duration_statementі бажаноpg_stat_statements.
Крок 5: Виправте топ‑3 патерни запитів
- Додайте відсутні індекси, що зменшують сканування рядків.
- Усуньте N+1 запити в додатку.
- Не запускайте дорогі запити на кожен запит; передобчислюйте або кешуйте.
Крок 6: Перетестуйте і встановіть запобіжники
- Повторіть завдання триажу під піком.
- Додайте алерти на активність swap, використання диска, кількість підключень і частоту повільних запитів.
- Задокументуйте «безпечні» налаштування і підґрунтя, щоб майбутнє‑ви їх не відкотив випадково.
FAQ
1) На VPS з 4 ГБ, що пріоритизувати: кеш БД чи кеш ОС?
Пріоритет — стабільність. Для single‑box веб+БД не душіть ОС. Помірний кеш БД плюс запас краще, ніж величезний кеш, що викликає своп під сплесками.
2) Чи PostgreSQL «повільніший» за MySQL для сайтів?
Не за законом. Для багатьох веб‑навантажень обидва достатньо швидкі при правильних індексах. Більший відмінник на 4 ГБ — управління підключеннями і патерни записів, а не сирий двигун.
3) Який перший параметр MySQL варто змінити?
innodb_buffer_pool_size, підібраний до реалій. Потім обмежте max_connections. Потім увімкніть slow query logging і виправляйте те, що воно покаже.
4) Який перший параметр PostgreSQL варто змінити?
Стратегія пулінгу підключень (pgBouncer) і max_connections. Потім консервативні shared_buffers і логування/pg_stat_statements, щоб ідентифікувати топ‑запити.
5) Чи можна просто збільшити swap, щоб вирішити проблеми пам’яті?
Можна збільшити swap, щоб уникнути раптових OOM‑крахів, але swap — не оперативна пам’ять для продуктивності. Якщо база або PHP‑воркери регулярно потрапляють у swap, латентність стане непередбачуваною.
6) Чи варто вимикати fsync для швидкості?
Ні для продуктивних сайтів, де важлива цілісність даних. Якщо ви вимикаєте надійність і хост падає — втрачаєте дані. Бенчмарки це люблять; клієнти — ні.
7) Як зрозуміти, що я I/O bound?
Високий iowait у vmstat, високий await і %util у iostat, а також сесії БД, що чекають на IO‑події (Postgres) — сильні сигнали.
8) Коли варто розділити веб і БД на окремі сервери?
Коли налаштування починають бути компромісом між пам’яттю веб‑шара і БД, або коли затримки сховища роблять записи невпевненими. Розділення дає ізоляцію і ясніше планування потужностей.
9) Чи достатньо дефолтів сьогодні?
Дефолти кращі, ніж раніше, але вони не підлаштовані під вашу «все на одній коробці 4 ГБ» ситуацію. Контроль підключень і бюджет пам’яті — все ще на вас.
10) Яка найбезпечніша «перемога в продуктивності», яку можна зробити без глибокої експертизи?
Увімкніть slow query logging (або pg_stat_statements), знайдіть топ‑3 споживача часу і додайте правильні індекси. Також обмежте підключення, щоб сервер залишався стабільним під навантаженням.
Наступні кроки, які не будуть вам соромно пізніше
На VPS з 4 ГБ ви не оптимізуєте базу даних. Ви управляєте конкуренцією між вебом, базою і сховищем, намагаючись зробити латентність нудною.
- Запустіть план швидкої діагностики під піком і запишіть, що насправді відбувається: swap, iowait, підключення, блокування, топ‑запити.
- Обмежте конкуренцію перш за все: PHP‑FPM воркери і підключення до БД. Додайте pgBouncer, якщо ви на Postgres.
- Встановіть перший пам’ятний регулятор (InnoDB buffer pool або Postgres shared_buffers) на консервативне значення, що залишає запас.
- Увімкніть збір доказів (slow query logs / pg_stat_statements) і виправте топ‑винуватців індексами й змінами запитів.
- Перепровірте диск і поведінку записів; згладьте чекпоінти, зменшіть тимчасові виливи і припиніть галасливі записи, що не потрібні.
- Прийміть рішення, чи справжнє рішення — архітектурне: перенести БД на окремий VPS, оновити рівень сховища або використовувати керовану базу. Іноді найдієвіший параметр тюнінгу — ваш рахунок.
Якщо зробите лише одне сьогодні: обмежте підключення і припиніть свопити. Все інше — гарнір.