Пікові записи не з’являються ввічливо. Вони приходять гуртом: виконавці завдань пробуджуються одночасно, черга очищується після розгортання, мобільні клієнти перепідключаються після тунелю в поїзді, або “ой” backfill, який ви пообіцяли запустити «повільно». Запитання не в тому, чи може ваша база даних записувати. Запитання в тому, чи може вона записувати багато, зараз, не перетворивши ваш on-call на хобі.
MariaDB та SQLite обидві можуть зберігати ваші дані. Але під час сплесків вони поводяться як різні види. MariaDB — це сервер із контролем конкуренції, фоновим скиданням на диск, буферними пулами та довгою історією виробничих навантажень. SQLite — це бібліотека, що живе всередині процесу, неймовірно ефективна і дуже проста в обслуговуванні — допоки ви не попросите її працювати як натовп одночасних записувачів.
Справжнє питання: що означає «сплеск» для вашої системи
«Піковий запис» — це розмите словосполучення, яке породжує дорогі непорозуміння. Щонайменше чотири різні тварини, які люди називають сплеском:
- Короткий піковий стрибок, висока конкуренція: 500 запитів приходять одночасно, кожен робить невеликий вставлений запис.
- Тривалий сплеск: 10× від звичайної швидкості записів протягом 10–30 хвилин (пакетні задачі, backfill).
- Вибух латентності в довгому хвості: середній пропуск виглядає нормально, але кожні 20 секунд коміти затримуються на 300–2000 мс.
- I/O-скеля: диск або система зберігання вдаряє по стіні скидання (fsync/flush cache), і все вишиковується в чергу за нею.
MariaDB проти SQLite у контексті «сплесків» — це в основному про те, як вони поводяться при конкуренції і як вони платять за надійність. Якщо у вас завжди лише один записувач і ви можете терпіти деяке чергування, SQLite може бути надзвичайно ефективним. Якщо у вас багато записувачів, багато процесів або потрібно продовжувати обслуговувати читання під час інтенсивних записів, MariaDB зазвичай є «дорослим» у кімнаті.
Але й там є пастки. Пастка SQLite — це блокування. Пастка MariaDB — це думати, що сервер бази даних є вузьким місцем, коли насправді проблеми зі сховищем (або ваша політика комітів).
Кілька фактів і історія, які справді важливі
Декілька контекстних моментів, які короткі, конкретні і дивно передбачувані для поведінки під сплесками:
- SQLite — це бібліотека, не сервер. Немає окремого демона; ваш додаток лінкує її і безпосередньо читає/записує файл БД. Це і суперсила продуктивності, і операційне обмеження.
- Початковий дизайн SQLite оптимізовано під вбудовані системи. Він став популярним на десктопі/мобільних пристроях, бо це «просто файл» і не потребує DBA.
- WAL-режим у SQLite введено для покращення конкуренції. Він розділяє читання та записи додаванням до журналу перед записом, дозволяючи читачам працювати під час записів — до певної межі.
- SQLite усе ще має правило одного записувача на рівні бази даних. WAL допомагає читачам, але кілька одночасних записувачів усе одно серіалізуються на write lock.
- MariaDB — це форк MySQL. Форк відбувся після придбання Sun Oracle; MariaDB стала «дружнім спільноті» продовженням для багатьох організацій.
- InnoDB став дефолтним рушієм MySQL/MariaDB не випадково. Він побудований навколо MVCC, redo журналів, фонового скидання і відновлення після аварій — фіч, які важливі під час сплесків.
- Продуктивність MariaDB під сплесками сильно залежить від поведінки fsync. Ваша політика скидання redo журналу може перенести біль від «кожен коміт затримується» до «деякі коміти затримуються, але загальна пропускна здатність зростає». Це винагорода за компроміс.
- Більшість інцидентів «база даних повільна» під час пікових записів — насправді «сховище повільне». База даних просто перша визнає проблему, блокуючись на fsync.
Анатомія шляху запису: MariaDB/InnoDB проти SQLite
SQLite: один файл, один записувач, мінімум церемоній
SQLite пише в один файл бази даних (плюс, у WAL-режимі, файл WAL та файл спільної пам’яті-shm). Ваш процес виконує SQL; SQLite перетворює його на оновлення сторінок. Під час коміту транзакції SQLite має забезпечити надійність згідно з вашими pragma налаштуваннями. Це зазвичай означає примусове записування на стійке сховище за допомогою викликів типу fsync, залежно від платформи і файлової системи.
Під час сплесків критичний момент SQLite — наскільки швидко вона може пройти через «отримати write lock → запис сторінок/WAL → політика синхронізації → звільнити lock». Якщо комітів багато і вони маленькі, накладні витрати домінують через виклики синхронізації і передачу блокувань. Якщо коміти пакетуються, SQLite може летіти.
WAL-режим змінює форму: записувачі додають у WAL, а читачі можуть продовжувати читати головну знімок БД. Але все одно лише один записувач одночасно, і чекпоїнти можуть стати другим видом сплеску (більше про це нижче).
MariaDB/InnoDB: конкуренція, буферизація і фоновий I/O
MariaDB — це серверний процес з кількома робочими потоками. InnoDB підтримує buffer pool (кеш) для сторінок, redo лог (write-ahead) і часто undo лог для MVCC. Коли ви комітите, InnoDB записує redo записи і — залежно від налаштувань — скидає їх на диск. Забруднені сторінки скидаються у фоні.
Під час сплесків суперсила InnoDB у тому, що він може приймати багато одночасних записувачів, ставити роботу в чергу і згладжувати її фонового скидання — якщо ви правильно підібрали розміри і ваш I/O витримує навантаження. Його слабкість у тому, що він все ще може вдаритись у жорстку стіну, коли redo лог або скидання dirty сторінок стає критичним, і тоді латентність стрибкоподібно зростає.
Є перефразована ідея від Werner Vogels (CTO Amazon), яку операційні люди часто повторюють, бо вона постійно вірна: усе ламається, тож проектуйте з огляду на відновлення і мінімізуйте зону ураження
(перефразована ідея). У світі сплесків це часто означає: очікуйте write amplification і очікуйте, що диск буде першим, хто поскаржиться.
Хто краще витримує сплески (і коли)
Якщо ви хочете чесне правило: SQLite витримує сплески без драми, коли ви можете сформувати навантаження записів у менше транзакцій і у вас небагато записувачів процесово. MariaDB витримує сплески без драми, коли у вас багато одночасних записувачів, декілька інстансів додатка і вам потрібна передбачувана поведінка під напругою — за умови, що ваше сховище і конфігурація не саботують вас.
SQLite перемагає, коли
- Один процес або контрольовані записувачі: один потік запису, черга або виділений процес запису.
- Короткі транзакції, пакетні коміти: ви можете комітити кожні N записів або кожні T мілісекунд.
- Локальний диск, низька латентність fsync: NVMe, а не хиткий мережевий файловий шар.
- Ви хочете простоти: немає сервера, менше рухомих частин, менше приводів прокидатися о 3 ранку.
- Читано-важке навантаження з періодичними сплесками: WAL-режим може зберегти читання швидкими під час записів.
SQLite програє (голосно), коли
- Багато одночасних записувачів: вони серіалізуються, і ваші потоки додатка накопичуються за «database is locked».
- Багато процесів одночасно пишуть: особливо на зайнятих хостах або контейнерах без координації.
- Чекпоїнтинг стає сплеском: WAL зростає, тригериться checkpoint, і вмить ви отримуєте ще один write-шторм у середині існуючого.
- Сховище має дивну поведінку fsync: деякі віртуалізовані чи мережеві диски роблять надійність надзвичайно дорогою або непослідовною.
MariaDB перемагає, коли
- У вас реальна конкуренція: багато інстансів додатка, що одночасно пишуть.
- Потрібні операційні інструменти: реплікація, бекапи, online schema changes, hooks для спостереження.
- Потрібно ізолювати навантаження: buffer pool поглинає сплески, thread pool і черги можуть запобігти повному колапсу.
- Потрібні передбачувані семантики ізоляції: MVCC з консистентними читаннями під навантаженням записів.
MariaDB програє, коли
- Ваш диск не встигає скидувати: flush redo журналу зупиняє світ; латентність вибухає.
- Ви неправильно розмірили buffer pool: занадто малий — буде трясіння кешу; надто великий — з’явиться драма з кешем ОС і свапом.
- Ви «налаштовуєте» надійність всліпу: ви купуєте пропускну здатність, продаючи майбутньому собі інцидент втрати даних.
- Ваша схема створює гарячі точки: лічильники на одному рядку, погані індекси чи монотонні вставки, що борються за одні структури.
Жарт №1: SQLite — друг, який завжди вчасно — поки не покличете ще трьох друзів говорити одночасно, тоді він просто зачиняє двері.
Налаштування надійності: за що ви насправді платите fsync
Пікові записи — це місце, де налаштування надійності перестають бути теоретичними. Вони стають рахунком, який вашому сховищу треба заплатити негайно.
Ричаги надійності в SQLite
SQLite відкриває налаштування надійності через pragmas. Основні для сплесків:
- journal_mode=WAL: зазвичай рекомендований варіант для конкурентних читань і стабільної продуктивності записів.
- synchronous: контролює, наскільки агресивно SQLite синхронізує дані на диск. Вища надійність зазвичай означає більші витрати на fsync.
- busy_timeout: не підвищує пропускну здатність, але запобігає марним відмовам, чекаючи на блокування.
- wal_autocheckpoint: контролює, коли SQLite намагається робити checkpoint (переносити вміст WAL у головний файл БД).
Тут тонкість: у WAL-режимі система може здаватися чудовою, поки WAL не виросте і checkpoint не стане неминучим. Цей «checkpoint-податок» часто проявляється як періодичні стрибки латентності, що виглядають як «хрипіння» бази даних. Якщо ви логируєте або вставляєте часорядні дані, це може сильно вдарити.
Налаштування надійності в MariaDB/InnoDB
У InnoDB критичні для сплесків налаштування пов’язані зі скиданням redo журналу та тим, як швидко можна записати dirty сторінки:
- innodb_flush_log_at_trx_commit: класичний компроміс надійність/продуктивність. Значення 1 — найбезпечніше (скидання при кожному коміті), 2 — трохи швидше з невеликою втратою надійності, 0 — швидше, але ризиковано.
- sync_binlog: якщо ви використовуєте бінлог для реплікації, це може додатково коштувати fsync при записі.
- innodb_redo_log_capacity (або старі параметри розміру log file): занадто мало — часті checkpoint; занадто багато — змінюється час відновлення. Піки часто виявляють недо-розміровані логи.
- innodb_io_capacity / innodb_io_capacity_max: кажуть InnoDB, наскільки агресивно виконувати фонове скидання.
Для толерантності до сплесків ви хочете, щоб база поглинала сплеск і скидувала рівномірно, а не панічно. Панічне скидання — це коли латентність стає «цікавою».
Поширені патерни сплесків і що ламається першим
Патерн: маленькі транзакції при високому QPS
Це класична петля «insert одного рядка і commit», помножена на конкуренцію. Це комітний шторм.
- SQLite: контенція на блокування + витрати на fsync. Ви побачите «database is locked» або великі затримки, якщо не чергуєте записи і не пакетируєте коміти.
- MariaDB: може витримати конкуренцію, але fsync на кожен коміт може домінувати над латентністю. Ви побачите багато commit’ів, очікування скидання журналу і насичення I/O.
Патерн: backfill з важкими індексами
Ви додаєте колонки, робите backfill і оновлюєте вторинні індекси. Тепер кожен запис розповсюджується на кілька оновлень B-дерева.
- SQLite: один записувач робить його передбачуваним, але повільним; вікно блокування довше, тому всі інші чекають довше.
- MariaDB: пропускна здатність залежить від buffer pool і I/O. Гарячі індекси можуть викликати contention на затискачах; занадто багато потоків може погіршити ситуацію.
Патерн: сплеск співпадає з циклом checkpoint/flush
Це сценарій «усе нормально, усе нормально… чому кожні 30 секунд все горить?».
- SQLite WAL checkpoint: довгі цикли checkpoint можуть блокувати або уповільнювати записи, залежно від режиму і умов.
- InnoDB checkpoint: redo лог заповнюється, dirty сторінки треба скинути, і основна робота починає чекати на фонового I/O.
Патерн: джиттер латентності сховища
Усе норм, поки диск не затримається. Хмарні томи, скидання кешу RAID, шумні сусіди, коміти файлової системи — обирайте свого винуватця.
- SQLite: ваш поток додатка — це база даних; він блокується. Піки латентності безпосередньо впливають на час відповіді запитів.
- MariaDB: може ставити в чергу і паралелізувати, але рано чи пізно серверні потоки теж блокуються. Різниця в тому, що ви можете побачити це всередині рушія через лічильники і логи.
Жарт №2: «Ми просто зробимо це синхронним і швидким» — це еквівалент фрази «Я просто залишусь спокійним і вчасно пройду контроль в аеропорту».
Три корпоративні міні-історії з передової
Інцидент через хибне припущення: «SQLite витримає кількох записувачів, правда?»
Команда середнього розміру випустила новий сервіс інгесту. Кожен контейнер брав події з черги і записував їх у локальний файл SQLite для «тимчасового буферування», потім інша задача відправляла файл в об’єктне сховище. Припущення було таке: «це локальний диск, отже буде швидко». І так було — під час демо.
Потім прийшов продакшн. Автоскейл підняв кілька контейнерів на тому ж хості, всі писали в один файл SQLite через спільний hostPath. Як тільки трафік підскочив, записувачі зіштовхнулись. SQLite зробила те, для чого вона створена: серіалізувала записи. Додаток зробив те, для чого він створений: запанікував.
Симптоми були брудні: тайм-аути запитів, помилки «database is locked» і петля повторних спроб, яка множила сплеск. Хост виглядав недовикористаним по CPU, що підштовхнуло до неправильного діагнозу: «це не може бути база; CPU вільний.»
Виправлення було простим і дорослим: один записувач на файл БД. Вони перейшли на per-container файли SQLite і ввели явну чергу записів в процесі. Коли потрібні були записі між контейнерами, вони перемістили буферний шар в MariaDB з коректним connection pooling і пакетуванням транзакцій.
Висновок: SQLite неймовірна, коли ви навмисно контролюєте серіалізацію записів. Це хаос, коли серіалізація виявляється випадковою.
Оптимізація, яка повернулась бумерангом: «Послабимо fsync і додамо потоки»
Внутрішня адмін-платформа працювала на MariaDB. Під час квартального імпорту вони бачили сплески латентності комітів. Хтось (доброзичливий, втомлений) змінив innodb_flush_log_at_trx_commit з 1 на 2 і збільшив конкуренцію в імпортері з 16 до 128 потоків. Вони хотіли «продавити пакет швидше» і зменшити вікно болю.
Пропускна здатність покращилась десь на п’ять хвилин. Потім система вдарилась у іншу стіну: трясіння buffer pool та write amplification від вторинних індексів. Dirty сторінки накопичувались швидше, ніж їх можна було скинути. InnoDB почав агресивно скидати. Латентність перетворилась зі сплесків на постійно жахливу, а primary почав відставати в реплікації через зміну fsync шаблону бінлога під навантаженням.
Вони не втратили дані, але втратили час: імпорт у загальному тривав довше, бо система коливалась між періодами прогресу і довгими простоями. Тим часом трафік користувачів страждав, бо база не могла стабільно тримати час відповіді.
Кінцеве рішення не було «більше налаштувань». Це було дисципліноване формування навантаження: обмежити імпортер, пакетувати коміти і планувати задачу з передбачуваним rate-limit. Вони залишили налаштування надійності консервативними і вирішили реальну проблему: імпортер не мав поводитись як DDoS-тест.
Висновок: кручення ручок без контролю конкуренції — це як міняти один режим відмови на инший, більш заплутаний.
Скучно, але правильно, що врятувало день: «Вимірювати fsync, тримати запас, репетиції відновлення»
Сервіс, суміжний із платежами (де ви не можете креативити з надійністю), використовував MariaDB з InnoDB. Кожні кілька тижнів вони мали сплеск: задачі звірки плюс підвищення трафіку. Це ніколи не приводило до аварії, і ніхто не святкував. Це було головною метою.
У них була нудна рутина. Вони постійно вимірювали латентність диска (включаючи fsync), а не лише IOPS. Вони тримали запас у ємності redo логу і підібрали buffer pool, щоб система не тряслась під час сплесків. Вони також регулярно репетирували відновлення, щоб ніхто не вчився робити бекап під час інциденту.
Одного дня джиттер латентності сховища подвоївся через шумного сусіда на обладнанні. Сервіс не впав. Він став повільнішим, спрацювали алерти, і команда застосувала відоме пом’якшення: тимчасово обмежила пакетні задачі і призупинила некритичних записувачів. Трафік користувачів залишився в SLO.
Пізніше, коли вони переходили на інше сховище, у них вже були базові графіки, що доводять, що причина в шарі сховища. Зустрічі з procurement набагато простіші, коли ви показуєте графіки замість емоцій.
Висновок: «нудна» практика вимірювань і тримання запасу — найдешевша страховка від сплесків.
Швидкий план діагностики
Коли сплеск записів вдаряє і все поводиться дивно, часу на філософствування немає. Потрібне швидке дерево рішень: чи ми зв’язані блокуванням, CPU чи I/O?
Спочатку: підтвердьте форму болю (латентність проти пропускної здатності)
- Якщо пропускна здатність залишається високою, але p95/p99 латентність вибухає: шукайте fsync/journal/checkpoint затримки.
- Якщо пропускна здатність падає: шукайте контенцію блокувань, виснаження потоків або насичення сховища.
Друге: визначте, чи специфічно це для SQLite або MariaDB
- SQLite: помилки «database is locked», довгі очікування, ріст WAL, або зупинки через checkpoint.
- MariaDB: потоки чекають на log flush, фонове скидання dirty сторінок, очікування row lock або відставання реплікації, що підсилює тиск.
Третє: доведіть або виключіть сховище як вузьке місце
- Перевірте латентність диска, глибину черги та поведінку fsync під навантаженням.
- Якщо сховище «стрибуче», майже будь-яка база виглядатиме винною.
Четверте: перестаньте робити гірше
- Тротлінгуйте джерело сплеску (пакетна задача, імпортер, петля повторних спроб).
- Пакетуйте коміти. Зменшіть конкуренцію. Вимкніть «безкінечні повтори без джиттера».
- Збирайте докази перед перезапуском сервісів. Рестарти стирають підказки і рідко вирішують фізику проблеми.
Практичні завдання: команди, виводи та рішення
Ось речі, які можна запустити під час інциденту або сесії налаштування. Кожна містить: команду, що означає вивід, і рішення, яке ви ухвалюєте.
1) Перевірити, чи система насичена I/O (Linux)
cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (db01) 12/30/2025 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.1 0.0 4.3 31.7 0.0 51.9
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util await svctm
nvme0n1 20.0 900.0 640.0 42000.0 0.0 50.0 98.7 22.4 0.8
Значення: %util близько 100% плюс високе await вказує, що пристрій є обмежувачем; iowait теж підвищений.
Рішення: Не починайте з «налаштування SQL». Тротлінгуйте записувачів, зменшіть частоту комітів або перемістіть на швидше сховище. Якщо том спільний, перевірте шум сусідів.
2) Визначити, який процес робить найбільше записів
cr0x@server:~$ pidstat -d 1 3
Linux 6.5.0 (db01) 12/30/2025 _x86_64_ (8 CPU)
# Time UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
12:01:01 999 2145 0.00 52000.00 0.00 mariadbd
12:01:01 1000 8812 0.00 1200.00 0.00 importer
Значення: MariaDB робить основні записи; importer також робить вклад, але не домінує.
Рішення: Сфокусуйтесь на InnoDB flushing/redo поведінці і латентності сховища, не лише на пакетуванні додатка.
3) Перевірити файлову систему і опції mount (сюди приходять сюрпризи з надійності)
cr0x@server:~$ mount | grep -E " /var/lib/mysql | /data "
/dev/nvme0n1p2 on /var/lib/mysql type ext4 (rw,relatime,commit=5)
/dev/nvme1n1p1 on /data type xfs (rw,relatime,attr2,inode64,logbufs=8)
Значення: інтервал commit ext4 і поведінка журналювання можуть впливати на періодичні латентні стрибки.
Рішення: Якщо бачите періодичні зупинки узгоджені з commits журналу, розгляньте налаштування файлової системи або перемістіть файли БД на том з кращими характеристиками латентності.
4) Виміряти сиру латентність fsync-подібних операцій простим тестом sync
cr0x@server:~$ dd if=/dev/zero of=/var/lib/mysql/sync-test.bin bs=4k count=4096 oflag=dsync
4096+0 records in
4096+0 records out
16777216 bytes (17 MB, 16 MiB) copied, 3.91 s, 4.3 MB/s
Значення: oflag=dsync примушує синхронізацію на блок; низька пропускна здатність вказує на високу вартість синхронізацій. Це не ідеальна модель, але вона показує, що «сховище брешуче».
Рішення: Якщо це виглядає погано навіть на «швидких» дисках, зупиніться і виправте сховище або віртуалізацію перед тим, як звинувачувати базу даних.
5) MariaDB: підтвердити політику скидання InnoDB і розміри redo
cr0x@server:~$ mariadb -e "SHOW VARIABLES WHERE Variable_name IN ('innodb_flush_log_at_trx_commit','sync_binlog','innodb_redo_log_capacity','innodb_io_capacity','innodb_io_capacity_max');"
+------------------------------+-----------+
| Variable_name | Value |
+------------------------------+-----------+
| innodb_flush_log_at_trx_commit | 1 |
| sync_binlog | 1 |
| innodb_redo_log_capacity | 1073741824|
| innodb_io_capacity | 200 |
| innodb_io_capacity_max | 2000 |
+------------------------------+-----------+
Значення: Повна надійність і для redo, і для бінлога (дорого при сплесках). Ємність redo може бути замалою залежно від навантаження.
Рішення: Якщо p99 гине і ви можете дозволити собі невеликі компроміси надійності, розгляньте коригування налаштувань — але лише з чітким погодженням бізнесу. Інакше підвищуйте продуктивність сховища і пакетируйте коміти.
6) MariaDB: перевірити, чи чекаєте ви на скидання журналу
cr0x@server:~$ mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_log_waits'; SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs';"
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| Innodb_log_waits | 1834 |
+------------------+-------+
+----------------------+--------+
| Variable_name | Value |
+----------------------+--------+
| Innodb_os_log_fsyncs | 920044 |
+----------------------+--------+
Значення: Log waits означає, що транзакції мусили чекати на скидання redo журналу. Сплески + латентність fsync = біль.
Рішення: Зменшіть частоту комітів (пакетуйте), зменшіть конкуренцію або покращте латентність fsync. Не додавайте лише CPU.
7) MariaDB: перевірити тиск dirty сторінок (заборгованість скидання)
cr0x@server:~$ mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_total';"
+--------------------------------+--------+
| Variable_name | Value |
+--------------------------------+--------+
| Innodb_buffer_pool_pages_dirty | 412345 |
+--------------------------------+--------+
+--------------------------------+--------+
| Variable_name | Value |
+--------------------------------+--------+
| Innodb_buffer_pool_pages_total | 524288 |
+--------------------------------+--------+
Значення: Дуже високе відношення dirty сторінок вказує, що система відстає зі скиданням; чекпоїнти можуть змусити затримки.
Рішення: Обережно збільшіть налаштування I/O capacity, переконайтеся, що сховище витримує записи, і зменшіть вхідний потік записів, поки dirty сторінки не стабілізуються.
8) MariaDB: виявити очікування блокувань і гарячі таблиці
cr0x@server:~$ mariadb -e "SELECT * FROM information_schema.innodb_lock_waits\G"
*************************** 1. row ***************************
requesting_trx_id: 123456
blocking_trx_id: 123455
blocked_table: `app`.`events`
blocked_lock_type: RECORD
blocking_lock_type: RECORD
Значення: Є контенція на конкретній таблиці/індексі.
Рішення: Виправте гарячу точку: додайте індекс, змініть патерн доступу, уникайте лічильників в одній рядку, або шардуйте за ключем/часом. Додавання ще потоків при контенції лише погіршить ситуацію.
9) MariaDB: переглянути поточні стани потоків (на що вони чекають?)
cr0x@server:~$ mariadb -e "SHOW PROCESSLIST;"
+-----+------+-----------+------+---------+------+------------------------+------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+------+-----------+------+---------+------+------------------------+------------------------------+
| 101 | app | 10.0.0.12 | app | Query | 12 | Waiting for handler commit | INSERT INTO events ... |
| 102 | app | 10.0.0.13 | app | Query | 11 | Waiting for handler commit | INSERT INTO events ... |
| 103 | app | 10.0.0.14 | app | Sleep | 0 | | NULL |
+-----+------+-----------+------+---------+------+------------------------+------------------------------+
Значення: «Waiting for handler commit» зазвичай корелює з тиском на commit/fsync.
Рішення: Дослідіть налаштування sкидання redo/binlog і латентність диска; розгляньте пакетування записів.
10) SQLite: перевірити режим журналу і synchronous
cr0x@server:~$ sqlite3 /data/app.db "PRAGMA journal_mode; PRAGMA synchronous; PRAGMA wal_autocheckpoint;"
wal
2
1000
Значення: WAL-режим увімкнено; synchronous=2 — FULL (дужe надійно, повільніше); autocheckpoint на 1000 сторінок.
Рішення: Якщо у вас сплески і ви бачите затримки, обміркуйте, чи дійсно потрібен FULL. Також плануйте стратегію checkpoint (ручну/контрольовану), замість того, щоб дозволяти autocheckpoint вас здивувати.
11) SQLite: виявити контенцію блокувань контрольованим тестом запису
cr0x@server:~$ sqlite3 /data/app.db "PRAGMA busy_timeout=2000; BEGIN IMMEDIATE; INSERT INTO events(ts, payload) VALUES(strftime('%s','now'),'x'); COMMIT;"
Значення: Якщо це періодично завершується помилкою «database is locked», у вас є суперечливі записувачі або довгі транзакції.
Рішення: Введіть single-writer чергу, скоротіть транзакції і переконайтесь, що читачі не тримають блокування довше, ніж очікується (наприклад, довгі SELECT у транзакції).
12) SQLite: стежити за ростом WAL і здоров’ям checkpoint
cr0x@server:~$ ls -lh /data/app.db /data/app.db-wal /data/app.db-shm
-rw-r--r-- 1 app app 1.2G Dec 30 12:05 /data/app.db
-rw-r--r-- 1 app app 3.8G Dec 30 12:05 /data/app.db-wal
-rw-r--r-- 1 app app 32K Dec 30 12:05 /data/app.db-shm
Значення: WAL більший за основну БД. Це не автоматично фатально, але знак, що checkpoint не встигає.
Рішення: Запустіть контрольований checkpoint під час тихого вікна або змініть навантаження так, щоб checkpoint проходили передбачувано. Розслідуйте довгоживучих читачів, які перешкоджають прогресу checkpoint.
13) SQLite: перевірити, чи читачі блокують checkpoint (busy database)
cr0x@server:~$ sqlite3 /data/app.db "PRAGMA wal_checkpoint(TRUNCATE);"
0|0|0
Значення: Три числа — (busy, log, checkpointed). Нулі після TRUNCATE означають, що checkpoint пройшов швидко і WAL усічено.
Рішення: Якщо «busy» не нуль або WAL не усічеться, шукайте довгі транзакції читачів і виправляйте їх (скорочуйте читання, уникайте відкритих транзакцій).
14) MariaDB: підтвердити розмір buffer pool і тиск
cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';"
+-------------------------+------------+
| Variable_name | Value |
+-------------------------+------------+
| innodb_buffer_pool_size | 8589934592 |
+-------------------------+------------+
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Innodb_buffer_pool_reads| 18403921 |
+-------------------------+----------+
Значення: Якщо buffer pool reads швидко зростають під час сплеску, ви втрачаєте кеш і робите більше фізичного I/O, ніж планували.
Рішення: Збільшіть buffer pool (якщо RAM дозволяє), зменшіть робочий набір (індекси, патерни запитів) або шардуйте навантаження. Не ігноруйте ОС; свап зіпсує все.
15) Підозра на мережеве сховище: швидко перевірити розподіл латентності
cr0x@server:~$ ioping -c 10 -W 2000 /var/lib/mysql
4 KiB <<< /var/lib/mysql (ext4 /dev/nvme0n1p2): request=1 time=0.8 ms
4 KiB <<< /var/lib/mysql (ext4 /dev/nvme0n1p2): request=2 time=1.1 ms
4 KiB <<< /var/lib/mysql (ext4 /dev/nvme0n1p2): request=3 time=47.9 ms
...
--- /var/lib/mysql ioping statistics ---
10 requests completed in 12.3 s, min/avg/max = 0.7/6.4/47.9 ms
Значення: Цей максимальний сплеск латентності — саме те, як виглядає латентність коміту, коли диск підстрибнув.
Рішення: Якщо бачите такий джиттер, перестаньте ганятися за мікрооптимізаціями в SQL. Виправляйте QoS сховища, переміщуйте томи або додавайте буферування/пакетування.
16) Знайти retry-шторм у логах застосунку (самопідсилюваний сплеск)
cr0x@server:~$ journalctl -u app-ingester --since "10 min ago" | grep -E "database is locked|retrying" | tail -n 5
Dec 30 12:00:41 db01 app-ingester[8812]: sqlite error: database is locked; retrying attempt=7
Dec 30 12:00:41 db01 app-ingester[8812]: sqlite error: database is locked; retrying attempt=8
Dec 30 12:00:42 db01 app-ingester[8812]: sqlite error: database is locked; retrying attempt=9
Dec 30 12:00:42 db01 app-ingester[8812]: sqlite error: database is locked; retrying attempt=10
Dec 30 12:00:42 db01 app-ingester[8812]: sqlite error: database is locked; retrying attempt=11
Значення: Ви не просто маєте контенцію; ви множите її повторними спробами.
Рішення: Додайте експоненціальний backoff з джиттером, обмежте кількість повторів і розгляньте single-writer чергу. Агресивні повтори — це те, як сплеск перетворюється в авралію.
Поширені помилки (симптом → корінна причина → виправлення)
1) Симптом: помилки «database is locked» під час сплесків (SQLite)
Корінна причина: Багато одночасних записувачів або довготривалі транзакції, що утримують блокування; реальність одного записувача стикається з багатопроцесним навантаженням.
Виправлення: Серіалізуйте записи явно (один потік/процес запису), використовуйте WAL-режим, встановіть розумний busy_timeout і пакетні коміти. Уникайте тривалих читальних транзакцій під час записів.
2) Симптом: періодичні затримки 200–2000 мс кожні N секунд (SQLite)
Корінна причина: Цикли WAL checkpoint або коміти журналу файлової системи, що створюють сплески синхронізації.
Виправлення: Контролюйте checkpoint (ручне під час тихих вікон), налаштуйте wal_autocheckpoint, знижуйте synchronous лише з чіткими вимогами на надійність і перевіряйте джиттер латентності сховища.
3) Симптом: MariaDB p99 сплески при низькому CPU
Корінна причина: I/O-залежні коміти: редо/бінлог fsync латентність домінує; потоки чекають на log flush або handler commit.
Виправлення: Пакетуйте транзакції, зменшіть конкуренцію, перегляньте innodb_flush_log_at_trx_commit і sync_binlog з бізнес-погодженням, і покращіть латентність сховища.
4) Симптом: пропускна здатність падає, коли ви «додаєте більше воркерів» (MariaDB)
Корінна причина: Контенція блокувань/затискачів або тиск на скидання, підсилений трясінням потоків; більше конкуренції збільшує контекстні переключення і контенцію.
Виправлення: Обмежте конкуренцію, використовуйте connection pooling, виправте гарячі індекси/таблиці і налаштуйте фонове скидання InnoDB замість додавання потоків.
5) Симптом: WAL файл росте вічно (SQLite)
Корінна причина: Довгоживучі читачі не дозволяють checkpoint завершитись; або налаштування автоперевірки не відповідають навантаженню.
Виправлення: Переконайтесь, що читачі не тримають транзакції відкритими, запускайте wal_checkpoint у контрольовані вікна, і розгляньте розділення навантаження між кількома файлами БД, якщо контенція структурна.
6) Симптом: відставання реплікації MariaDB під час імпортів
Корінна причина: fsync бінлога і скидання redo під важким записовим навантаженням; однопотокове застосування (залежно від налаштувань) не встигає.
Виправлення: Пакетуйте записи, плануйте імпорти, перегляньте налаштування durability бінлога і переконайтесь, що конфігурація застосування на репліці відповідає навантаженню. Реплікація не безкоштовна.
7) Симптом: «На моєму ноутбуці швидко, у проді повільно» (обидві)
Корінна причина: Семантика сховища відрізняється: NVMe ноутбука проти спільного хмарного тому; fsync і джиттер латентності — різні світи.
Виправлення: Бенчмарк на схожому до продакшену сховищі, виміряйте розподіл латентності і встановіть SLO навколо p99 латентності коміту — не лише середнього throughput.
Чеклісти / покроковий план
Якщо ви обираєте між MariaDB і SQLite для навантажень з піковими записами
- Рахуйте записувачів, а не запити. Скільки процесів/хостів може писати одночасно?
- Визначте, чи можете забезпечити одного записувача. Якщо так — SQLite залишається варіантом.
- Чітко визначте вимоги на надійність. «Ми можемо втратити 1 секунду даних» — реальна вимога; «повинно бути надійно» — ні.
- Виміряйте латентність fsync сховища. Якщо вона стрибуча, обидві БД виглядатимуть ненадійними під сплесками.
- Плануйте backfill-и. Якщо ви регулярно імпортуєте або перепроцесуєте дані, з самого початку проектуйте throttle і пакетування.
План загартування SQLite під сплески (практично)
- Увімкніть WAL-режим і підтвердьте, що він залишається увімкненим.
- Встановіть busy_timeout на значення, що має значення (сотні до тисяч мс), і обробляйте SQLITE_BUSY з backoff + джиттером.
- Пакетуйте коміти: робіть коміт кожні N рядків або кожні T мс.
- Впровадьте чергу записів з одним потоковим записувачем. Якщо є багато процесів, введіть один процес-записувач.
- Контролюйте checkpoint: запускайте wal_checkpoint у періоди низького трафіку; налаштуйте wal_autocheckpoint.
- Слідкуйте за розміром WAL і успішністю checkpoint як за метриками першої черги.
План загартування MariaDB під сплески (практично)
- Підтвердьте, що ви використовуєте InnoDB для таблиць з інтенсивними записами.
- Розмір buffer pool так, щоб робочий набір поміщався настільки, наскільки дозволяє RAM, без свапу.
- Перевірте ємність redo логу; уникайте надто малих redo, що примушують часті checkpoint.
- Вирівняйте innodb_io_capacity з реальною здатністю сховища (не за побажаннями).
- Обмежте конкуренцію додатка; використовуйте connection pooling; уникайте штормів потоків.
- Пакетуйте записи і використовуйте multi-row inserts там, де це безпечно.
- Вимірюйте і ставте алерти на log waits, індикатори латентності fsync і відношення dirty сторінок.
Коли мігрувати з SQLite до MariaDB (або навпаки)
- Мігрувати SQLite → MariaDB коли ви не можете забезпечити одного записувача, потрібні записи з декількох хостів або важливі операційні інструменти (реплікація/онлайн бекапи).
- Мігрувати MariaDB → SQLite коли навантаження локальне, один записувач і ви платите зайві операційні накладні за невелику вбудовану базу даних.
Часті питання
1) Чи може SQLite витримати високу швидкість запису?
Так — якщо ви пакетируєте транзакції і тримаєте записувачів серіалізованими. SQLite може бути дуже швидкою на одному ядрі, бо уникає мережевих витрат і серверного оверхеду.
2) Чому SQLite каже «database is locked» замість того, щоб ставити записувачів у чергу?
Модель блокувань SQLite проста і свідома. Вона очікує, що додаток контролюватиме конкуренцію (busy_timeout, повтори і, бажано, один записувач). Якщо ви хочете, щоб база сама керувала важкою багатозаписовістю — вам потрібна серверна БД.
3) Чи завжди WAL-режим — правильний вибір для SQLite під сплесками?
Часто — але не завжди. WAL допомагає при конкурентних читаннях під час записів і може згладжувати стабільне навантаження записів. Він також вводить поведінку checkpoint, яку треба керувати. Ігнорування checkpoint призведе до періодичних затримок і гігантських WAL-файлів.
4) Для MariaDB, яке налаштування найсильніше впливає на поведінку під сплесками?
innodb_flush_log_at_trx_commit і (якщо використовується binlog) sync_binlog. Вони безпосередньо визначають, як часто ви платите вартість fsync. Зміна цих значень змінює надійність, тож ставтесь до цього як до бізнес-рішення.
5) Чому сплески записів іноді виглядають гірше після додавання індексів?
Індекси збільшують write amplification. Одна вставка перетворюється на кілька оновлень B-дерева і більше dirty сторінок. Під сплесками різниця між «одним записом» і «п’ятьма записами» — це ваш p99.
6) Чи варто розміщувати SQLite на мережевому сховищі?
Зазвичай ні. SQLite залежить від коректної, низьколатентної семантики блокувань і синхронізації. Мережеві файлові системи та деякі розподілені томи можуть зробити блокування непередбачуваним, а fsync — надзвичайно повільним. Якщо мусите — тестуйте конкретну реалізацію сховища під навантаженням.
7) Якщо MariaDB повільна під сплесками, чи треба просто масштабувати CPU?
Тільки якщо ви довели, що вузьке місце — CPU. Більшість проблем сплесків — це латентність I/O або контенція. Додавання CPU до fsync-вузького місця — як додати касирів, коли в магазині лише одна каса.
8) Який найпростіший спосіб змусити будь-яку базу витримувати сплески краще?
Пакетуйте коміти і тротлінгуйте конкуренцію. Сплески часто самі себе створюють через «необмежені воркери» і «коміт кожного рядка». Виправте це насамперед.
9) Хто безпечніший щодо надійності під сплесками?
Обидва можуть бути безпечними; обидва можуть бути налаштовані небезпечно. За замовчуванням MariaDB має консервативніші налаштування для серверних навантажень. SQLite теж може бути повністю надійною, але вартість продуктивності під сплесками більш помітна, бо вона сидить у вашому запитному шляху.
10) Як зрозуміти, чи ви заблоковані чекпоїнтами?
SQLite: WAL росте і checkpoint повідомляє «busy» або не усічається. MariaDB: log waits зростають, dirty сторінки наростають, і ви бачите затримки, пов’язані зі скиданням. У обох випадках корелюйте з пиками латентності диска.
Висновок: практичні кроки
Якщо у вас є пікові записи і ви вирішуєте між MariaDB та SQLite, не починайте з ідеології. Почніть з моделі записів.
- Якщо ви можете забезпечити одного записувача, пакетувати коміти і тримати базу на низьколатентному локальному сховищі, SQLite витримає сплески тихо і дешево.
- Якщо у вас багато записувачів між процесами/хостами і вам потрібні операційні інструменти як реплікація та спостережуваність, MariaDB — безпечніший вибір — за умови, що ви поважаєте фізику fsync і налаштовуєтесь обачно.
Далі зробіть непомітну роботу, що запобігає драмі: виміряйте латентність fsync, обмежте конкуренцію, пакетируйте записи і зробіть checkpoints/flush контрольованою частиною системи, а не сюрпризом. Ваш майбутній я все ще буде втомленим, але принаймні нудним. Оце і є мета.