PostgreSQL vs SQLite: Надійність проти простоти — що виходить з ладу першим

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

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

PostgreSQL і SQLite — обидва відмінні. Але вони ламаються по-різному. Це практична карта того, де кожен з них ламається першим,
чому, і як швидко діагностувати проблему, коли ваш пейджер починає свій танець.

Призма рішення: що саме ви оптимізуєте?

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

SQLite оптимізує безшовну коректність… поки ви не масштабуєте по неправильній осі

SQLite — це бібліотека. Немає сервера. Це не дрібниця; це змінює всю поверхню відмов. Ви отримуєте менше рухомих частин, менше портів, менше демонів, менше облікових даних, менше рукбуків.
Ваш процес додатку — це і процес бази даних.

Перше, що зазвичай ламається, — це не «доступність». Зазвичай це конкурентність записів або операційні припущення:
«Ми можемо покласти БД на NFS», або «у нас 20 воркерів постійно пишуть», або «нам не потрібен бекап, бо це просто файл».

PostgreSQL оптимізує передбачувану поведінку під навантаженням… і вимагає плату оператора

PostgreSQL — це сервер бази даних. Він спроєктований для спільного використання багатьма клієнтами, що роблять багато речей одночасно, і в цьому він дуже сильний.
Але ви успадковуєте класичні операційні обов’язки: налаштування пам’яті, керування з’єднаннями, WAL, реплікація, vacuum, оновлення, моніторинг,
і інколи питання «чому цей запит раптово став злочином?»

У PostgreSQL рідко першим ламається файл бази даних. Зазвичай це затримки (погані запити, IO-стали),
операційне відхилення (неправильно налаштований autovacuum, майже повне сховище) або людська помилка (неправильна настройка в проді).

Моя упередженість: якщо ваш додаток однонодовий, з помірним об’ємом записів, і ви готові «масштабувати вгору або рефакторити пізніше», SQLite — це подарунок.
Якщо вам потрібен multi-writer throughput, віддалений доступ, ізоляція між орендарями або серйозний HA, PostgreSQL — це дорослий у кімнаті.
Ви все одно можете нашкодити собі з обома; PostgreSQL просто дає більше способів зробити це за більший бюджет.

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

  • SQLite створено в 2000 році D. Richard Hipp для підтримки контракту ВМС США; він був задуманий як малий, надійний і самодостатній.
  • SQLite відомий як «безсерверний», тобто немає окремого демона; це бібліотека, підключена до вашого процесу, що змінює домени відмов і моделі розгортання.
  • SQLite прагне стабільного формату файлу між версіями. Ви часто можете переносити файл бази між машинами з мінімальними проблемами — якщо поважаєте правила файлової системи.
  • PostgreSQL походить від POSTGRES в UC Berkeley (1980-ті). Частина «SQL» з’явилася пізніше; культура правильності залишилася.
  • PostgreSQL рано ввів MVCC як основну модель конкурентності, тому читачі не блокують записувачів так, як у простіших системах блокувань.
  • WAL-режим SQLite став широко використовуватися для поліпшення конкурентності та продуктивності, відокремлюючи читання від записів.
  • WAL у PostgreSQL — це не тільки для довговічності; це основа реплікації й відновлення до точки в часі.
  • SQLite всюдисущий — у телефонах, браузерах, вбудованих пристроях — бо «це просто файл» ідеально підходить постачальникам апаратних рішень.
  • Походження VACUUM у PostgreSQL — це операційна вартість MVCC: «мертві» кортежі не зникають, поки система їх не прибере.

Що виходить з ладу першим: PostgreSQL проти SQLite

SQLite: перша відмова зазвичай — контенція або файловий шар

Біль SQLite не романтичний. Це не «відмова бази даних», це «чому запити застрягли», або «чому ми отримали ‘database is locked’»,
або «чому цей файл пошкоджено після ребута».

Типові «перші відмови» SQLite у продакшені:

  • Контенція записів: занадто багато одночасних записувачів, довгі транзакції або активний цикл контрольної точки.
  • Погане розміщення: файл бази на мережевих файлових системах або ненадійне сховище; поведінка файлових блокувань і гарантій довговічності дивна.
  • Неправильна налаштування довговічності: PRAGMA-настройки обрані для швидкості без розуміння моделі відкату при збої.
  • Бекап шляхом простого копіювання файлу в живому режимі: ви отримуєте файл, що виглядає чистим, але логічно неконсистентним (або просто пошкодженим).
  • Домен відмови на рівні процесу: пошкодження пам’яті, SIGKILL або викид контейнера вбиває і додаток, і БД одночасно.

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

PostgreSQL: перша відмова зазвичай — операційне відхилення або тиск на IO

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

Типові «перші відмови» PostgreSQL у продакшені:

  • Шторм з’єднань: забагато клієнтських з’єднань, витрати пам’яті, перемикання контекстів і контенція блокувань.
  • IO-стали: повільне сховище, неправильно підібраний WAL, піки чекпойнтів або насичені диски.
  • Затримки autovacuum: блоут, зростання таблиць/індексів, ризик wraparound transaction ID і погіршення планів виконання.
  • Сюрпризи реплікації: лаґ, слоти, що тримають WAL назавжди, скрипти відкату, які працювали, поки не перестали.
  • Погані плани запитів: відсутні індекси, застарілі статистики і іноді «ми вимкнули nested loops глобально, бо блог сказав».

Довговічність і захищеність від аварій: що насправді означає «commit»

Надійність починається на межі commit. Коли ваш код повертає «OK», що саме ви купили?
Не «мабуть». Не «в кеші». Не «можливо після того, як ядро це відчує». Що ви фактично гарантуєте?

Довговічність SQLite налаштовувана — і це і сила, і пастка

SQLite дає вам PRAGMA, які змінюють характеристики довговічності. Якщо ви їх не налаштували, успадковуєте безпечні за замовчуванням,
але команди, орієнтовані на швидкість, часто полюють за прискоренням і випадково торгують безпекою від аварій.

Ключові важелі SQLite:

  • journal_mode: DELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFF. WAL зазвичай найкращий практичний баланс.
  • synchronous: OFF, NORMAL, FULL, EXTRA. Нижчий рівень — швидше, але ризикованіше при втраті живлення.
  • locking_mode: NORMAL vs EXCLUSIVE; EXCLUSIVE може пришвидшити роботу, але здивує інші процеси.
  • temp_store: впливає на тимчасові об’єкти; може змінити IO-поведінку.

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

Довговічність PostgreSQL більш жорстка — і її легше осмислити

У PostgreSQL теж є регулятори довговічності, але культура і значення за замовчуванням схиляються до правильності. Основна модель:
коміт стає довговічним, коли запис WAL надійно збережено (в контексті вашої настройки synchronous_commit).

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

Рівень сховища вирішує, хто правий

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

Одна перефразована ідея, часто приписувана Werner Vogels (CTO Amazon): надійність приходить від побудови систем, які передбачають відмову і швидко відновлюються, а не від прикидатися, що відмови не буде.

Конкурентність: блокування, контенція та форма болю

Конкурентність SQLite: один записувач (переважно), багато читачів (зазвичай)

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

Що це означає на практиці:

  • Короткі транзакції — питання виживання. Довгі транзакції — самонанесення шкоди.
  • busy timeouts — не лікування; вони маскують черги, поки латентність не стане помітною для користувача.
  • Поведінка WAL checkpoint може створювати періодичні паузи, якщо її не контролювати (особливо на повільних дисках).

Конкурентність PostgreSQL: MVCC плюс блокування — краще, але складніше

PostgreSQL блискуче поводиться при змішаній читально-записній конкурентності, бо читачі не блокують записувачів у наївний спосіб. Але не плутайте «MVCC» з «відсутністю блокувань».
У Postgres багато блокувань: важких, легких і внутрішніх точок контенції.

Типова форма болю в Postgres:

  • Один повільний запит блокує інших, утримуючи блокування довше, ніж очікують.
  • Одна міграція змінює таблицю і викликає черги блокувань і таймаути.
  • Забагато з’єднань витрачають пам’ять і насичують CPU перемиканням контекстів.

Жарт №2: PostgreSQL дозволить робити вам майже що завгодно — іноді навіть те, про що ви потім будете заперечувати під час постмортему.

Бекапи й відновлення: простота проти гарантій

Бекапи SQLite прості лише якщо ви робите їх правильно

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

Безпечні шаблони:

  • Використовуйте SQLite backup API (через sqlite3 .backup або інтеграцію в застосунок).
  • Якщо ви використовуєте WAL: захоплюйте базу і стан WAL узгоджено (або робіть checkpoint перед копіюванням).
  • Регулярно перевіряйте відновлення. Бекап, який ви не відновлювали, — це чутка.

Бекапи PostgreSQL складніші — і набагато гнучкіші

Postgres має зрілі інструменти для логічних бекапів (pg_dump), фізичних бекапів (pg_basebackup)
і відновлення до точки в часі з використанням архівації WAL. Складність не є опцією, але вона дає справжні операційні можливості:
відновити до вчора, клонувати прод, відновитися після помилки оператора і побудувати репліки для читання.

Типова відмова не в «інструменті бекапу, що не працює». Це «ми ніколи не тестували відновлення», або «архівація WAL мовчки зламалася»,
або «ми заповнили диск WAL, бо replication slot його закріпив».

Операційна реальність: моніторинг, оновлення, міграції

Операції для SQLite: менше ручок, але ви керуєте життєвим циклом процесу

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

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

Операції для PostgreSQL: ви отримуєте потужні інструменти, але вони кусають

Операційна майстерність PostgreSQL виглядає як нудна послідовність: слідкувати за об’ємом WAL, лагом реплікації, здоров’ям vacuum, повільними запитами, зростанням диску та контенцією блокувань.
Оновлення керовані, але вони існують. Те саме стосується міграцій схеми.

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

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

Міні-історія 1: Інцидент, спричинений неправильним припущенням

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

Через тижні під час техобслуговування стався короткий мережевий збій. Додаток продовжував працювати. SQLite продовжував намагатися.
Логи показували періодичні помилки «database disk image is malformed» і «database is locked». Інструмент не впав повністю; він просто став недостовірним.
Користувачі повторювали запити. Повторні спроби посилили записи. Файл бази став місцем злочину.

Постмортем був незручним, бо ніхто не зробив однозначно дурного кроку. Вони зробили звучне припущення:
«Файл бази — це файл, тож будь-яке сховище файлів підійде». Але коректність SQLite залежить від конкретних семантик блокувань і довговічності.
Мережевий файловий шар переважно працював — поки не перестав.

Виправлення було нудним: перенести файл SQLite на локальне сховище, увімкнути WAL і створити реальний pipeline бекапів за допомогою .backup.
Згодом, коли використання зросло, вони мігрували на PostgreSQL, щоб кілька сервісів могли писати без того, щоб трактувати файлове блокування як алгоритм розподіленого консенсусу.

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

Інша команда використовувала PostgreSQL для клієнтського додатка з нормальним OLTP паттерном: багато читань, стабільні записи, іноді сплески.
Новий інженер помітив стрибки латентності під час чекпойнтів. Він прочитав кілька постів з тюнінгу і вирішив «зменшити накладні витрати на flush диска».
Він змінив налаштування, щоб Postgres став менше готовим виконувати fsync, і агресивно підстроїв параметри чекпойнта.

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

Оптимізація обміняла довговічність на продуктивність без письмового рішення про ризики. Поодинці налаштування були «допустимими».
Насправді бізнесова вимога була «не втрачати визнані оновлення», і семантика системи була змінена.

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

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

Платформна група вела PostgreSQL з streaming replication і консервативним підходом до бекапів: нічні base backups, безперервна архівація WAL
і щомісячна тренувальна відновлення. Це була не гламурна праця. Ніхто не отримував підвищення за «успішне відновлення №12».

Одного дня інженер запустив скрипт очищення даних в неправильному середовищі. Це не було злочином. Це була помилка м’язової пам’яті:
вкладка термінала, автозаповнення, Enter. Скрипт виконався швидко й зробив саме те, що йому сказали.

Команда виявила проблему за хвилини через моніторинг: раптове падіння кількості рядків і сплеск активності delete.
Оголосили інцидент, призупинили записи й обрали точку відновлення безпосередньо перед виконанням скрипта. Оскільки архівація WAL була здорова
й процедури відновлення були відрепетировані, вони виконали point-in-time recovery на новий кластер і переключили трафік.

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

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

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

Завдання 1 (SQLite): Визначити режим журналювання та рівень довговічності

cr0x@server:~$ sqlite3 /var/lib/app/app.db "PRAGMA journal_mode; PRAGMA synchronous;"
wal
2

Що це означає: увімкнено режим WAL; synchronous=2 означає FULL. Коміти більш довговічні, зазвичай повільніші, ніж NORMAL.

Рішення: Для продакшну на локальному SSD, WAL + FULL — безпечна базова конфігурація. Якщо затримка надто велика, тестуйте NORMAL, але задокументуйте ризик.

Завдання 2 (SQLite): Перевірити busy_timeout і помилки негайного блокування

cr0x@server:~$ sqlite3 /var/lib/app/app.db "PRAGMA busy_timeout;"
5000

Що це означає: клієнт чекатиме до 5 секунд на блокування перед помилкою.

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

Завдання 3 (SQLite): Запустити integrity check після збою або події на сховищі

cr0x@server:~$ sqlite3 /var/lib/app/app.db "PRAGMA integrity_check;"
ok

Що це означає: структура бази консистентна.

Рішення: Якщо вивід не ok, припиніть записи, зробіть копію для судово-патологічного аналізу і відновіть з відомого доброго бекапу.

Завдання 4 (SQLite): Інспектувати стан WAL/checkpoint

cr0x@server:~$ sqlite3 /var/lib/app/app.db "PRAGMA wal_checkpoint(TRUNCATE);"
0|0|0

Що це означає: чекпойнт успішний; кадрів у WAL немає; WAL усічений.

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

Завдання 5 (SQLite): Виконати безпечний онлайн-бекап

cr0x@server:~$ sqlite3 /var/lib/app/app.db ".backup '/var/backups/app-$(date +%F).db'"

Що це означає: використовується механізм бекапу SQLite, що створює консистентну копію.

Рішення: Віддавайте перевагу цьому над cp для живих баз. Потім запускайте PRAGMA integrity_check на бекапі як частину pipeline.

Завдання 6 (Linux): Підтвердити, що база не на мережевій файловій системі

cr0x@server:~$ df -T /var/lib/app/app.db
Filesystem     Type 1K-blocks     Used Available Use% Mounted on
/dev/nvme0n1p2 ext4 205113344 73214512 121345024  38% /

Що це означає: локальний ext4, не NFS/CIFS. Добре для припущень довговічності SQLite.

Рішення: Якщо бачите nfs або cifs, перегляньте доцільність SQLite для спільного доступу або перемістіть файл на локальний диск і реплікуйте на вищому рівні.

Завдання 7 (PostgreSQL): Перевірити, чи сервер насправді піднятий і приймає з’єднання

cr0x@server:~$ pg_isready -h 127.0.0.1 -p 5432
127.0.0.1:5432 - accepting connections

Що це означає: Postgres активний і відповідає на TCP-рівні.

Рішення: Якщо «rejecting» або «no response», перевірте логи, умови повного диску та статус відновлення перед тим, як звинувачувати додаток.

Завдання 8 (PostgreSQL): Визначити тиск з’єднань

cr0x@server:~$ psql -X -qAt -c "SELECT count(*) FROM pg_stat_activity;"
187

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

Рішення: Якщо кількість висока і зростає, впровадьте pooler і встановіть розумні обмеження в застосунку.

Завдання 9 (PostgreSQL): Швидко знайти блокуючі блокування

cr0x@server:~$ psql -X -qAt -c "SELECT blocked.pid, blocked.query, blocking.pid, blocking.query FROM pg_stat_activity blocked JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid)) WHERE blocked.wait_event_type='Lock';"
4123|UPDATE orders SET status='paid' WHERE id=$1;|3999|ALTER TABLE orders ADD COLUMN note text;

Що це означає: міграція блокує записові операції додатку.

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

Завдання 10 (PostgreSQL): Виміряти лаг реплікації (якщо є репліки)

cr0x@server:~$ psql -X -qAt -c "SELECT application_name, state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
replica1|streaming|00:00:00.120|00:00:00.180|00:00:00.450

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

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

Завдання 11 (PostgreSQL): Перевірити, чи autovacuum відстає

cr0x@server:~$ psql -X -qAt -c "SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
events|983421|2025-12-30 08:12:41+00
orders|221904|2025-12-30 08:03:10+00
sessions|110992|2025-12-30 08:15:02+00

Що це означає: накопичуються «мертві» кортежі; autovacuum працює, але може не встигати за інтенсивністю записів.

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

Завдання 12 (PostgreSQL): Виявити запити з найвищою латентністю (потрібно pg_stat_statements)

cr0x@server:~$ psql -X -qAt -c "SELECT calls, mean_exec_time::numeric(10,2), left(query,80) FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 5;"
142|812.34|SELECT * FROM reports WHERE org_id = $1 ORDER BY created_at DESC LIMI
9211|203.11|UPDATE sessions SET last_seen = now() WHERE id = $1

Що це означає: один запит постійно повільний; інший помірно повільний, але викликається часто.

Рішення: Спочатку виправте постійно повільний запит, якщо він блокує шляхи користувача; потім оптимізуйте часто викликані помірні запити, якщо вони домінують у CPU/IO.

Завдання 13 (OS): Перевірити місце на диску (бо бази не люблять сюрпризів)

cr0x@server:~$ df -h /var/lib/postgresql
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  196G  189G  1.9G  99% /

Що це означає: ви в одному лог-файлі від поганого дня.

Рішення: Звільніть місце негайно. Потім налаштуйте алерти на 80/90/95% і сплануйте розширення ємності. Дефіцит диску — це не «пізніше» проблема.

Завдання 14 (PostgreSQL): Проінспектувати тиск росту WAL

cr0x@server:~$ psql -X -qAt -c "SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),'0/0')) AS wal_bytes_since_origin;"
1643 GB

Що це означає: грубий індикатор, але підказує значну генерацію WAL з часом (або просто що origin нульовий). Краще: перевірити розмір каталогу pg_wal і replication slots.

Рішення: Якщо каталог WAL великий, перевірте застряглі replication slots або зламану архівацію перед тим, як міняти параметри чекпойнта.

Завдання 15 (PostgreSQL): Перевірити replication slots, що можуть фіксувати WAL

cr0x@server:~$ psql -X -qAt -c "SELECT slot_name, active, restart_lsn FROM pg_replication_slots;"
analytics_slot|f|1A/2F000000

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

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

План швидкої діагностики

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

Перше: визначте, до якого класу відмови ви належите

  • Повний даун: не вдається підключитися / файл не відкривається / помилки корупції.
  • Частковий даун: з’єднання працюють, але запити тайм-аутяться.
  • Неправильні результати: відсутні дані, неконсистентні читання або часткові оновлення.

Друге: перевірте субстрат (диск і файлову систему) перед тим, як звинувачувати SQL

  • Чи диск майже заповнений?
  • Чи IO насичений або повільний?
  • Чи база на мережевій файловій системі (ризик для SQLite) або на ненадійному томі?

Третє: перевірте сигнали контенції

  • SQLite: «database is locked», довгі транзакції, затримки WAL checkpoint, busy timeouts.
  • PostgreSQL: очікування блокувань, кількість з’єднань, повільні запити, відставання autovacuum, лаг реплікації.

Четверте: вирішіть найшвидше безпечне пом’якшення

  • SQLite: зменшіть конкуренцію записів, скоротіть транзакції, увімкніть WAL, додайте busy_timeout (як пластир), перенесіть на локальний диск.
  • Postgres: вбийте блокуючий запит, призупиніть міграцію, обережно додайте індекс, масштабируйте IO, увімкніть pooling або перемкніть failover, якщо primary нездоровий.

П’яте: запишіть режим відмови в одному реченні

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

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

Помилки SQLite

  • Симптом: часті помилки «database is locked» під навантаженням
    Корінь: забагато одночасних записувачів або довгі транзакції; стандартний rollback journal mode посилює контенцію
    Виправлення: увімкнути WAL, зменшити конкуренцію записів, тримати транзакції короткими, додати таргетовані батчі, встановити розумний busy_timeout.
  • Симптом: корупція бази після ребута/втрати живлення
    Корінь: synchronous=OFF/NORMAL обрано без розуміння; сховище бреше про fsync; або БД розміщена на непридатній файловій системі
    Виправлення: використовувати локальний диск, встановити synchronous=FULL для критичних даних, уникати мережевих файлових систем, перевіряти integrity_check і проводити відновлення з бекапів.
  • Симптом: бекап відновлюється, але додаток поводиться дивно (відсутні рядки, помилки обмежень пізніше)
    Корінь: жива копія файлу під час активних записів; WAL не захоплено узгоджено
    Виправлення: використовувати SQLite backup API; робити checkpoint відповідно; тестувати відновлення і запускати integrity_check на бекапах.
  • Симптом: періодичні стрибки латентності кожні кілька хвилин
    Корінь: WAL checkpoint викликає IO-сплески, часто ускладнюється повільними дисками або довгими читачами, що перешкоджають завершенню checkpoint
    Виправлення: налаштувати стратегію чекпойнтів, зменшити довгі читальні транзакції, розглянути ручний checkpoint у низьке навантаження.

Помилки PostgreSQL

  • Симптом: високий CPU і помилки «too many connections»
    Корінь: патерни один-з’єднання-на-запит; відсутність пулінгу; збільшення max_connections до проблемної пам’яті
    Виправлення: використовувати connection pooler, обмежити з’єднання, виправити повторне використання з’єднань у застосунку, моніторити pg_stat_activity.
  • Симптом: сплески латентності записів і періоди інтенсивного fsync
    Корінь: сплески чекпойнтів, WAL на повільному сховищі, неправильно підібрані shared_buffers/checkpoint параметри або насичення IO
    Виправлення: розмістити WAL на швидкому диску, налаштувати checkpoint_timeout і checkpoint_completion_target обачно, вимірювати IO системними інструментами.
  • Симптом: запити повільнішають тижнями, індекси ростуть, використання диску надувається
    Корінь: autovacuum не справляється; накопичення блоуту; довгі транзакції перешкоджають очищенню
    Виправлення: налаштувати autovacuum для конкретних таблиць, усунути довгі транзакції, розглянути партиціонування, виконувати vacuum/analyze де потрібно.
  • Симптом: диск наповнюється WAL несподівано
    Корінь: replication slot зафіксувався; репліка офлайн; архівація WAL зламана і утримання росте
    Виправлення: перевірити replication slots, відновити споживачів, видалити непотрібні слоти, налаштувати алерти на розмір pg_wal і помилки архіватора.
  • Симптом: міграція викликає масштабні таймаути
    Корінь: DDL захоплює блокування; довгі транзакції блокують DDL і навпаки
    Виправлення: використовувати шаблони міграцій, що мінімізують блокування, встановлювати lock timeouts, розгортати в вікна низького навантаження, перевіряти блокування через pg_blocking_pids.

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

Чеклист A: Коли SQLite — правильний вибір (і як не пожалкувати)

  1. Підтвердіть форму навантаження: переважно читання, обмежені одночасні записи, короткі транзакції.
  2. Розмістіть БД на локальному диску: уникайте NFS/CIFS і «shared volumes» з невизначеною семантикою блокувань.
  3. Увімкніть WAL режим: використовувати WAL для кращої читально-записної конкурентності.
  4. Вибирайте рівень довговічності свідомо: synchronous=FULL для критичних записів; документуйте, якщо обираєте NORMAL.
  5. Реалізуйте бекапи через backup API: плануйте, ротацію і перевірки відновлення.
  6. Додайте перевірки цілісності: запускати integrity_check в CI для артефактів бекапу або після некоректного завершення.
  7. Сплануйте вихід: визначте поріг, при досягненні якого мігруєте на Postgres (записувачі, віддалений доступ, кілька інстансів).

Чеклист B: Коли PostgreSQL — правильний вибір (і як зробити його нудним)

  1. Правильно підійміть з’єднання: не ототожнюйте «більше з’єднань» з «більшою пропускною здатністю». Використовуйте пулінг.
  2. Розмістіть WAL на хорошому сховищі: диски з низькою затримкою важливі більше, ніж здається.
  3. Увімкніть необхідну видимість: логування повільних запитів, pg_stat_statements, моніторинг блокувань і реплікації.
  4. Зробіть vacuum першокласним: слідкуйте за блоутом, мертвими кортежами і довгими транзакціями.
  5. Бекапи + тренування відновлення: оберіть logical/physical/PITR залежно від RPO/RTO і регулярно репетируйте відновлення.
  6. Зміни схеми — це розгортання: практикуйте безпечні міграції і встановлюйте lock timeouts.
  7. Майте план відкату: навіть якщо він ручний — запишіть його і тестуйте у спокійному стані.

Покроково: вибір між ними в реальному проєкті

  1. Запишіть терпимість до відмов: прийнятна втрата даних (RPO) та прийнятний час простою (RTO).
  2. Квантифікуйте конкурентність: кількість одночасних записувачів і максимальний час транзакції в критичному шляху.
  3. Визначте топологію розгортання: однонодовий vs мультиінстансний; потрібен віддалений доступ; потрібні репліки.
  4. Обирайте найпростішу річ, що відповідає SLO: SQLite, якщо однонодовий і низька конкуренція записів; Postgres в інших випадках.
  5. Прототипуйте гірший сценарій: навантажувальне тестування записів; інжекція відмов (kill -9, ребут в staging, симуляція затримки диска).
  6. Операціоналізуйте: бекапи, алерти, дашборди і тести відновлення перед тим, як вважати роботу «завершеною».

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

1) Чи SQLite «менш надійний» за PostgreSQL?

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

2) Яким найрезультативнішим способом SQLite відмовляє в продакшні?

Контенція блокувань: забагато записувачів або занадто довгі транзакції. Симптоми — «database is locked», таймаути або помітна для користувача латентність.
Виправлення радше архітектурне (зменшити конкуренцію записів), а не магічне PRAGMA-тюнінг.

3) Чи можна запускати SQLite на NFS або спільному Kubernetes томі?

Можна, але ви ставитеся на ризик через семантику файлової системи, яку, ймовірно, не тестували під відмовами. Якщо потрібен спільний доступ між вузлами,
PostgreSQL є безпечнішим дефолтом. Якщо мусите використовувати SQLite — тримайте його на node-local persistent storage і вважайте його як стан інстансу.

4) Чи робить WAL режим SQLite «multi-writer»?

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

5) Який найпоширеніший спосіб, як PostgreSQL відмовляє першим?

Операційне перевантаження: забагато з’єднань, насичення IO або відставання autovacuum. Postgres зазвичай зберігає коректність; він просто стає повільним або застрягає за блокуваннями.

6) Якщо PostgreSQL потужніший, чому не використовувати його завжди?

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

7) Чим відрізняються бекапи у сенсі «що виходить з ладу першим»?

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

8) Що простіше відлагоджувати під час інциденту?

Зазвичай PostgreSQL. У вас є видимість: pg_stat_activity, блокування, статистика запитів, реплікаційні view. Дебаг SQLite часто починається з логів застосунку і поведінки файлу/IO на рівні ОС.
Простота SQLite зменшує поверхню відмов, але коли вона ламається, причина часто поза SQL.

9) А як щодо ризику корупції даних?

Обидві системи спроєктовані уникати корупції. Більший ризик — це середовище: ненадійне сховище, небезпечні налаштування і неправильні процедури бекапу.
SQLite більш вразливий до особливостей файлової системи, бо це один файл із очікуваннями блокувань. PostgreSQL більше піддається операційним помилкам та подіям «диск заповнений».

10) Який чистий шлях міграції, якщо я починаю зі SQLite?

Плануйте це заздалегідь: тримайте сумісність схем, уникайте SQLite-специфічних штук і зробіть інструмент міграції, який може детерміністично експортувати/імпортувати.
Коли час настане, виконуйте dual-write або контрольований cutover з перевіркою, а не п’ятничний «це просто SQL».

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

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

А далі — зробіть непривабливу, але важливу роботу:

  • Для SQLite: увімкніть WAL + оберіть свідомий рівень synchronous, перенесіть БД на локальне сховище і бекапуйте через backup API. Протестуйте відновлення.
  • Для PostgreSQL: обмежте з’єднання і додайте пулінг, стежте за блокуваннями і здоров’ям vacuum, впровадьте бекапи з тренуваннями відновлення. Розглядайте міграції як продові зміни.
  • Для обох: налаштуйте алерти на місце на диску, перевірте припущення про файлову систему і запишіть ваш модель відмов простими словами.

Переможна база даних — та, чиї відмови ви відрепетирували. Програшна — та, яку ви вибрали, бо вона здавалася простою, до того моменту, поки це не стало неправдою.

← Попередня
Аварії оновлень: як один поганий патч може вивести з ладу весь світ
Наступна →
Чи повернеться гонка тактових частот? Не так, як ви думаєте

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