MySQL проти PostgreSQL на VPS з 1 ГБ ОЗП: що реально придатне (і які налаштування це забезпечують)

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

Ви орендували крихітний VPS, поставили «серйозну» базу даних, а за п’ять хвилин машина почала свопити так, ніби оплата йде за сторінку. У додатку все виглядає нормально в девелопі, але в продакшені настає повільний катастрофічний режим: запити затримуються, середнє навантаження стрибає, а монітор аптайму починає писати поезію.

На VPS з 1 ГБ ОЗП не варто романтизувати бази даних. Потрібно бути конкретним. Це практичний, суб’єктивний гайд з запуску MySQL (InnoDB) або PostgreSQL на 1 ГБ VPS без самонашкоджень: арифметика пам’яті, важливі налаштування та перевірки, які покажуть, що реально відбувається.

Єдине питання, яке має значення на 1 ГБ: де ваш вузький пляшок?

На VPS з 1 ГБ ви завжди платите якийсь податок: тиск пам’яті, затримки диска, CPU steal або накладні витрати на підключення. Вибір СУБД важливий, але не настільки, як форма вашого робочого навантаження і чи ви обмежили очевидні підводні камені. Якщо додаток відкриває 200 підключень, і MySQL, і PostgreSQL постраждають. Якщо сховище повільне і ви наполягаєте на синхронній стійкості з крихітними кешами, обидві відчують себе так, ніби їх дзвонять пейджером з 1999 року.

Мета — не «швидко». Мета — «передбачувано». Передбачувано означає:

  • Використання пам’яті має верхню межу, яку ви можете пояснити.
  • Записи на диск виписані рівномірно (або принаймні не вибухоподібно).
  • Підключення обмежені й використовують пулінг.
  • Фонова техобслуга не дивує вас о 3:00 ранку.

Це стандарт. Все інше — імпровізаційна комедія, і ваші користувачі не купували квитки.

Вердикт щодо придатності: хто насправді придатний на 1 ГБ?

If you want the straight answer

Обидві СУБД придатні на 1 ГБ ОЗП для невеликих навантажень, але вони відмовляють по-різному:

  • PostgreSQL більш чутливий до «занадто багатьох підключень» і сюрпризів з пам’яттю на запит (сортування, хеші, work_mem). Він надзвичайно стабільний, коли ви ставите жорсткі ліміти і використовуєте пулінг. Також він краще зберігає вашу ментальну модель у цілості.
  • MySQL (InnoDB) більш чутливий до невірно встановлених кешів і поведінки флашінгу; він може тихо спалювати пам’ять у буферах на підключення, якщо йому дозволити. При консервативному налаштуванні і низькій кількості підключень він може відчуватися «швидшим» для простих OLTP читань/записів.

Що я рекомендую (суб’єктивно)

Якщо ви будуєте типовий веб-додаток, особливо з репортами, міграціями й «додамо функцію пізніше», запускайте PostgreSQL і ставте перед ним PgBouncer. Операційні запобіжники зрозуміліші, а екосистема (міграції, обмеження, транзакційні DDL) часто зменшує дивності на рівні додатку.

Якщо у вас просте ключ-значення подібне OLTP навантаження з фреймворком, що очікує MySQL, або у вас вже є операційний досвід з MySQL, запускайте MySQL і жорстко обмежуйте підключення. Просто не вважайте конфіги за замовчуванням «дружніми до малих серверів». Вони дружні до бенчмарків виробника.

Одна фраза для пам’яті: на 1 ГБ PostgreSQL — добрий сусід, якщо ви його прив’язали; MySQL — добрий сусід, якщо ви годуєте його правильними порціями.

Короткий жарт #1: VPS з 1 ГБ — як студія: можна приймати гостей, але не якщо вони всі принесуть меблі.

Моделі пам’яті: чому «вміщається» — не план

The 1GB budget you actually have

«1 ГБ ОЗП» — це маркетинг. Частину забирає ядро. Частину — моніторинг. Ваші SSH-сесії, cron і демони логів теж з’їдають пам’ять. Якщо це сучасний дистрибутив, systemd існує лише для того, щоб нагадати, що ентропія реальна.

На VPS з 1 ГБ реалістичний безпечний бюджет для пам’яті процесу бази даних часто становить 500–750 МБ, залежно від того, що ще працює. Якщо додаток співмешкає на тому ж VPS, зменшіть бюджет. Якщо ви використовуєте Docker — зменшіть ще і почніть сумніватися у своїх життєвих виборах.

PostgreSQL memory in one paragraph

PostgreSQL має розділену пам’ять (переважно shared_buffers) та велику кількість пам’яті на бекенд: кожне підключення — процес (якщо ви не використовуєте спеціальні режими). Пам’ять на запит регулюється work_mem (сортування/хеш), і вона може множитися на кількість одночасних операцій. Класична пастка: ви встановлюєте work_mem на «розумне» значення, а запит використовує кілька таких буферів одночасно на багатьох підключеннях — і ви з «все ок» швидко потрапляєте під OOM killer.

MySQL memory in one paragraph

MySQL з InnoDB має великий спільний кеш (innodb_buffer_pool_size) плюс буфери на підключення (сорти, джойни, читання) та внутрішню пам’ять (словники, adaptive hash тощо). Buffer pool зазвичай головний важіль: він має бути достатньо великий, щоб уникнути постійних звернень на диск, але не настільки великий, щоб позбавити ОС кешу сторінок. На 1 ГБ «правильний» buffer pool зазвичай менший, ніж інтуїція підказує.

Two rules that prevent most 1GB disasters

  1. Обмежуйте підключення, щоб пам’ять на підключення не перемножилася у відмову.
  2. Резервуйте пам’ять для ОС і файлового кешу; бази даних не отримують ексклюзивних прав на RAM тільки через власну драматичність.

Interesting facts & historical context (pick these up and sound wise in meetings)

  1. PostgreSQL походить від проєкту POSTGRES в UC Berkeley (1980-ті), а частина «SQL» була додана пізніше — дизайн орієнтований на розширюваність і коректність, а не «просто відправити в реліз».
  2. MySQL став веб-стандартом у кінці 1990-х/початку 2000-х насамперед тому, що був легким і простим у розгортанні, а не тому, що тоді найкраще підтримував транзакції.
  3. InnoDB спочатку був стороннім движком; він став дефолтним в MySQL 5.5. Це змінило операційну гру: відновлення після збоїв і MVCC стали масовими для користувачів MySQL.
  4. Vacuum у PostgreSQL є через MVCC, який зберігає старі версії рядків; якщо не вакуумити, ви отримаєте не лише надмірне місце, а й ризик обертання transaction ID.
  5. Історично MySQL мав різні налаштування стійкості в залежності від параметрів типу innodb_flush_log_at_trx_commit; багато історій «MySQL швидкий» насправді означали «MySQL не синхронізував кожну транзакцію».
  6. WAL у PostgreSQL (журнал перед записом) схожий концептуально на інші серйозні СУБД: стійкість забезпечується записом намірів у журнал перед флашем сторінок даних.
  7. Doublewrite буфер InnoDB існує, щоб захистити від часткових записів сторінок; він платить додатковими записами за меншу ймовірність корупції після краху.
  8. Планувальник PostgreSQL може бути дуже розумним, але на крихітних машинах він також може виявитися дуже «дорогим», якщо ви попросите сортувати великі проміжні результати в пам’яті, якої немає.
  9. Кеш запитів у MySQL колись вважався «функцією продуктивності», але часто перетворювався на баг; його депрекували та видалили з вагомих причин.

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

Базові налаштування: MySQL на 1 ГБ, який не з’їдає весь хост

When MySQL makes sense on 1GB

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

A sane MySQL/InnoDB memory target

Припустимо, ви можете виділити 600–700 МБ для mysqld на сервері лише для БД з 1 ГБ ОЗП. Якщо ви співмешкаєте з додатком — знизьте до 400–500 МБ. Тепер розподіліть:

  • innodb_buffer_pool_size: зазвичай 256–512 МБ
  • innodb_log_file_size: помірний (наприклад, 64–128 МБ) щоб час відновлення був розумним
  • буфери на підключення: тримайте їх малими; обмежуйте підключення

Baseline config snippet (MySQL 8-ish)

Це навмисно консервативно. Пізніше можна розслабляти налаштування; назад після OOM не повернешся.

cr0x@server:~$ sudo bash -lc 'cat >/etc/mysql/mysql.conf.d/99-vps-1gb.cnf <<"EOF"
[mysqld]
# Core
max_connections = 60
skip_name_resolve = 1

# InnoDB memory
innodb_buffer_pool_size = 384M
innodb_buffer_pool_instances = 1

# InnoDB durability / IO
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
innodb_io_capacity = 200
innodb_io_capacity_max = 400

# Redo log (MySQL 8 uses innodb_redo_log_capacity; keep modest)
innodb_redo_log_capacity = 256M

# Temp / sort behavior
tmp_table_size = 32M
max_heap_table_size = 32M
sort_buffer_size = 1M
join_buffer_size = 256K
read_buffer_size = 256K
read_rnd_buffer_size = 512K

# Avoid surprise thread memory
thread_cache_size = 16

# Slow query visibility
slow_query_log = 1
long_query_time = 0.5
log_slow_admin_statements = 1
EOF
systemctl restart mysql'
...output...

Нотатки, які мають значення:

  • max_connections на 1 ГБ — не предмет переговорів. Якщо додаток потребує більше — потрібен пулінг.
  • O_DIRECT допомагає уникнути подвійного кешування (InnoDB buffer pool + кеш ОС). На деяких VPS/накопичувачах це перевага; на інших — «ок». Тестуйте, але не захоплюйтеся.
  • Малі буфери на підключення — тихий герой. Великі буфери для кожного потоку — шлях до ранкового OOM при спайку трафіку.

Binary logs on 1GB: pick a stance

Якщо вам не потрібна реплікація або point-in-time recovery, відключіть binlog, щоб зменшити навантаження на запис. Якщо потрібні — тримайте їх, але агресивно ротуйте і стежте за диском.

Базові налаштування: PostgreSQL на 1 ГБ, який тримається на ногах

When PostgreSQL makes sense on 1GB

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

A sane PostgreSQL memory target

На сервері для БД з 1 ГБ ОЗП виділіть приблизно 600–750 МБ для процесів Postgres і спільної пам’яті, а решту — для кеша ОС і здорового глузду. Тоді:

  • shared_buffers: 128–256 МБ (рідко більше на 1 ГБ)
  • work_mem: 2–8 МБ (так, так мало; конкурентність множить)
  • maintenance_work_mem: 64–128 МБ (vacuum/індексні збірки потребують цього, але не ставте 512 МБ на 1 ГБ, якщо любите paging)
  • max_connections: тримайте низьким і використовуйте PgBouncer

Baseline config snippet (PostgreSQL 14–17 style)

cr0x@server:~$ sudo bash -lc 'PGVER=$(psql -V | awk "{print \$3}" | cut -d. -f1); \
CONF="/etc/postgresql/$PGVER/main/postgresql.conf"; \
cat >>"$CONF" <<"EOF"

# 1GB VPS baseline (conservative)
max_connections = 40

shared_buffers = 192MB
effective_cache_size = 512MB

work_mem = 4MB
maintenance_work_mem = 96MB

wal_buffers = 8MB
checkpoint_timeout = 10min
max_wal_size = 1GB
min_wal_size = 256MB
checkpoint_completion_target = 0.9

# Autovacuum: keep it on, but don’t let it stampede
autovacuum_max_workers = 2
autovacuum_naptime = 30s
autovacuum_vacuum_cost_limit = 1000
autovacuum_vacuum_cost_delay = 10ms

# Observability
log_min_duration_statement = 500
log_checkpoints = on
log_autovacuum_min_duration = 1000
EOF
systemctl restart postgresql'
...output...

Чому саме такі вибори:

  • effective_cache_size — підказка планувальнику, а не алокація. На 1 ГБ не брешіть планувальнику.
  • Малий work_mem знижує ризик OOM. Якщо окремому запиту потрібно більше, задайте його на сесію для того конкретного завдання.
  • Autovacuum — не опціонально. Якщо ви його вимикаєте, щоб «зберегти ресурси», ви лише берете проблеми в кредит з хижим відсотком.

Короткий жарт #2: Вимкнути autovacuum у Postgres — це як відключити пожежну сигналізацію, бо вона шумить.

Підключення вас вб’ють: пулінг і жорсткі ліміти

Why PostgreSQL feels “worse” under naive connection storms

Кожне підключення PostgreSQL — це процес. Це не провина; це дизайн, який робить ізоляцію та спостережуваність чистими. На 1 ГБ це означає, що 150 підключень — фактично замовлений вами DoS.

PgBouncer is not “extra,” it’s the seatbelt

Поставте PgBouncer у режим пулінгу за транзакціями для типовго веб-трафіку. Він дозволяє зберегти низьке max_connections і обслуговувати вищу клієнтську конкурентність безпечно.

cr0x@server:~$ sudo bash -lc 'apt-get update -y && apt-get install -y pgbouncer'
...output...
cr0x@server:~$ sudo bash -lc 'cat >/etc/pgbouncer/pgbouncer.ini <<"EOF"
[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432

auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction
max_client_conn = 200
default_pool_size = 20
reserve_pool_size = 5

server_idle_timeout = 60
query_timeout = 30
log_connections = 1
log_disconnections = 1
EOF
systemctl restart pgbouncer'
...output...

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

Зберігання та ввід/вивід: диск VPS — прихований бос

На маленьких VPS диск часто найповільніший компонент і найменш передбачуваний. Ви можете бути на спільному SSD з кредитами на вибухи або на «SSD», що поводиться як чемний HDD. Бази даних роблять поведінку диска видимою, бо вони постійно пишуть і викликають fsync.

Durability settings are performance settings

Коли ви запускаєте стійкі за замовчуванням налаштування (innodb_flush_log_at_trx_commit=1, синхронні коміти Postgres, WAL fsync), ви свідомо чекаєте підтвердження запису від сховища. Це правильний вибір. Якщо ви послаблюєте це, ви торгуєте коректністю за швидкість.

На 1 ГБ VPS для хобі-наванттажень ви можете погодитися на менш жорстку стійкість. У продакшені будьте обережні: ви дізнаєтесь про свою реальну толерантність до ризику лише після втрати даних. Це не той момент, коли хотілося б обговорювати.

Filesystem cache matters even when you have a buffer cache

Linux page cache все одно допомагає. Він кешує метадані файлової системи, tablespace’и та часто доступні сторінки, які база даних не «заколачує». Позбавлення ОС пам’яті призводить до випадкових стрибків латентності навіть якщо власний кеш БД «великий». Саме тому «дати InnoDB 80% RAM» не універсальна істина — особливо на 1 ГБ.

Swap: the villain with a purpose

На 1 ГБ я зазвичай вважаю корисним мати деякий swap (навіть невеликий swapfile), щоб поглинути короткі піки замість негайного виклику OOM killer. Але якщо ви постійно свопите — ви вже в проблемі. Swap — подушка безпеки, а не рушій.

Швидкий план діагностики: знайти вузьке місце за хвилини

Порядок, що допомагає не скотитися в тижневе налаштування «впівоберта».

First: is it memory pressure or I/O?

  1. Перевірте swap і пам’ять: якщо swap зростає або відбуваються OOM-кілли, припиніть «налаштування SQL» і спочатку обмежте підключення/пам’ять.
  2. Перевірте I/O wait: високий %iowait означає, що ви чекаєте на диск; план запитів може бути нормальним, а диск — сумним.

Second: is the database overloaded or the app?

  1. Кількість підключень: чи ви на межі max connections? Якщо так — у вас черга і треш.
  2. Повільні запити: чи кілька запитів домінують? Якщо так — виправляйте їх перед «налаштуванням сервера».

Third: is it checkpoints/flushes or lock contention?

  1. PostgreSQL: перевірте частоту контрольних точок (checkpoint) і поведінку autovacuum; також шукайте блокування.
  2. MySQL: перевірте тиск на redo log, пропуски буфера pool, і очікування блокувань.

Fourth: verify storage and kernel constraints

  1. Дискове місце: заповнений диск викликає хаос.
  2. CPU steal: на загальному хості ваш «CPU» може бути радше теоретичним.

Практичні завдання (з командами): спостерігай, вирішуй, діюй

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

Task 1: Confirm RAM, swap, and pressure

cr0x@server:~$ free -m
              total        used        free      shared  buff/cache   available
Mem:            987         612          54          12         320         248
Swap:          1023         310         713

Значення: низький показник available і ненульове використання swap свідчать про тиск пам’яті. Якщо swap росте під час піків — ви прямуєте до стрибків латентності.

Рішення: зменшіть max connections, зменшіть кеші БД трохи, додайте пулінг або додайте ОЗП. Не починайте з додавання індексів.

Task 2: See if you’re I/O bound (high 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
 1  0 317440  55296  12000 310000    0    8   120   980  300  450 12  6 72 10  0
 2  1 317680  41000  11800 309500    0   64   400  2200  500  900 18  8 58 16  0

Значення: wa (I/O wait) у десятках означає, що диск — вузьке місце, особливо якщо це корелює з латентністю.

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

Task 3: Identify top memory consumers

cr0x@server:~$ ps -eo pid,comm,rss,pmem --sort=-rss | head
 1823 postgres   98200  9.9
 1744 postgres   94120  9.5
 1602 postgres   90500  9.2
 1320 mysqld     82000  8.3
  911 node       61000  6.1

Значення: багато бекендів Postgres, кожен приблизно по ~90 МБ, вказують на пам’ять на підключення + роботу запитів. MySQL показуєся як один великий процес.

Рішення: для Postgres: зменшіть підключення і work_mem; додайте PgBouncer. Для MySQL: перевірте розмір buffer pool і буфери на потік.

Task 4: Check if the kernel is killing processes (OOM)

cr0x@server:~$ dmesg -T | tail -n 20
[Mon Dec 30 10:12:14 2025] Out of memory: Killed process 1823 (postgres) total-vm:1620400kB, anon-rss:210000kB, file-rss:1200kB, shmem-rss:0kB

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

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

Task 5: Confirm disk space and inode sanity

cr0x@server:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        25G   22G  2.1G  92% /

Значення: 92% — не «повний», але достатньо близько, щоб autovacuum/temp файли/WAL створили проблему.

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

Task 6: Measure storage latency quickly

cr0x@server:~$ iostat -x 1 3
Device            r/s     w/s   r_await   w_await  aqu-sz  %util
vda              8.00   35.00     6.10    28.40    1.20   92.00

Значення: високий w_await і високий %util означають, що диск насичений записами.

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

Task 7: PostgreSQL—check connection count and states

cr0x@server:~$ sudo -u postgres psql -c "select state, count(*) from pg_stat_activity group by 1 order by 2 desc;"
   state   | count
-----------+-------
 idle      |    22
 active    |     9
 idle in transaction | 6
(3 rows)

Значення: «idle in transaction» — класика: сесії утримують блокування і створюють ризик блоату.

Рішення: виправте поведінку транзакцій у додатку; встановіть idle_in_transaction_session_timeout. Пулінг допомагає, але не вирішує поганий код.

Task 8: PostgreSQL—find top slow queries by total time

cr0x@server:~$ sudo -u postgres psql -c "select query, calls, total_exec_time::int as total_ms, mean_exec_time::int as mean_ms from pg_stat_statements order by total_exec_time desc limit 5;"
                               query                                | calls | total_ms | mean_ms
--------------------------------------------------------------------+-------+----------+---------
 select * from events where user_id = $1 order by created_at desc    |  1200 |    98000 |      81
 update accounts set last_seen = now() where id = $1                 |  4500 |    42000 |       9
(2 rows)

Значення: перший запит — кандидат на індекс типу (user_id, created_at desc) і, можливо, вибір меншої кількості колонок.

Рішення: виправте топ-порушників перед тим, як змінювати глобальні налаштування. Налаштування на 1 ГБ не врятують повний скан таблиці при кожному запиті.

Task 9: PostgreSQL—check checkpoint pressure

cr0x@server:~$ sudo -u postgres psql -c "select checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend from pg_stat_bgwriter;"
 checkpoints_timed | checkpoints_req | buffers_checkpoint | buffers_backend
-------------------+-----------------+--------------------+----------------
                18 |              42 |            8123456 |         456789
(1 row)

Значення: багато запитаних чекпоїнтів вказує, що WAL заповнюється швидко (записно важкі піки), що може викликати I/O сплески.

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

Task 10: PostgreSQL—see if autovacuum is keeping up

cr0x@server:~$ sudo -u postgres psql -c "select relname, n_dead_tup, last_autovacuum from pg_stat_user_tables order by n_dead_tup desc limit 5;"
 relname  | n_dead_tup |        last_autovacuum
----------+------------+-------------------------------
 events   |     182345 | 2025-12-30 09:50:12.12345+00
 logs     |      93440 | 
(2 rows)

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

Рішення: налаштуйте autovacuum scale factors для конкретних таблиць, додайте індекси обережно і переконайтеся, що vacuum не блокується довготривалими транзакціями.

Task 11: MySQL—check current memory-relevant variables

cr0x@server:~$ sudo mysql -e "show variables where Variable_name in ('max_connections','innodb_buffer_pool_size','tmp_table_size','max_heap_table_size','sort_buffer_size','join_buffer_size');"
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 402653184 |
| join_buffer_size        | 262144    |
| max_connections         | 60        |
| max_heap_table_size     | 33554432  |
| sort_buffer_size        | 1048576   |
| tmp_table_size          | 33554432  |
+-------------------------+-----------+

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

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

Task 12: MySQL—check buffer pool hit rate and reads

cr0x@server:~$ sudo mysql -e "show global status like 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name                         | Value     |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests      | 184003211 |
| Innodb_buffer_pool_reads              | 1200345   |
+---------------------------------------+-----------+

Значення: деякі звернення на диск нормальні. Якщо Innodb_buffer_pool_reads швидко росте відносно запитів, робочий набір не вміщується і ви обмежені I/O.

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

Task 13: MySQL—check temp tables going to disk

cr0x@server:~$ sudo mysql -e "show global status like 'Created_tmp%tables';"
+-------------------------+--------+
| Variable_name           | Value  |
+-------------------------+--------+
| Created_tmp_disk_tables | 18420  |
| Created_tmp_tables      | 40210  |
+-------------------------+--------+

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

Рішення: додавайте індекси, переписуйте запити або трохи підвищуйте ліміти тимчасових таблиць, якщо пам’ять дозволяє. На 1 ГБ виправлення запитів переважить «додати ще пам’яті».

Task 14: Confirm connection usage (MySQL)

cr0x@server:~$ sudo mysql -e "show global status like 'Threads_connected'; show global status like 'Max_used_connections';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 48    |
+-------------------+-------+
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 59    |
+----------------------+-------+

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

Рішення: якщо виникають тайм-аути — додайте пулінг і зменшіть конкурентність у додатку. Підвищення max_connections на 1 ГБ — зазвичай просто збільшить площу ураження.

Task 15: Check CPU steal time (shared VPS reality check)

cr0x@server:~$ mpstat 1 3
Linux 6.1.0 (server) 	12/30/2025 	_x86_64_	(1 CPU)

12:10:01 AM  %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
12:10:02 AM  18.0  0.0  8.0    10.0  0.0   1.0   12.0   0.0    0.0  51.0

Значення: %steal на рівні 12% означає, що гіпервізор забирає ваш CPU. Налаштування не виправлять вашого орендодавця.

Рішення: розгляньте кращий план/провайдера, зменшіть навантаження або перенесіть БД в інше місце. Не витрачайте дні на мікро-оптимізації, поки хост перепроданий.

Task 16: Confirm latency from the database itself

cr0x@server:~$ sudo -u postgres psql -c "select now(), pg_sleep(0.1), now();"
              now              | pg_sleep |              now
-------------------------------+----------+-------------------------------
 2025-12-30 00:10:10.0101+00   |          | 2025-12-30 00:10:10.1109+00
(1 row)

Значення: якщо цей простий виклик повільний або зависає — проблема на системному рівні (I/O, CPU steal, пам’ять), а не в запитах.

Рішення: припиніть звинувачувати ORM і дивіться на хост.

Три корпоративні міні-історії з бойового фронту

1) Incident caused by a wrong assumption: “It’s only 1GB, but it’s just a dev box”

У них був невеликий внутрішній сервіс на VPS з 1 ГБ. «Внутрішній» означало «користуються лише інженери», що є ввічливим способом сказати «ніхто не міряв і всі вважали, що все ок». Вони обрали PostgreSQL, виставили max_connections в 200, бо додаток іноді підбивався, і встановили work_mem в 64 МБ, бо в якомусь блогпості написали, що сортування в пам’яті швидше.

Це працювало тижнями. Потім випустили нову панель, яка запускала кілька агрегуючих запитів на сторінку. Кожен запит використовував кілька сортувань і хешів. Інженери відкривали панель паралельно під час інциденту. База даних не деградувала плавно; вона впала з кручі. Ядро почало вбивати бекенди. Панель повторно запитувала. Повторні запити створили ще більше бекендів. Далі здогадаєтесь самі.

Припущення було: «64 МБ — мало; сервер має 1 ГБ». Реальність: work_mem — на операцію, на підключення, і запити можуть використовувати декілька одночасно. Помножте на конкурентність — і ви маєте генератор OOM.

Виправлення не було героїчним. Вони обмежили max_connections до 40, знизили work_mem до 4 МБ, поставили PgBouncer і перевели панель на асинхронні важчі запити з кешованими результатами. «Внутрішній» сервіс знову став нудним — що є найвищою похвалою для бази даних.

2) Optimization that backfired: “Let’s make checkpoints disappear”

Інша команда працювала на Postgres на маленькому VPS і скаржилася на періодичні стрибки латентності. Вони побачили логи чекпоїнтів і вирішили, що чекпоїнти — ворог. Рішення: агресивно збільшити max_wal_size і розтягнути checkpoint_timeout, щоб чекпоїнти траплялися рідше.

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

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

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

3) Boring but correct practice that saved the day: connection caps + slow query logs

Малий SaaS запускав MySQL на 1 ГБ інстансах для деяких шард. Нічого надзвичайного, просто OLTP. У них була сувора політика: для кожного шарду жорсткий max_connections, консервативні буфери на потік і увімкнений журнал повільних запитів з низьким порогом. Також щотижнево хтось переглядав зразки повільних запитів і шукав регресії.

Одного п’ятничного дня нова фіча додала запит, який «все ок» на стейджингу, але був патологічним для одного шарду з асиметричним набором даних. Запит робив join без потрібного індексу і випадав у тимчасові таблиці на диску. На 1 ГБ VPS з середнім I/O це перетворює один запит у DoS.

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

Ніяких героїчних налаштувань. Ні ночей «можливо Linux поламаний». Просто запобіжники і запис. Інцидент був нудний, і нудність — саме те, чого ви хочете.

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

1) Symptom: sudden timeouts, load average spikes, swap climbing

Корінна причина: Занадто багато підключень + пам’ять на підключення (бекенди Postgres, буфери потоків MySQL), що викликає тиск пам’яті і своп-шторм.

Виправлення: обмежити серверні підключення; додати пулінг; знизити work_mem (Postgres) і буфери на потік (MySQL). Розгляньте невеликий swapfile, якщо його не було, але сприймайте його як страховку, а не як додаткову потужність.

2) Symptom: periodic latency spikes every few minutes

Корінна причина: Піки контрольних точок/флашів і насичення записів на сховище.

Виправлення: для Postgres настройте розподіл чекпоїнтів (checkpoint_completion_target) і дайте помірний розмір WAL; для MySQL перегляньте налаштування флашу і redo. Зменшіть пікові записні завдання і уникайте великих транзакцій.

3) Symptom: PostgreSQL disk usage grows even when data “isn’t changing much”

Корінна причина: Бloat від MVCC + відставання vacuum, часто погіршене довготривалими транзакціями або вимкненим autovacuum.

Виправлення: тримайте autovacuum ввімкненим; шукайте довгі транзакції; налаштуйте autovacuum для конкретних таблиць; розгляньте періодичний VACUUM (ANALYZE) або REINDEX у вікнах обслуговування.

4) Symptom: MySQL feels fast, then suddenly stalls on writes

Корінна причина: Тиск на redo log і затримки fsync; можлива мала ємність redo у поєднанні з піковими записами.

Виправлення: тримайте redo capacity помірною, уникайте величезних транзакцій і перевірте сховище. Якщо диск VPS непередбачуваний — жоден параметр його не зробить чесним.

5) Symptom: “idle in transaction” connections accumulate (Postgres)

Корінна причина: Додаток відкриває транзакцію і забуває закомітити/відкотити, утримуючи блокування і заважаючи vacuum.

Виправлення: виправте обсяг транзакцій в додатку; встановіть idle_in_transaction_session_timeout; моніторьте pg_stat_activity і при потребі вбивайте порушників.

6) Symptom: high CPU, but query throughput is low

Корінна причина: Погані плани запитів, відсутні індекси або дорогі сорти/хеші, що випадають на диск; інколи — CPU steal.

Виправлення: використовуйте EXPLAIN (ANALYZE, BUFFERS) (Postgres) або EXPLAIN (MySQL), виправляйте індекси і перевіряйте %steal. Якщо steal високий — ваше «тюнінгування CPU» переважно театральне.

7) Symptom: everything slows down after “raising caches”

Корінна причина: Позбавлення ОС і файлового кеша пам’яті; свопінг; фонові задачі конкурують за RAM.

Виправлення: трохи зменшіть розміри кешів БД, залиште запас пам’яті і перевірте знову. На 1 ГБ залишати 200 МБ вільними іноді вигідніше, ніж заганяти кожен мегабайт у базу даних.

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

Step-by-step: choosing MySQL vs PostgreSQL for 1GB

  1. Порахуйте вашу конкурентність: якщо потрібно багато одночасних клієнтів, плануйте пулінг незалежно від СУБД.
  2. Класифікуйте запити: переважно простий індексований OLTP чи змішаний OLTP + репортинг. Змішані навантаження віддають перевагу Postgres, але тільки якщо ви контролюєте підключення.
  3. Визначте підхід до стійкості: якщо не можете допустити втрату даних — не «оптимізуйте» шляхом вимкнення fsync.
  4. Перевірте якість диска: якщо диск повільний, налаштуйте менше записів і менші піки; розгляньте перенесення БД з VPS.
  5. Виберіть нудні дефолти з цього гайду і змінюйте по одній змінній з вимірюваннями.

Step-by-step: hardening a 1GB database host

  1. Встановіть моніторинг (щонайменше: диск, пам’ять, swap, load, iowait).
  2. Створіть swapfile, якщо його немає (малий, наприклад 1 ГБ) і налаштуйте vm.swappiness відповідно до дистрибутива.
  3. Обмежте підключення до БД (max_connections), потім реалізуйте пулінг у додатку.
  4. Увімкніть журнал повільних запитів ( log_min_duration_statement або MySQL slow log) з порогом, який ловить проблему рано (наприклад 500 мс).
  5. Переконайтеся, що autovacuum увімкнено (Postgres) і не заблоковано; перевірте, що InnoDB використовується (MySQL) і buffer pool не абсурдний.
  6. Тримайте запас вільного місця на диску і налаштуйте ротацію логів. Повний диск перетворює «проблему продуктивності» на «інцидент».
  7. Прогріть навантаження тестом, який відповідає реальності, а не надіям.

Step-by-step: tuning without cargo cult

  1. Отримайте базову лінію: латентність, пропускна здатність, CPU, iowait, пам’ять.
  2. Виправте найгірший запит спочатку (по загальному часу), а не «найбільш обговорюваний».
  3. Тільки потім розглядайте розміри кешів: buffer pool / shared buffers.
  4. Перевіряйте після змін. Якщо не бачите покращення — відкотіть і рухайтеся далі.

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

1) Can I run PostgreSQL on 1GB without PgBouncer?

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

2) What’s a safe shared_buffers on 1GB?

Зазвичай 128–256 МБ. Якщо виставити 512 МБ на 1 ГБ і дозволити багато підключень, ви створюєте тиск пам’яті. Залишайте запас для ОС і бекендів.

3) What’s a safe innodb_buffer_pool_size on 1GB?

Часто 256–512 МБ. Якщо VPS також запускає додаток, схиляйтесь до 256–384 МБ. Решта пам’яті не «марна»; вона перешкоджає свопінгу і допомагає кешу ОС.

4) Should I disable fsync or synchronous commit to go faster?

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

5) Is swap good or bad for databases on tiny boxes?

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

6) Which database is “lighter” on memory?

Жодна за замовчуванням. MySQL зазвичай домінує одним великим процесом (buffer pool) плюс буфери на підключення; Postgres множить пам’ять з підключеннями. З розумними обмеженнями та пулінгом обидві помістяться. Без обмежень обидві можуть зіпсувати вам день.

7) What’s the single most important setting on 1GB?

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

8) How do I tell if I need more RAM vs better disk?

Якщо ви свопите або отримуєте OOM-кілли — потрібна більше RAM або менша конкурентність. Якщо %iowait високий і w_await великий — потрібен кращий диск або менше записів. Часто потрібно обидва, але одне зазвичай голосніше.

9) Can I “just add indexes” to fix 1GB performance?

Індекси допомагають читанням і можуть шкодити записам. На 1 ГБ з повільним сховищем надлишок індексів підвищує write amplification і роботу vacuum. Додавайте правильні індекси для топ-запитів, а не всі, які пропонує ORM.

10) Should I run the app and DB on the same 1GB VPS?

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

Наступні кроки, які можна зробити сьогодні

  1. Виберіть ліміт підключень (Postgres 40, MySQL 60 — гідна відправна точка) і впровадьте пулінг.
  2. Застосуйте консервативний базовий конфіг для обраної СУБД, перезапустіть і перевірте, що налаштування дійсно застосовано.
  3. Увімкніть видимість повільних запитів (порог 500 мс) і збирайте докази протягом дня.
  4. Запустіть Швидкий план діагностики наступного разу, коли з’являться піки латентності: пам’ять → iowait → підключення → топ-запити → чекпоїнти/флаші.
  5. Визначте тригер для апгрейду: якщо регулярно ви близькі до лімітів, свопите або насичуєте диск, 2 ГБ RAM або краще сховище дадуть більше стабільності, ніж чергова хитрість.

Якщо вам потрібне одне фінальне правило для баз даних на 1 ГБ VPS: обмежте найгірший випадок. Саме для цього потрібні налаштування. Решта — сварка з фізикою.

← Попередня
Томи Docker: bind-маунти проти іменованих томів — що краще переживе міграцію
Наступна →
Кастомні чекбокси й радіокнопки на чистому CSS: доступні патерни, що не брешуть

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