PostgreSQL проти SQLite на VPS: найшвидший вибір без жалю

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

Ви працюєте на VPS. Потрібна «база даних». Не проєкт на вихідні й не стадо яків. Щось, що не розбудить вас о 03:00 через застряглий файл або тому, що ваш застосунок раптово отримав реальний трафік і ваш «простий» вибір перетворився на болісну міграцію.

Найшвидший спосіб обрати між PostgreSQL і SQLite — припинити сперечатися про фічі й поставити одне жорстке питання: де межа вашої конкурентності та відмовостійкості? Якщо вона всередині одного процесу — SQLite як скальпель. Якщо вона розкидана між багатьма процесами, користувачами, джобами й з’єднаннями — PostgreSQL ваш нудний, перевірений часом гайковий ключ.

Рішення за хвилину

Якщо ви прочитаєте тільки цей розділ — все одно зробите пристойний вибір.

Обирайте SQLite, якщо всі ці твердження вірні

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

Обирайте PostgreSQL, якщо хоча б одне з цих тверджень вірне

  • У вас є декілька записувачів, кілька інстансів застосунку, cron-завдань, воркерів, аналітичні запити, адмін-інструменти… все, що поводиться як невелике натовп.
  • Вам потрібна стійка конкурентність, щоб не перетворювати застосунок на координатор блокувань.
  • Ви дбаєте про ізоляцію, гарантії довговічності та відновлення при реальних відмовах.
  • Вам потрібні онлайн-зміни схеми, багаті індекси та плани запитів, що масштабується за межі «милого» застосунку.
  • Ви передбачаєте зростання і вважаєте за краще масштабуватися через додавання CPU/RAM зараз і реплік пізніше, а не робити ризикову міграцію під тиском.

Правило великого пальця: якщо БД має посередничати між людським нетерпінням (веб-трафік) і машинним нетерпінням (джоби), PostgreSQL — дорослий у кімнаті.

Жарт #1: SQLite як велосипед: швидкий, елегантний і ідеальний, поки ви не спробуєте перевезти ним диван.

Ментальна модель, що рятує від жалю

Більшість дебатів «Postgres vs SQLite» вмирають, бо люди порівнюють SQL-синтаксис або чеклісти фіч. Вибір справді про операційну форму: хто звертається до бази, як часто і що відбувається, коли щось іде не так.

SQLite: бібліотека з файлом, а не сервер

SQLite працює в процесі. Немає серверного демонa, що приймає з’єднання. Ваш застосунок лінкує бібліотеку; «база даних» — це файл (плюс опціональні файли журналювання/WAL). Це означає:

  • Латентність може бути чудовою, бо немає мережевого хопу. Виклики — це виклики функцій.
  • Конкурентність обмежена блокуванням файлу. Читання — ок. Записи вимагають координації; WAL покращує це, але не робить усе довільно паралельним.
  • Довговічність залежить від семантики файлової системи, опцій монтування і ваших налаштувань synchronous. Це не «небезпечно», це «ви несете відповідальність за гострі краї».
  • Резервні копії — це копії файлу, що можуть бути дивовижно простими — поки ви не зробите їх у невдалий момент без використання backup API SQLite.

PostgreSQL: сервер з процесами, пам’яттю та своїми поглядами

PostgreSQL працює як сервер бази даних зі своїми процесами, кешами, write-ahead log (WAL), фоновим vacuum та чіткими транзакційними семантиками. Це означає:

  • Висока конкурентність з MVCC (multi-version concurrency control): читачі не блокують записувачів так, як це відбувається при файлових блоках.
  • Довговічність і відновлення після крашу — основа системи. Потрібно налаштувати й тестувати, але система створена для поганих днів.
  • Є операційне навантаження: оновлення, бекупи, моніторинг, vacuum і управління з’єднаннями.
  • Шляхи масштабування ясніші: реплікація, read replicas, партиціювання, пулери з’єднань та зріла екосистема інструментів.

Питання межі

Запитайте: «Чи є база даних межою спільного сервісу?» Якщо так — PostgreSQL. Якщо ні — SQLite може бути легітимною production-базою. Не недооцінюйте, як часто «ні» тихо перетворюється на «так», коли додаєте воркер, потім другий інстанс застосунку, потім адмін-дашборд з важкими запитами.

Цікаві факти та трохи історії

Трохи контексту допомагає, бо архітектурні вибори не були довільними. Це шрами від реального використання.

  1. SQLite з’явився в 2000 році як вбудована база, щоб уникнути оверхеду клієнт/серверних СУБД для конкретного проєкту; згодом став дефолтним «малим SQL» двигуном у світі.
  2. PostgreSQL має корені в 1980-х (проєкт POSTGRES в UC Berkeley), і його ДНК це показує: розширюваність, коректність і академічна одержимість транзакційною поведінкою.
  3. SQLite, ймовірно, найбільш розгорнута СУБД, бо вона постачається в телефонах, браузерах, ОС і безлічі додатків як бібліотека.
  4. PostgreSQL популяризував багату розширюваність через користувацькі типи, оператори та розширення; тому він став дефолтною платформою «SQL плюс» у багатьох сучасних стеків.
  5. WAL режим SQLite був доданий пізніше, щоб зменшити блокування записувачів і покращити конкурентність; це змінило уявлення про те, для чого підходить SQLite в production.
  6. MVCC у PostgreSQL означає, що старі версії рядків залишаються до очищення vacuum; це і фіча продуктивності, і операційний клопіт.
  7. SQLite відомий суворою портативністю файлу бази між архітектурами і версіями, але все одно залежить від поведінки файлової системи щодо довговічності.
  8. WAL у PostgreSQL теж називають WAL (той самий акронім, інша реалізація), і він лежить в основі реплікації та відновлення до точки у часі.
  9. Повідомлення «database is locked» у SQLite — не баг; це очевидний наслідок моделі блокувань. Баг — ваша думка, що він поводиться як серверна СУБД.

Реалії VPS: диски, пам’ять і сусіди

VPS — це не ноутбук і не керована база. Це невеликий шматок великої машини з розділеним IO і іноді непередбачуваними сусідами. Ваш вибір бази має це враховувати.

Дисковий IO — перша неправда в ваших бенчмарках

На VPS ваш «SSD» може бути швидким, а може бути «швидким, коли сусіди сплять». І SQLite, і PostgreSQL піклуються про поведінку fsync, але відчувають її по-різному:

  • SQLite пише в один файл бази (плюс журнал/WAL). Випадкові записи можуть бути болючими, якщо навантаження інтенсивне.
  • PostgreSQL пише в кілька файлів: дані й WAL-сегменти. WAL-записи більш послідовні і можуть бути лагідніші до реальних дисків, але при цьому з’являються фонові процеси і контрольні точки.

Пам’ять — це не просто «кеш»; це політика

SQLite сильно покладається на page cache ОС. Це нормально — Linux добре кешує. PostgreSQL має власні shared_buffers плюс кеш ОС. Якщо ви неправильно налаштуєте це на маленькому VPS, отримаєте подвійний кеш і позбавите систему ресурсів.

Модель процесів важлива при малій RAM

SQLite живе в процесі вашого застосунку. PostgreSQL використовує кілька процесів і має витрати пам’яті на з’єднання. На VPS з 1 GB RAM купа простих з’єднань може стати помилкою продуктивності, а не дрібницею. Якщо ви запускаєте Postgres на малому залізі, ви навчитесь любити connection pooling.

Операційний радіус ураження

Радіус ураження SQLite часто — «цей файл». Радіус ураження PostgreSQL — «цей кластер», але з кращими інструментами для ізоляції та відновлення. SQLite можна відновити, скопіювавши файл — якщо ви не скопіювали його в неправильний момент. PostgreSQL можна відновити, відігнавши WAL — якщо ви не тестували backup-и. Оберіть свій яд, а потім пом’якште його наслідки.

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

Нижче завдання, які ви можете виконати на VPS сьогодні. Кожне дає сигнал, а не відчуття. Мета — вирішувати на основі доказів: можливостей IO, потреб у конкурентності та ризиків відмови.

Завдання 1: Перевірте CPU та тиск пам’яті (чи вам взагалі дозволено запускати Postgres?)

cr0x@server:~$ lscpu | egrep 'Model name|CPU\(s\)'
CPU(s):                               2
Model name:                           Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz
cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           1.0Gi       220Mi       180Mi        12Mi       620Mi       690Mi
Swap:          1.0Gi         0B       1.0Gi

Що це означає: На 1 GB RAM Postgres можливий, але потрібно дисципліновано підходити (пулінг з’єднань, тюнінг пам’яті). SQLite буде відчуватися беззусильним.

Рішення: Якщо ви не можете дозволити собі кількасот МБ для Postgres плюс запас для застосунку, оберіть SQLite або апгрейд VPS.

Завдання 2: Визначте тип сховища і опції монтування (довговічність тут)

cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /
/dev/vda1 ext4 rw,relatime,errors=remount-ro

Що це означає: ext4 з relatime — нормальна конфігурація. Якщо бачите дивні опції, наприклад data=writeback або екзотичні мережеві FS, слід ставитися з підозрою до заяв про довговічність SQLite і тюнити Postgres відповідно.

Рішення: Якщо ви на мережевому або дивному сховищі, Postgres з протестованою поведінкою WAL+fsync зазвичай безпечніший, ніж «копіювати файл бази».

Завдання 3: Швидка перевірка латентності диска (майбутній квиток «бд повільна»)

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

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           3.10    0.00    1.20    0.40    0.10   95.20

Device            r/s     w/s   rkB/s   wkB/s  await  svctm  %util
vda              5.00    8.00   80.0   210.0   2.10   0.40   0.52

Що це означає: await в одиницях низьких цифр — прийнятно. Якщо бачите спайки 20–100ms, і SQLite, і Postgres постраждають, але в SQLite це виявиться як затримки в потоках застосунку.

Рішення: Високий IO wait підштовхує до використання Postgres з тюнінгом контрольних точок і, можливо, переходу на краще сховище; також це аргумент зменшити write amplification у будь-якому випадку.

Завдання 4: Виміряйте вартість синхронізації файлової системи (за це платять і SQLite, і Postgres)

cr0x@server:~$ sudo dd if=/dev/zero of=/var/tmp/fsync.test bs=4k count=25000 conv=fdatasync status=progress
102400000 bytes (102 MB, 98 MiB) copied, 1.52 s, 67.4 MB/s
25000+0 records in
25000+0 records out
102400000 bytes (102 MB, 98 MiB) copied, 1.52 s, 67.3 MB/s

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

Рішення: Якщо примусовий sync дорогий, SQLite потребує WAL + розумних synchronous налаштувань; Postgres потребує тюнінгу checkpoint-ів і не варто надто агресивно виставляти synchronous_commit для не критичних записів.

Завдання 5: Перевірте ліміти відкритих файлів (Postgres це більше зачепить)

cr0x@server:~$ ulimit -n
1024

Що це означає: 1024 — це тісно для Postgres під навантаженням з багатьма з’єднаннями й файлами. SQLite це зачепить менше, але ваш застосунок може.

Рішення: Якщо обираєте Postgres — підніміть ліміти через systemd або limits.conf; якщо не можете, зберігайте мало з’єднань і використовуйте пулер.

Завдання 6: Подивіться на кількість живих з’єднань (якщо вже натовп — SQLite стане гострим)

cr0x@server:~$ sudo ss -tanp | awk '$4 ~ /:5432$/ {c++} END {print c+0}'
0

Що це означає: Зараз Postgres відсутній, але важлива сама картина: скільки одночасних клієнтів буде.

Рішення: Якщо ви очікуєте десятки/сотні одночасних з’єднань — Postgres з пулером виграє. SQLite не має «з’єднань» в тому сенсі; там є «потоки й процеси, що борються за файл».

Завдання 7: Створіть SQLite базу з WAL і перевірте pragmas (зробіть її менш крихкою)

cr0x@server:~$ sqlite3 /var/lib/myapp/app.db 'PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA wal_autocheckpoint=1000;'
wal

Що це означає: WAL режим увімкнено; synchronous NORMAL — поширений компроміс (достатньо довговічності для багатьох застосунків, менше IO-болю ніж FULL).

Рішення: Якщо ви обираєте SQLite — будьте явні щодо pragmas. За замовчуванням це не «production-політика», а «загальні бібліотечні налаштування».

Завдання 8: Симулюйте конкурентні записи в SQLite (впізнайте стіну блокувань раніше)

cr0x@server:~$ for i in $(seq 1 20); do (sqlite3 /var/lib/myapp/app.db "BEGIN IMMEDIATE; CREATE TABLE IF NOT EXISTS t(x); INSERT INTO t VALUES($i); COMMIT;" >/dev/null 2>&1 &); done; wait; echo done
done

Що це означає: Це грубий тест. Якщо при повторі з більшим контентіоном ви почнете бачити «database is locked» в stderr — це ваша сирена тривоги.

Рішення: Якщо реальне навантаження схоже на це (багато записів) — перестаньте романтизувати SQLite і використайте Postgres.

Завдання 9: Встановіть Postgres і перевірте стан сервісу

cr0x@server:~$ sudo apt-get update -qq
...output...
cr0x@server:~$ sudo apt-get install -y postgresql
...output...
cr0x@server:~$ sudo systemctl status postgresql --no-pager
● postgresql.service - PostgreSQL RDBMS
     Loaded: loaded (/lib/systemd/system/postgresql.service; enabled)
     Active: active (exited)

Що це означає: На Debian/Ubuntu обгортка сервісу може показувати «active (exited)», тоді як кластерні юніти працюють. Не панікуйте; перевірте кластер.

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

Завдання 10: Перевірте готовність кластера Postgres

cr0x@server:~$ pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
16  main    5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log

Що це означає: Він онлайн. Є директорія даних і шлях до логу — дві речі, які ви навчитесь поважати.

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

Завдання 11: Перевірте налаштування довговічності та контрольних точок Postgres (не летіть навмання)

cr0x@server:~$ sudo -u postgres psql -c "SHOW synchronous_commit; SHOW fsync; SHOW full_page_writes; SHOW checkpoint_timeout; SHOW max_wal_size;"
 synchronous_commit
-------------------
 on
(1 row)

 fsync
-------
 on
(1 row)

 full_page_writes
------------------
 on
(1 row)

 checkpoint_timeout
--------------------
 5min
(1 row)

 max_wal_size
--------------
 1GB
(1 row)

Що це означає: За замовчуванням налаштування консервативні. Вони орієнтовані на безпеку на загальному апаратному забезпеченні, а не на ваш конкретний VPS.

Рішення: Якщо вам потрібна висока пропускна здатність записів — тюньте checkpoint-и й max_wal_size. Якщо потрібна максимальна безпека — тримайте консервативні значення і інвестуйте в резервні копії та тестування.

Завдання 12: Виявіть тиск vacuum (операційний «податок» Postgres)

cr0x@server:~$ sudo -u postgres psql -c "SELECT relname, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
 relname | n_dead_tup
---------+------------
(0 rows)

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

Рішення: Якщо ви обираєте Postgres — потрібно моніторити vacuum/bloat. Якщо ви не можете зобов’язатися до цього, простота SQLite починає виглядати привабливо — за умови, що конкурентність підходить.

Завдання 13: Виявіть повільні запити в Postgres (вирішіть, чи потрібні індекси або інша БД)

cr0x@server:~$ sudo -u postgres psql -c "SHOW shared_preload_libraries;"
 shared_preload_libraries
--------------------------
(1 row)

Що це означає: Якщо pg_stat_statements не увімкнено, ви втрачаєте ключову лінзу у поведінці запитів.

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

Завдання 14: Оцініть розмір БД і зростання (файл SQLite vs кластер Postgres)

cr0x@server:~$ du -sh /var/lib/myapp/app.db
48M	/var/lib/myapp/app.db
cr0x@server:~$ sudo -u postgres psql -c "SELECT pg_size_pretty(pg_database_size(current_database()));"
 pg_size_pretty
----------------
 7289 kB
(1 row)

Що це означає: SQLite — це один файл; Postgres — дерево директорій плюс WAL. Шляхи зростання відрізняються: файл SQLite росте і може не зменшуватися; Postgres може мати блоут, якщо не виконувати vacuum.

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

Завдання 15: Протестуйте workflow резервного копіювання/відновлення (це вирішує, чи спатимете ви)

cr0x@server:~$ sqlite3 /var/lib/myapp/app.db ".backup '/var/backups/app.db.bak'"
cr0x@server:~$ ls -lh /var/backups/app.db.bak
-rw-r--r-- 1 root root 48M Dec 30 03:12 /var/backups/app.db.bak
cr0x@server:~$ sudo -u postgres pg_dump -Fc -f /var/backups/pg.dump postgres
cr0x@server:~$ ls -lh /var/backups/pg.dump
-rw-r--r-- 1 postgres postgres 36K Dec 30 03:13 /var/backups/pg.dump

Що це означає: Обидві можна бекапити. Ключ — консистентність і тестування відновлення. SQLite потребує правильного методу бекапу; Postgres потребує відпрацювання прав доступу та ресторів.

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

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

Це послідовність при «щось повільно». Мета — ізолювати вузьке місце за хвилини, а не сперечатися в Slack годинами.

Перше: це CPU, пам’ять чи диск?

cr0x@server:~$ uptime
 03:20:11 up 12 days,  2:41,  1 user,  load average: 0.22, 0.40, 0.35
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      0 184320  28000 635000    0    0    10    25  120  180  3  1 95  1  0
 0  0      0 183900  28000 635200    0    0     0     0  110  170  2  1 97  0  0

Інтерпретація: Високий wa означає очікування диска; високий si/so — свапінг; високий r при низькому idle — тиск CPU.

Дія: Якщо хост свопить — спочатку вирішіть пам’ять (зменшіть з’єднання, тюньте Postgres, додайте RAM). Якщо IO wait високий — дивіться на checkpoint-и, вартість fsync і моделі записів.

Друге: база заблокована чи заблокована процесами?

SQLite: шукайте помилки блокувань в логах застосунку; перевірте довгі транзакції.

Postgres: перевірте блокуючі лока.

cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, wait_event_type, wait_event, state, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY pid;"
 pid  | wait_event_type | wait_event | state  | query
------+-----------------+------------+--------+-------
(0 rows)

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

Третє: це проблема запиту чи проблема ємності?

Для Postgres ідентифікуйте повільні запити і робіть EXPLAIN. Для SQLite перегляньте шаблони доступу й індекси та розгляньте винесення важких запитів поза гарячий шлях.

cr0x@server:~$ sudo -u postgres psql -c "EXPLAIN (ANALYZE, BUFFERS) SELECT 1;"
                                      QUERY PLAN
--------------------------------------------------------------------------------------
 Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.003..0.004 rows=1 loops=1)
 Planning Time: 0.020 ms
 Execution Time: 0.010 ms
(3 rows)

Інтерпретація: У реальній роботі шукайте послідовні скани по великих таблицях, величезні буферні хіти або час, витрачений на очікування IO.

Дія: Якщо запити повільні через відсутні індекси — виправляйте схему. Якщо повільно через повільний диск — змінюйте сховище або зменшуйте churn записів. Якщо повільно через конкурентність — виправляйте пулінг або обирайте правильну БД.

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

Це не моральні провали. Це передбачувані наслідки ставитися до бази як до чорної скриньки.

1) Поява «database is locked» спорадично (SQLite)

Симптоми: Помилки застосунку під навантаженням, спайки під час фонових завдань, запити падають і потім проходять при повторі.

Корінь: Декілька записувачів або довгі транзакції, що утримують write-лок. WAL допомагає, але один записувач усе одно потребує часу.

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

2) SQLite здається швидким, поки ви не розгорнете кілька інстансів

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

Корінь: Блокування файлу між процесами стає точкою конкуренції. Також: спільні файлові системи — пастка.

Виправлення: Не кладіть SQLite на NFS. Якщо вам потрібно більше ніж один процес-писач — використайте Postgres.

3) Postgres «повільний», але CPU простоює

Симптоми: Висока латентність, низький CPU, періодичні паузи.

Корінь: IO wait під час checkpoint-ів або fsync-важкий робочий набір; max_wal_size замале; погане сховище.

Виправлення: Збільшіть max_wal_size; тюньте checkpoint-и; перемістіть WAL на швидший диск, якщо можливо; обережно зменшіть synchronous налаштування для не критичних шляхів.

4) Postgres падає через багато з’єднань на малому VPS

Симптоми: Спайки пам’яті, OOM kills, «too many clients», випадкові тайм-аути.

Корінь: Модель «одне з’єднання на запит»; витрати пам’яті на з’єднання; відсутній пулінг.

Виправлення: Використайте PgBouncer; зменшіть max_connections; виставте розумний розмір пулу; виправте застосунок, щоб повторно використовувати з’єднання.

5) Резервні копії є, але відновлення не працює

Симптоми: Тест відновлення провалюється; права зіпсовані; бракує ролей; файл резервної копії SQLite пошкоджено або неконсистентний.

Корінь: Резервні копії зроблені неправильно (копія файлу SQLite під час запису) або не тестуються (дампи Postgres без глобалів/ролей).

Виправлення: Для SQLite використовуйте .backup або backup API; для Postgres проводьте drills з відновлення, включаючи ролі та схему; автоматизуйте перевірку.

6) Таблиці Postgres блоять і запити деградують за тижні

Симптоми: Використання диску росте швидше за дані; індекси роздуваються; запити повільні; vacuum працює постійно.

Корінь: MVCC мертві кортежі накопичуються; autovacuum не встигає; агресивні UPDATE/DELETE патерни.

Виправлення: Тюньте autovacuum для таблиць; уникайте hot updates де можливо; розгляньте партиціювання або періодичне технічне обслуговування.

7) Файл SQLite роздувається і ніколи не зменшується

Симптоми: Використання диску росте, навіть після видалень; VPS починає бракувати місця.

Корінь: SQLite переиспользує сторінки, але не завжди повертає простір файловій системі; фрагментація; великі видалення.

Виправлення: Періодичний VACUUM (дорогий); продумайте стратегію збереження; розгляньте розбиття великих таблиць або перехід на Postgres при інтенсивному churn.

8) «Ми вибрали Postgres бо це enterprise» і тепер ops тоне

Симптоми: Ніхто не відповідає за оновлення, vacuum, бекапи; БД стала «пет», а не «кеттл».

Корінь: Вибір Postgres без виділення операційної зрілості.

Виправлення: Або інвестуйте в базові операції (моніторинг, drills відновлення, план оновлень), або тримайте простіше зі SQLite, поки дійсно не потрібен серверний DB.

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

Міні-історія 1: Інцидент, спричинений неправильною припущенням (файл SQLite на «спільному сховищі»)

Компанія була середнього розміру, продукт був здоровий, і хтось придумав блискучу ідею: запустити два інстанси застосунку за балансувальником «для стійкості». База була SQLite, лежала на тому, що провайдер VPS рекламував як «спільне сховище», змонтоване в обох інстансах. Здавалося елегантно. Один файл. Два інстанси. Що може піти не так?

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

На виклику інженер знайшов переривчасті «database is locked» у логах, але не систематично. Гірше — були випадкові повідомлення «disk I/O error», що виглядали як апаратні проблеми. Але це не було апаратне. Це була файлова система і менеджер блокувань, які сперечались, хто володіє правдою між двома вузлами.

Неправильне припущення було тонким: «якщо сховище спільне, то блокування файлу теж спільні». На багатьох спільних файлових системах advisory locks не працюють як локальні ext4-локи, особливо при відмовах або затримках. SQLite не «зламався»; середовище порушило припущення, на яких воно базується, щоб забезпечити ACID.

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

Міні-історія 2: Оптимізація, що відбилась боком (Postgres затюнено для швидкості, заплатила тривогою про втрату даних)

Інша організація мала Postgres на малому VPS. Записи були інтенсивними: події, логи, лічильники. Команда хотіла зменшити латентність і прочитала блог про відключення деяких механізмів довговічності. Вони змінили налаштування, щоб зменшити fsync-навантаження і зробити коміти швидшими. Усі вітали. Графіки пішли вниз і праворуч.

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

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

Оптимізація відбилась боком, бо оптимізувала не те: стабільну латентність за рахунок передбачуваної довговічності. Є обґрунтовані причини послабити довговічність для еpheremal аналітики або кешів. Але вони використовували це для стану, що впливає на клієнтів.

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

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

Це менш драматично, і в цьому суть. Команда, що працювала SaaS на одному VPS, використовувала Postgres. Вони не були витончені. У них не було платформи. Але вони робили одне ритуально: щотижневі drills відновлення на тестовій VM з чеклістом.

У них був скрипт, який тягнув останній бекап, відновлював його, запускав невеликий набір sanity-запитів і підтверджував, що застосунок може запуститись проти нього. Також зберігався мінімальний runbook про те, як підняти відновлену БД, якщо первинна помре. Ніхто не любив це робити. Це як чистити зуби.

Потім розробник випадково виконав руйнівну міграцію в продакшн. Не навмисно. Просто помилковий environment variable і інструмент міграцій, що охоче послухався. Таблиці були видалені. On-call приглушив алерти, тихо поклявся і почав drill відновлення, який вони багато разів практикували.

У них все ще була погана година, але не поганий тиждень. Вони відновили, коректно прогнали міграції й відіграли вузьке вікно бізнес-подій з логів. CEO ніколи не дізнався, що таке «WAL», і це найвищий комплімент, який можна отримати від операцій.

Цитата (парафраз): «Ви не піднімаєтесь до виклику; ви відкатуєтесь до підготовки.» — парафраз ідеї, що часто цитують у надійності/ops колах

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

Чекліст A: Якщо ви схиляєтесь до SQLite (зробіть його production-ready)

  1. Підтвердіть реальність single-writer: перерахуйте всі шляхи, що виконують записи (веб-запити, воркери, cron, адмін-скрипти). Якщо більше одного актора одночасно — плануйте серіалізацію або міграцію.
  2. Використайте WAL режим: встановіть PRAGMA journal_mode=WAL.
  3. Поставте розумне synchronous: зазвичай NORMAL — хороший компроміс для VPS; використовуйте FULL, якщо не можете терпіти втрату нещодавніх записів при краші.
  4. Встановіть busy_timeout: нехай застосунок трохи зачекає, а не впаде одразу при контенції блокувань.
  5. Резервуйте правильно: використовуйте механізм backup SQLite, а не «cp файлу під час піку записів».
  6. Плануйте зростання файлу: моніторте розмір БД і вільне місце; плануйте періодичний VACUUM тільки якщо потрібно.
  7. Не кладіть SQLite на NFS/спільні томи: тільки локальний диск, якщо не хочете розбирати блокування через затримки.

Чекліст B: Якщо ви схиляєтесь до PostgreSQL (зробіть його нудним, стабільним і дешевим)

  1. Правильно розмірюйте з’єднання: тримайте max_connections розумним; використовуйте пулер для веб-застосунків.
  2. Налаштуйте пам’ять свідомо: тюньте shared_buffers консервативно на малій RAM; залиште запас для ОС-кешу та вашого застосунку.
  3. Увімкніть видимість запитів: вмикайте статистику запитів, щоб бачити, що повільне, раніше ніж користувачі почнуть скаржитись.
  4. Моніторте vacuum: слідкуйте за мертвими кортежами та активністю autovacuum; блоут — повільна течія.
  5. Резервні копії і тести відновлення: автоматизуйте обидва процеси. Бекап без відновлення — бажання.
  6. План оновлень: вирішіть, як ви будете обробляти мажорні та мінорні оновлення до того, як опинитесь під тиском.
  7. Керування диском: моніторьте простір для даних і WAL; уникайте роботи на 90% заповненні VPS.

Покроково: шлях без жалю (15 хвилин)

  1. Запустіть Завдання 1–4, щоб зрозуміти реалії RAM і IO.
  2. Перелічіть ваших писачів. Якщо більше одного конкурентного писача існує зараз або скоро — обирайте Postgres.
  3. Якщо SQLite все ще можливий — виконайте Завдання 7–8. Якщо під час іграшкового тесту з’являється контенція блокувань — обирайте Postgres.
  4. Якщо обираєте Postgres — запустіть Завдання 9–12 і підтвердіть, що можете тримати його здоровим на цьому VPS.
  5. Запустіть Завдання 15 і проведіть хоча б одне відновлення. Обирайте систему, шлях відновлення якої ви зможете реально виконати під стресом.

Жарт #2: Найшвидша база — це та, яку ви не загубили о 03:00, саме тому бекапи дають найкращий ROI з усіх фіч, які ви ніколи не покажете на демо.

Запитання й відповіді (FAQ)

1) Чи витримає SQLite production-трафік?

Так, якщо «production-трафік» означає переважно читання, невелику кількість записів і контрольовану модель конкурентності. Його використовують у багатьох реальних системах. Він просто не хоче бути вашим мульти-тенантним координатором записів.

2) Чи робить WAL режим SQLite «таким самим, як Postgres»?

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

3) Чи є Postgres overkill для малого VPS?

Іноді. Якщо VPS дуже крихітний і навантаження просте, Postgres може додати зайвих рухомих частин. Але якщо у вас декілька писачів або є траєкторія зростання, «overkill» швидко перетворюється на «дякую, що не змушували мене мігрувати в стресі».

4) Який найбільший прихований витратний фактор Postgres на VPS?

Управління з’єднаннями та пам’яттю. Без пулінгу і розумних лімітів Postgres може спалювати RAM на простаках і падати так, ніби це «випадкова нестабільність». Це не випадковість; це математика.

5) Який найбільший прихований витратний фактор SQLite на VPS?

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

6) Якщо я почну з SQLite, наскільки болісна міграція на Postgres?

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

7) Чи варто використовувати SQLite для кешу, а Postgres як джерело істини?

Може працювати, але не будьте випадково розподіленою системою. Якщо потрібен кеш — розгляньте in-memory кеши або Postgres-native стратегії. Якщо використовуєте SQLite як локальний кеш — тримайте його як витратний і відновлювальний.

8) Щодо довговічності: чи небезпечний SQLite?

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

9) Чи потрібна реплікація на VPS?

Не завжди. Для багатьох VPS-налаштувань перший виграш — надійні бекапи й drills відновлення. Реплікація корисна, коли у вас вимоги до uptime, що перевищують «відновити в межах X хвилин», і ви можете дозволити собі складність.

10) Як вирішити, чи у мого застосунку «декілька писачів»?

Якщо записи можуть виконуватись одночасно з більш ніж одного OS-процесу або контейнера (веб-воркери, job-воркери, заплановані таски, адмін-скрипти) — у вас кілька писачів. Якщо ви розгортаєте кілька інстансів застосунку — точно так.

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

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

Якщо ви обираєте SQLite

  • Увімкніть WAL і явне synchronous налаштування.
  • Додайте busy timeout і тримайте транзакції короткими.
  • Реалізуйте бекапи через backup-механізм SQLite і проведіть тест відновлення.
  • Запишіть жорстке правило: «без спільних файлових систем, без мульти-писачів».

Якщо ви обираєте PostgreSQL

  • Налаштуйте розумний connection pooling і ліміти негайно.
  • Увімкніть видимість запитів і слідкуйте за повільними запитами й блокуваннями.
  • Автоматизуйте бекапи і періодично виконуйте drills відновлення.
  • Моніторьте використання диску і стан vacuum до того, як це стане проблемою.

Версія без жалю — це не про вибір «найкращої» бази. Це про вибір бази, моделі відмов якої ви можете передбачити, спостерігати і відновлювати з людським ритмом на VPS.

← Попередня
Pentium 4 / NetBurst: найгучніша помилка ери ГГц
Наступна →
Proxmox «неможливо виділити пам’ять»: балонінг, перевиділення і як це налаштувати

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