MariaDB проти SQLite: чому «швидко локально» може бути повільним у продакшні

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

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

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

Реальна різниця: архітектура, а не синтаксис SQL

SQLite — це бібліотека, яку ви підключаєте до свого застосунку. MariaDB — це сервер, до якого ви підключаєтесь.
Це одне речення пояснює більшість сюрпризів з продуктивністю.

У випадку SQLite «база даних» — це файл, а двигун БД працює всередині вашого процесу. Читання може бути надзвичайно швидким,
бо ви уникаєте мережевих затримок, аутентифікаційних рукопотискань, перемикання контексту між клієнтом і сервером,
і часто потрапляєте в сторінковий кеш ОС. На тихій машині розробника з одним користувачем це майже нечестно.

MariaDB — це окремий процес (часто на іншому хості). Вона платитиме за накладні витрати, які можна виміряти: TCP, обробка з’єднань,
парсинг запитів, планування потоків. Але натомість дає властивості, які теж видно в метриках: надійне управління конкуренцією, зріле
буферування та фонове скидання, налаштовані I/O патерни, інструментування та оперативні регуліровки, створені для мультиорендного
середовища.

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

Дві метрики продуктивності, які люди плутають (а потім страждають)

  • Затримка: скільки часу займає один запит. «Мій запит відчувається повільно.»
  • Пропускна здатність: скільки запитів ви обробляєте за секунду під навантаженням. «Система падає при 20 RPS.»

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

Цитата, бо вона досі влучна

Все ламається, весь час. — Werner Vogels

Якщо ви розробили систему так, ніби файл завжди доступний, блок завжди неконкурентний, а диск завжди швидкий, ви спроектували демо.

Цікаві факти та історія (те, що пояснює сьогодні)

  1. «Безсерверний» дизайн SQLite був свідомим вибором: це вбудований двигун баз даних, а не демон, що робить його ідеальним для додатків і апаратних пристроїв.
  2. SQLite зберігає всю базу в одному файлі (плюс допоміжні файли, як WAL або журнал), що зручно операційно та чутливо до продуктивності.
  3. SQLite використовує блокування на рівні бази для записів (з нюансами в режимі WAL), що спрощує коректність, але обмежує паралельні записи.
  4. WAL (Write-Ahead Logging) у SQLite суттєво покращує паралельність читань/записів, відокремлюючи читачів від записувача — але не безмежно.
  5. MariaDB — це форк MySQL, створений після придбання Sun компанією Oracle; він зберіг екосистему MySQL з іншим моделем управління.
  6. InnoDB став дефолтним сховищем MySQL, бо забезпечував ACID-транзакції та відновлення після аварій у спосіб, що краще масштабується за старіші движки.
  7. Буферний пул InnoDB — одна з головних причин, чому MariaDB може обігнати у реальних навантаженнях: це спеціалізований кеш з розумною поведінкою, кращою за «те, що кешує ОС».
  8. SQLite повсюдно використовується — у мобільних пристроях, браузерах, вбудованих системах — бо історія розгортання (одна бібліотека, один файл) неперевершена, коли навантаження підходить.

Чому SQLite часто блискавично швидкий локально

Локальні тести — це найкращий випадок для SQLite. Ваш додаток і база діляться пам’яттю та CPU, і файл лежить на швидкому локальному SSD.
Сторінковий кеш ОС робить більшу частину роботи після прогріву. І оскільки ви, ймовірно, запускаєте один процес,
ви уникаєте найдорогішої частини дизайну SQLite: координації.

1) Немає мережі

MariaDB потребує сокета (TCP або Unix), протоколу й серверного потоку, що відповідає. Навіть коли це ефективно, це не безкоштовно.
SQLite — це виклики функцій. Ось чому вона відчувається швидкою.

2) «Це в кеші» (ви просто цього не помітили)

Коли ви запускаєте бенчмарк двічі й другий прогін швидший, ви, ймовірно, вимірюєте сторінковий кеш ОС, а не базу даних.
SQLite читає файл. Ядро кешує сторінки. Ваш другий прогін — це швидкість пам’яті. Вітаю: ви тестували RAM.

3) Один записувач, без конкуренції

Багато локальних налаштувань запускають один процес. Тут SQLite сяє. Один записувач може виконувати багато роботи в секунду,
особливо коли ви батчите транзакції і не робите fsync після кожного дрібного запису.

4) Простий накладний план запиту

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

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

Що змінює продакшн: конкуренція, зберігання та режими відмов

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

Конкуренція перетворює «швидко» на «заблоковано»

Саме тут конкуренція SQLite стає причиною більшості історій «в деві все ок». Типова помилка не в тому, що «SQLite повільна».
А в тому, що «SQLite чекає».

  • Кілька записувачів змагаються за той самий блок на рівні бази.
  • Довготривалі транзакції читання можуть заважати завершенню контрольних точок WAL, через що WAL росте і виникають зависання.
  • Таймаути busy неправильно налаштовані, що призводить до миттєвих помилок database is locked або довгих затримок.

Зберігання — це не просто «швидкість диска»

Надійність SQLite залежить від семантики файлової системи. Якщо файл на локальному POSIX-подібному файловому сховищі і все поводиться як треба, ви в безпеці.
Якщо файл на NFS, SMB, розподіленій ФС з «достатньо гарним» блокуванням або на драйвері з несподіваною поведінкою fsync, ви отримаєте все — від обриву продуктивності до ризику корупції.

MariaDB теж залежить від файлової системи, але її I/O-патерни й налаштування створені для грізних реалій:
груповий коміт, фонове скидання, doublewrite, адаптивне скидання та здорові дефолти для відновлення після аварій.

Налаштування надійності: ви завжди платите десь

Бенчмарки «швидко локально» часто тихо вимикають надійність. SQLite може працювати з synchronous=OFF або
NORMAL і виглядати приголомшливо. Але якщо в продакшні потрібно не втрачати дані при відключенні електрики або краху вузла,
ви включите надійність і знайдете, куди пішов час: fsync.

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

Наблюдання — це фіча продуктивності

Коли щось повільно о 2:00, питання «як ми бачимо, що відбувається?» стає єдиним. MariaDB має зріле інструментування: журнал повільних запитів, performance schema
(у MySQL; у MariaDB є подібні інструменти), статус движка, статистику буферного пулу та інформацію про з’єднання/потоки. SQLite можна інструментувати, але це зазвичай на вашому боці:
метрики на рівні застосунку, трасування та уважне логування навколо транзакцій.

Чому MariaDB (InnoDB) зазвичай перемагає в продакшні

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

InnoDB обробляє конкуренцію так, ніби люди роблять погані вибори

InnoDB використовує блокування на рівні рядка (і MVCC) для багатьох навантажень, дозволяючи одночасним записувачам рухатися вперед без частих блокувань.
Писанець SQLite має грубіший блок. WAL допомагає читачам співіснувати з записами, але не з багатьма записувачами так, як це робить OLTP-серверний двигун.

Буферний пул проти кешу ОС: ідея подібна, контроль інший

SQLite сильно покладається на сторінковий кеш ОС. MariaDB має InnoDB buffer pool — керований кеш з внутрішніми евристиками й видимістю.
Ви можете його розмірювати, моніторити коефіцієнти попадань і робити висновки.

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

Лог-структурована надійність і груповий коміт

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

Операційні фічі, що опосередковано впливають на продуктивність

  • Обробка з’єднань: пулінг, кеш потоків, max connections. У SQLite немає з’єднань у тому ж сенсі, але модель процесу вашого додатку стає вузьким місцем.
  • Реплікація: не безкоштовна, але дозволяє масштабувати читання й безпечніше проводити роботи з обслуговування.
  • Онлайн-зміни схеми: все ще складно, але можливі з правильними інструментами. Міграції в SQLite можуть заблокувати світ, якщо не бути обережним.
  • Бекапи: MariaDB підтримує консистентні снапшоти та стратегії стрімінгу; бекапи SQLite можливі, але вимагають обережної роботи з WAL/журналом і копіюванням файлів.

Жарт №2: Бенчмаркінг SQLite на вашому ноутбуці та оголошення перемоги — це як тестування моста велосипедом і називати його «готовим до вантажівок».

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

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

Завдання 1: Визначте файлову систему та опції монтування (SQLite це дуже цікавить)

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

Значення: Локальний ext4 з типовими опціями. Добрий базовий рівень для SQLite. Якщо ви бачите nfs, cifs або щось екзотичне — це червоний прапор.

Рішення: Якщо файл БД на мережевому сховищі, перемістіть його на локальний диск або переходьте на MariaDB/Postgres. SQLite на NFS — це шлях до інцидентів.

Завдання 2: Перевірте затримку диска під реальним навантаженням (ваша «повільна БД» може бути повільним сховищем)

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server)  12/30/25  _x86_64_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00    4.22    8.55    0.00   74.92

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz  aqu-sz  %util
nvme0n1         55.0   4200.0     1.0   1.79    1.20    76.36    90.0    8600.0     3.0   3.23   18.50    95.56    1.80  92.00

Значення: Високий w_await (18.5ms) з високим %util свідчить про насичення пристрою на записах. І SQLite, і MariaDB страждають, але SQLite може сильніше зависати, коли частота fsync висока.

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

Завдання 3: Підтвердіть режим журналювання та налаштування синхронізації SQLite (звичайний «швидкий локальний» трюк)

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

Значення: wal добрий для конкуренції читань/записів. synchronous=2 означає FULL. Надійно, повільніше.

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

Завдання 4: Перевірте, чи не зростає WAL (часто викликано довгими читачами)

cr0x@server:~$ ls -lh /var/lib/app/app.db*
-rw-r----- 1 app app 1.2G Dec 30 01:58 /var/lib/app/app.db
-rw-r----- 1 app app 3.8G Dec 30 02:10 /var/lib/app/app.db-wal
-rw-r----- 1 app app  32K Dec 30 01:59 /var/lib/app/app.db-shm

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

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

Завдання 5: Знайдіть довготривалі транзакції SQLite (на рівні додатку, але ви можете зробити висновки)

cr0x@server:~$ lsof /var/lib/app/app.db | head
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
api      1221  app    9u   REG  259,2 1288490188  77 /var/lib/app/app.db
worker   1304  app   11u   REG  259,2 1288490188  77 /var/lib/app/app.db

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

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

Завдання 6: Перевірте логи на симптоми контенції за блокуваннями

cr0x@server:~$ journalctl -u app-api -n 30 --no-pager | sed -n '1,8p'
Dec 30 02:12:01 server app-api[1221]: ERROR db: sqlite busy: database is locked
Dec 30 02:12:01 server app-api[1221]: WARN  db: retrying transaction attempt=1
Dec 30 02:12:02 server app-api[1221]: ERROR db: sqlite busy: database is locked
Dec 30 02:12:02 server app-api[1221]: WARN  db: retrying transaction attempt=2

Значення: Помилки busy/locked. Це колапс пропускної здатності: воркери витрачають час на повторні спроби замість роботи.

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

Завдання 7: Перевірте таймаут busy у SQLite (уникнути миттєвого фейлу, але не ховати проблему)

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

Значення: Немає таймауту busy; блокування призводять до миттєвих помилок.

Рішення: Встановіть адекватний busy timeout у додатку (наприклад, 2000–5000ms) і додайте повтори з джитером. Але розглядайте це як пластир; все одно виправляйте контенцію.

Завдання 8: Переконайтесь, що MariaDB справді використовує InnoDB і не робить щось дивне

cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'default_storage_engine';"
+------------------------+--------+
| Variable_name          | Value  |
+------------------------+--------+
| default_storage_engine | InnoDB |
+------------------------+--------+

Значення: InnoDB за замовчуванням, як і треба для продакшн OLTP.

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

Завдання 9: Перевірте розмір buffer pool MariaDB (поширена продакшн помилка)

cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+

Значення: 128MB buffer pool. Достатньо для крихітних БД, трагедія для чогось реального.

Рішення: Збільшіть його (часто 60–75% пам’яті на виділеному хості БД). Потім перевірте коефіцієнт потраплянь і тиск пам’яті. Не голодуйте ОС і інші сервіси.

Завдання 10: Виявлення навантаження на диск у MariaDB (fsync-податок)

cr0x@server:~$ mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_data_fsyncs'; SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Innodb_data_fsyncs| 98321 |
+-------------------+-------+
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| Innodb_os_log_fsyncs| 22110 |
+---------------------+-------+

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

Рішення: Якщо латентність корелює зі сплесками fsync, перевірте innodb_flush_log_at_trx_commit і затримки сховища. Не змінюйте надійність необережно; краще виправити I/O і батчувати записи.

Завдання 11: Знайдіть повільні запити в MariaDB (не вгадуйте)

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

Значення: Журнал повільних запитів увімкнений; поріг 1с.

Рішення: Використовуйте slow log для виявлення реальних винуватців. Якщо у вас цього немає в продакшні — ви дебагуєте в сліпу.

Завдання 12: Перевірте поточні очікування й блокування в MariaDB (шукаємо контенцію)

cr0x@server:~$ mariadb -e "SHOW FULL PROCESSLIST;" | head
Id	User	Host	db	Command	Time	State	Info
48	app	10.0.2.41:51244	prod	Query	12	Waiting for table metadata lock	ALTER TABLE events ADD COLUMN x INT
51	app	10.0.2.40:49810	prod	Query	11	Sending data	SELECT * FROM events WHERE created_at > NOW() - INTERVAL 1 DAY

Значення: Metadata lock від ALTER блокує запити. Це класичне продакшн уповільнення, що не пов’язане з «БД повільна», а з стратегією онлайн DDL.

Рішення: Припиніть робити блокуючі зміни схеми під час піку. Використовуйте методи онлайн-зміни схеми або плануйте вікна обслуговування.

Завдання 13: Переконайтесь, що ви випадково не запускаєте SQLite на overlay файловій системі

cr0x@server:~$ stat -f -c "%T" /var/lib/app/app.db
ext2/ext3

Значення: Файл на файловій системі сімейства ext (ext4 поводиться аналогічно). Якщо бачите overlayfs, можливо ви в шарі контейнера з неприємною поведінкою fsync.

Рішення: Помістіть файл БД на реальний персистентний том, а не в записувальний шар контейнера.

Завдання 14: Перевірте ліміти відкритих дескрипторів (SQLite + багато воркерів = сюрприз)

cr0x@server:~$ ulimit -n
1024

Значення: Низький FD ліміт. Під навантаженням ви можете вичерпати дескриптори і помилково діагностувати «повільну БД», бо все починає повторюватись.

Рішення: Підніміть ліміти для сервісу і перевірте налаштування systemd unit. Потім повторіть тест під навантаженням.

Завдання 15: Швидка перевірка CPU-тротлінгу (хмара може таке зробити)

cr0x@server:~$ mpstat 1 3
Linux 6.5.0 (server)  12/30/25  _x86_64_  (8 CPU)

01:20:11 PM  all  %usr %nice %sys %iowait %irq %soft %steal %idle
01:20:12 PM  all  18.0  0.0  6.0   2.0    0.0  1.0   0.0   73.0
01:20:13 PM  all  19.0  0.0  6.0   3.0    0.0  1.0   0.0   71.0

Значення: Тут немає очевидного steal. Якщо %steal високий, ваша «проблема БД» може бути через гіпервізор, що краде CPU.

Рішення: Якщо steal високий, змініть тип інстансу або хост, або ізолюйте БД. Налаштування запитів не вирішить чужий CPU.

Завдання 16: Підтвердіть налаштування надійності MariaDB (не запускайте випадково «режим бенчмарка»)

cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; SHOW VARIABLES LIKE 'sync_binlog';"
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog   | 1     |
+---------------+-------+

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

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

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

Коли продуктивність падає, часу на філософію немає. Вам потрібен 10-хвилинний триаж, що звужує поле проблем.
Ось план, який я використовую, коли питання стоїть «SQLite чи MariaDB — що повільно і чому?»

Перше: Чи чекає система на блокування або на I/O?

  • SQLite: шукайте в логах database is locked; перевірте розмір WAL; перевірте busy timeout; ідентифікуйте кількість записувачів.
  • MariaDB: дивіться SHOW FULL PROCESSLIST на очікування блокувань; перевіряйте статус InnoDB за потреби; дивіться slow query log на винятки.
  • Хост: запустіть iostat -xz і подивіться на високий await та високий %util.

Друге: Чи насичується якийсь ресурс?

  • CPU: високий %usr/%sys, низький iowait. Планування запитів, парсинг JSON або шифрування можуть домінувати.
  • Диск: високий iowait, високе очікування/утилізація диска. Ви обмежені IOPS або затримкою.
  • Мережа: MariaDB на віддаленому хості: перевірте RTT і втрачені пакети. «Швидкий запит» через повільну мережу все одно повільний.
  • Потоки/воркери: забагато воркерів додатку можуть зруйнувати SQLite через контенцію; забагато з’єднань MariaDB може додати перемикання контексту й тиск пам’яті.

Третє: Перевірте форму робочого навантаження (питання, що вирішує вибір БД)

  • Скільки одночасних записувачів у піку?
  • Чи записи малі й часті (гірший випадок) чи батчені (кращий випадок)?
  • Чи запускаєте ви довгі транзакції читання (звітність, експорт, аналітика) проти тієї ж БД, що OLTP?
  • Чи потрібні вам HA/реплікація/бекапи з мінімальним простоєм?

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

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

1) Симптом: «Випадкові сплески латентності, потім все таймаутиться»

Корінна причина: Контенція записувача SQLite; повтори в додатку підсилюють навантаження (ефект натовпу).

Виправлення: Зменшіть одночасних записувачів; реалізуйте чергу для записів; батчте записи; увімкніть WAL; встановіть busy timeout з джитером повторів. Якщо паралельні записи неминучі — мігруйте на MariaDB.

2) Симптом: «WAL SQLite росте безупинно»

Корінна причина: Довготривалі транзакції читання перешкоджають чекпойнту; або чекпойнти вимкнені/неправильно налаштовані.

Виправлення: Переконайтесь, що читачі швидко комітять; не загортайте довгі звіти в одну транзакцію; запускайте періодичні чекпойнти; відділіть аналітику від OLTP.

3) Симптом: «Було швидко, поки ми не перемістили БД на загальне сховище»

Корінна причина: Блокування та fsync семантика мережевих файлових систем; затримка й узгодженість блокувань вбивають SQLite.

Виправлення: Тримайте SQLite виключно на локальному диску; інакше переходьте на MariaDB з відповідним сховищем. Не намагайтесь «налаштувати навколо» NFS-блокувань з надією.

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

Корінна причина: Buffer pool InnoDB занадто малий; все читається з диска.

Виправлення: Збільшіть innodb_buffer_pool_size; перевірте робочий набір; додайте індекси. Зміряйте знову.

5) Симптом: «MariaDB гальмує під час змін схеми»

Корінна причина: Metadata locks або блокуючі ALTER; довгі транзакції перешкоджають завершенню DDL.

Виправлення: Використовуйте стратегію онлайн-зміни схеми; скорочуйте транзакції; виконуйте DDL поза піком; забеспечте інструменти міграції, безпечні для продакшну.

6) Симптом: «SQLite «працює», але ми втрачаємо дані після краху»

Корінна причина: Расслаблені налаштування надійності (synchronous=OFF), або підлягаюче сховище бреше про скидання даних.

Виправлення: Поверніть надійні налаштування; перевірте файлову систему і поведінку кешу пристрою; якщо потрібні сильні гарантії, переходьте на MariaDB з налаштованою надійністю і відповідним обладнанням.

7) Симптом: «Висока p99 латентність лише в продакшні»

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

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

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

Міні-історія 1: Інцидент через хибне припущення

Середня компанія побудувала внутрішній планувальник задач: таблиці черги, пул воркерів, повтори, як зазвичай.
Спочатку це був один двійковий файл з вбудованим SQLite. Швидко, дешево і розгортається будь-де.
Команді це подобалося, бо не треба було координуватися з групою баз даних.

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

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

Виправлення не було хитрим PRAGMA. Вони перемістили чергу задач у MariaDB, залишили SQLite для локального кешування там, де йому місце,
і додали просте правило: вбудовані бази — це одновузлові компоненти, якщо явно не доведено інше.
Із звіту про інцидент вирізнили речення, яке кожен SRE пізнає: «Ми припустили, що файлове сховище поводиться як локальний диск.»

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

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

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

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

План відновлення був нудним: повернути надійні налаштування, батчити вставки належним чином, використовувати multi-row inserts і забезпечити
сховище, яке може витримати швидкість записів без «хитань». Продуктивність знову покращилася — цього разу без ставок на удачу.

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

Малий сервіс, пов’язаний із платежами, працював на MariaDB з суворою оперрутиною: журнал повільних запитів увімкнений, щотижневий перегляд топ-винуватців
і звичка запускати міграції в стенді з даними, схожими на продакшн.
Це не було гламурно. Але це й не потрапляло до презентацій.

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

Завдяки звичці перевіряти плани і дивитись slow log, команда впіймала це до релізу.
Вони додали індекс, переписали запит, щоб він був sargable, і інцидент ніколи не стався.
Найефективніший інструмент продуктивності в продакшні все ще: «ми дивимось логи як дорослі».

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

Чекліст рішення: чи має це навантаження бути на SQLite або MariaDB?

  • Вибирайте SQLite коли:
    • Один вузол, локальний диск, ніяких спільних файлових систем.
    • Переважно читання, низька частота записів або можливість батчувати записи.
    • Потрібна нульова операційна складність і ви толеруєте обмежену паралельність.
    • Ви готові «масштабувати вгору», а не «масштабувати назовні».
  • Вибирайте MariaDB коли:
    • Багато одночасних записувачів — норма, а не виняток.
    • Потрібні HA/реплікація, онлайн-обслуговування та передбачувана поведінка під навантаженням.
    • Потрібні оперативні інструменти та видимість без інструментування всього вручну.
    • Очікується зростання, кілька сервісів або спільні шаблони доступу.

Покроково: заставити SQLite поводитись (коли це правильний інструмент)

  1. Тримайте файл БД на локальному диску, не на NFS/SMB/overlay шарах.
  2. Увімкніть WAL режим для навантажень з великою кількістю читань і поодинокими записами.
  3. Батчте записи: обгорніть кілька INSERT/UPDATE в одну транзакцію.
  4. Встановіть busy timeout і реалізуйте повтори з джитером, щоб уникнути ефекту натовпу.
  5. Тримайте транзакції короткими; не тримайте їх під час мережевих викликів або довгих обчислень.
  6. Слідкуйте за ростом WAL; робіть чекпойнти навмисно і уникайте довгих читачів.
  7. Будьте явними щодо надійності і документуйте, що ви можете втратити при краху.

Покроково: зробити MariaDB швидкою (без відключення безпеки)

  1. Підберіть розмір buffer pool, щоб вмістити робочий набір у межах пам’яті.
  2. Увімкніть і використовуйте slow query log; виправляйте запити, а не враження.
  3. Додайте правильні індекси; перевіряйте плани до і після.
  4. Батчте записи (multi-row inserts, prepared statements).
  5. Контролюйте кількість з’єднань через пулінг; уникайте тисяч одночасних з’єднань, якщо ви не любите перемикання контекстів.
  6. Забезпечте IOPS і виміряйте затримки диска; не вірте вчорашнім казкам про чудодійні томи.
  7. Плануйте зміни схеми, щоб уникнути штормів метаданих і блокувань.

FAQ

1) Чи «повільніший» SQLite за MariaDB?

Не завжди. SQLite може бути швидшою для одно-процесних локальних навантажень з невеликими або середніми наборами даних і низькою конкуренцією записів.
MariaDB зазвичай швидша (і стабільніша) при одночасних записувачах і мультикористувацькому доступі.

2) Чому SQLite здається миттєвою на моєму ноутбуці?

Ви вимірюєте наклад виклику функції плюс кешування ОС на локальному SSD з мінімальною конкуренцією. Це найкраще середовище.
Продакшн додає конкуренцію, тиск I/O і реальні витрати на надійність.

3) Чи робить WAL режим SQLite «придатною для продакшну»?

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

4) Чи можна запускати SQLite на NFS, якщо бути обережним?

Можна, але ви ставите свою доступність на заміну файлових блокувань і поведінку flush, якою ви не керуєте. Якщо вам потрібно спільне сховище —
використовуйте серверну базу. Файлове блокування SQLite — не найприємніший розподілений системний проект.

5) Чому MariaDB повільніша в моєму однопотоковому бенчмарку?

Бо ви включаєте наклад клієнт/сервер і, ймовірно, не навантажуєте те, для чого MariaDB призначена: конкурентність.
Чесний тест використовує реалістичну конкуренцію, реальні дані і вимірює p95/p99 латентність плюс пропускну здатність.

6) Який найпоширеніший вбивця продуктивності SQLite у продакшні?

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

7) Який найпоширеніший вбивця продуктивності MariaDB у продакшні?

Погане індексування і занадто малий buffer pool, поруч із затримками диска та недбало спланованими змінами схеми.
MariaDB прощає багато, але не читає думки.

8) Чи можу я зберегти SQLite і все одно масштабувати додаток горизонтально?

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

9) Чи варто відключати fsync/надійність, щоб зробити все швидше?

Лише з явним прийняттям ризику втрати даних і після того, як ви оптимізували батчування і забезпечили I/O. Вимикати надійність за замовчуванням —
перетворення проблем продуктивності на проблеми цілісності.

10) Що зазвичай болить при міграції з SQLite на MariaDB?

Сумісність запитів, припущення про ізоляцію транзакцій і оперативні завдання (бекапи, користувачі, зміни схеми).
Віддача — передбачувана конкуренція й інструменти. Плануйте час на міграцію і тюнінг планів запитів.

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

Якщо ви запускаєте SQLite в продакшні і бачите уповільнення:

  • Підтвердіть, що файл БД на локальному диску, а не на спільній/мережевій/overlay файловій системі.
  • Увімкніть WAL режим (якщо доречно), встановіть busy timeout і батчте записи.
  • Вимірюйте контенцію блокувань явно (логуйте повтори, відстежуйте тривалості транзакцій).
  • Вирішіть, чи намагаєтесь ви робити multi-writer OLTP з одним файлом БД. Якщо так — зупиніться і мігруйте.

Якщо ви запускаєте MariaDB і бачите уповільнення:

  • Увімкніть журнал повільних запитів (або перевірте, що він увімкнений), потім виправляйте топ-винуватців.
  • Підійміть innodb_buffer_pool_size до потрібного розміру і перевірте, що ви не читаєте з диска кожен запит.
  • Перевірте затримки сховища і fsync-навантаження; купіть IOPS, перш ніж вірити полунам.
  • Аудитуйте практику зміни схеми, щоб не самонаносити метадані lockdown.

Основний урок: локальна швидкість доводить, що ваш SQL працює. Продуктивність у продакшні доводить, що ваша архітектура працює.
Розглядайте це як різні етапи — і ви будете спокійніші.

← Попередня
Тести якості VPN в офісі: виміряйте затримку, джитер і втрати, щоб довести проблему з провайдером
Наступна →
Черга електронної пошти зростає — знайдіть винуватця, поки листування не зупиниться

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