У вас база даних добре працювала на bare metal. Потім ви запакували її в контейнер, вказали директорію даних на «щось персистентне», і раптом графіки затримок нагадують сейсмограф.
Записи зависають. Контрольні точки стрибають. Хтось каже «просто використайте volumes, bind mounts повільні», а хтось навпаки говорить це з упевненістю нічної дискусії в Twitter.
Ось правда з операційного фронту: «bind mount vs volume» — це не заявлене про продуктивність твердження. Це шлях до файлової системи, плюс кілька шарів опосередкування, які можуть — або не можуть — мати значення.
Важливо те, що знаходиться за цим шляхом: ядро, файлова система, стек зберігання, рантайм контейнера та налаштування надійності вашої бази даних.
Реальні вибори, які ви робите (не ті, про які сперечаються люди)
Коли хтось питає «bind mount чи volume для MariaDB/Postgres?», насправді ставиться низка питань, про які вони не усвідомлюють:
- Чи я записую у змінний шар контейнера (OverlayFS) чи ні? Це найбільше «не робіть цього» для баз даних.
- Яка фактична бекенд-файлова система? ext4, XFS, ZFS, btrfs та «містична мережева блочна пам’ять» по-різному поводяться під тиском fsync.
- Сховище локальне чи мережеве? Локальне NVMe і мережевий CSI-том можуть обидва бути «томом», але поведуться як різні види.
- Які мої налаштування надійності? PostgreSQL
fsync,synchronous_commit, розміщення WAL; MariaDB InnoDBinnodb_flush_log_at_trx_commitі doublewrite. Це не косметичні перемикачі. - Яка в мене ампліфікація записів? Розміри сторінок, WAL/binlog, doublewrite, контрольні точки, autovacuum/purge. Ви платите за записи, про які не підозрювали.
- Чи я обмежений IOPS, затримкою, CPU або записом забруднених сторінок? Якщо не знаєте — будете оптимізувати не те з великим ентузіазмом.
Прямо кажучи: bind mount на швидкому локальному XFS на NVMe переможе «Docker volume», що лежить на завантаженому мережевому сховищі.
Навпаки, Docker-керований том на локальному блочному сховищі може бути так само швидким, як bind mount. Ярлик не є вузьким місцем.
Жарт №1: База даних, що працює в змінному шарі контейнера — це як тримати паспорт у пакетиковому пакеті — технічно можливо, емоційно безвідповідально.
Цікаві факти та історичний контекст (чому ми тут)
Кілька коротких конкретних фактів пояснюють, чому дискусії про зберігання в контейнерах дивні:
- WAL PostgreSQL існує з середини 1990-х (коли PostgreSQL еволюціонував із POSTGRES), він розроблений для безпеки при аваріях на недосконалих дисках — задовго до появи «хмарних томів».
- InnoDB став дефолтним рушієм MySQL у 5.5 (ера близько 2010), і MariaDB успадкувала цю лінію; історія надійності InnoDB сильно спирається на редо-логи і механіку doublewrite.
- Пейдж-кеш ядра Linux — це робоча конячка продуктивності для обох БД; «direct I/O скрізь» не є типовою стратегією і часто шкодить.
- Семантика fsync залежить від файлової системи та опцій монтування; одна і та сама конфігурація БД може бути безпечною в одному середовищі і «переважно безпечною» в іншому — ввічливий спосіб сказати «неочікувана втрата даних».
- OverlayFS став масовим з драйвером Docker overlay2, бо він ефективний для шарів образів, а не тому, що він любить робочі навантаження баз даних з fsync та випадковими записами.
- Історично мережеві файлові системи були типовою «підводною міной» для баз даних; сучасні системи кращі, але трикутник «затримка + fsync + джиттер» досі псує вихідні дні.
- Іменовані томи Docker популяризувалися заради портативності («переносити додаток, не турбуючись, де живуть дані»), але бази даних дуже дбають про те, куди фізично лягають байти.
- Абстракції PV Kubernetes спростили запити на сховище і ускладнили розуміння; «я попросив 100Gi» не означає «я отримав низьколатентні синхронні записи».
MariaDB vs PostgreSQL: як їхні I/O-патерни карають сховища
PostgreSQL: спочатку WAL, потім файли даних, потім драматичні контрольні точки
PostgreSQL записує зміни у WAL (Write-Ahead Log) і пізніше скидає забруднені буфери у файли даних, із контрольними точками, що координують «наскільки далеко WAL безпечно утилізувати».
Шлях WAL fsync — це ваш пульс надійності. Якщо він сповільнюється, ваші коміти сповільнюються. І це відбивається чесно: затримка сховища стає затримкою транзакції.
Типові болючі місця в контейнерах:
- WAL на повільному сховищі (або гірше: у змінному шарі контейнера): затримки комітів різко ростуть.
- Стрибки під час контрольних точок: ви бачите періодичні бурі записів і різкі падіння латентності.
- I/O автоприбиральника: постійні випадкові читання/записи, які швидко виявляють обмеження IOPS.
- fsync та бар’єри: якщо ваш «том» бреше про надійність, PostgreSQL дізнається про це не одразу — а коли дізнається, часто надто пізно.
MariaDB (InnoDB): редо-логи, doublewrite та фонова синхронізація
InnoDB у MariaDB спирається на редо-логи для зроблення комітів довговічними (залежно від innodb_flush_log_at_trx_commit) і використовує буфер doublewrite, щоб зменшити корупцію частково записаних сторінок.
На практиці це означає більшу ампліфікацію записів в обмін на безпеку під час аварій.
Типові болючі місця:
- Каденс fsync редо-логу: при строгій надійності латентність лог-пристрою контролює пропускну здатність.
- Doublewrite + сторінки даних: можливо, ви платите за запис тієї самої сторінки двічі (або більше), що карає повільні носії.
- Фонове скидання: якщо сховище не встигає, InnoDB може зупинити роботу переднього плану.
- Бінлог (якщо увімкнено): додаткові послідовні записи, які виглядають «дешевими», поки політика fsync не дасть про себе знати.
Що це означає для «bind mount vs volume»
PostgreSQL зазвичай більш чутливий до затримки на коміт (шлях WAL fsync видно відразу), тоді як InnoDB може буферизувати і «розмазувати» біль, поки не перестане — тоді стрибає сильніше.
Обидві системи ненавидять непередбачувану затримку. Обидві ненавидять спалахи обмежень. Обидві ненавидять, коли «fsync — це рекомендація».
Bind mounts проти Docker volumes: що насправді відрізняється
Перестаньте ставитися до цих речей як до містичних об’єктів.
- Bind mount: шлях у контейнері відображається на існуючий хостовий шлях. Ви контролюєте директорію на хості, файлову систему, опції монтування, SELinux-мітки та життєвий цикл.
- Іменований том Docker: Docker керує директорією (зазвичай під
/var/lib/docker/volumes) і монтує її в контейнер. Бекенд все ще хостова файлова система, якщо драйвер тому не змінює цього.
Різниця в продуктивності зазвичай опосередкована:
- Операційні налаштування за замовчуванням: томи часто лежать на тому ж диску, що і Docker root, який часто не є тим диском, який ви планували для баз даних.
- Опції монтування та вибір файлової системи: bind mounts роблять природнішим вибір «дружньої до БД» файлової системи й опцій монтування.
- Наклад витрат від міток безпеки: на системах з SELinux релебелінг і контекст можуть додавати витрати або призводити до відмов; зазвичай спочатку це питання коректності, потім — продуктивності.
- Робочі процеси резервного копіювання/відновлення: bind mounts інтегруються з хостовими інструментами; томи інтегруються з Docker-інструментами. Обидва підходи можуть бути нормальними; обидва можуть бути зловживані.
Якщо ви використовуєте просту локальну хостову файл систему, і bind mounts, і томи обходять OverlayFS. Це ключове. Файли бази даних не повинні жити у записуваному шарі overlay.
OverlayFS/overlay2: антагоніст із виправданням
OverlayFS створений для шарування образів. Він відмінно справляється зі своїм завданням: багато майже тільки для читання файлів з час від часу копіюванням при записі.
Бази даних — навпаки: перезапис тих самих файлів, синхронізація їх, робити це постійно і голосно скаржитися при додаванні затримки.
Режим несправності виглядає так:
- Малі випадкові записи перетворюються на складніші операції CoW.
- Зростає метаданний шум: атрибути файлів, записи в директоріях, copy-up.
- Поведінка fsync стає дорожчою, особливо під навантаженням.
Якщо ваша БД на overlay2 — ви бенчмаркуєте драйвер зберігання Docker, а не MariaDB чи PostgreSQL.
Кут зору Kubernetes: HostPath, local PVs, network PVs, та чому ваша БД їх по-різному ненавидить
Kubernetes перетворює ваше просте питання на шведський стіл абстракцій:
- HostPath: по суті bind mount до шляху на вузлі. Швидко і просто. Але прив’язує ваш pod до вузла і може бути пасткою доступності.
- Local Persistent Volumes: як HostPath, але з семантикою розміщення та життєвого циклу. Часто найкращий варіант «локального диска», якщо ви погоджуєтеся на афінітет по вузлу.
- Мережеві блочні томи: iSCSI, NVMe-oF, блочні диски хмар — часто мають гарну латентність, але все ж буває джиттер і конкуренція між клієнтами.
- Мережеві файлові системи: NFS-подібні або розподілені FS. Можуть працювати, але «стіна fsync» реальна і проявляється як затримка комітів.
Для станних баз даних не вважайте, що «PVC прив’язаний» означає «проблема вирішена». Потрібно знати, що ви отримали: профіль затримок, поведінку fsync, пропускну здатність при паралельних записувачах і як це відмовляє.
Як бенчмаркувати без обману себе
Якщо ви хочете правду про продуктивність, бенчмаркуйте весь шлях: DB → libc → ядро → файлова система → блочний шар → пристрій.
Не тестуйте так, щоб уникнути fsync, якщо у продакшені вам потрібен fsync.
Також не запускайте один інструмент один раз і не оголошуйте перемогу. У контейнеризованих системах є шумні сусіди, CPU-обмеження, бурі запису та фонова компакція.
Потрібно захоплювати варіацію, а не лише середні значення.
Одна перефразована ідея від Werner Vogels: «Все ламається, весь час — проектуйте так, щоб вміти це експлуатувати.» (перефразована ідея)
Швидкий план діагностики
Коли хтось каже «Postgres в Docker повільний» або «MariaDB в Kubernetes скаче», робіть це в порядку. Не імпровізуйте.
-
Перше: підтвердіть, де фактично живуть дані.
Якщо це overlay2 — зупиніться і виправте це перед будь-яким бенчмарком. -
Друге: виміряйте шлях затримки fsync/commit.
PostgreSQL: перевірте метрики WAL/commit; MariaDB: поведінку скидання редо-логу та затримки.
Якщо коміти повільні — підозрюйте сховище, поки не доведено інше. -
Третє: ідентифікуйте тип обмеження.
Випадкові записи, обмежені IOPS vs послідовні записи, обмежені пропускною здатністю vs CPU-обмеження vs тиск пам’яті, що спричиняє writeback-сплески. -
Четверте: перевірте файлову систему та опції монтування.
ext4 vs XFS vs ZFS, бар’єри, discard, atime, режим журналювання та чи не знаходитеся ви на loopback-файлі. -
П’яте: шукайте фонові вбивці I/O.
Контрольні точки, autovacuum, purge, компактація, резервні завдання, node-level лог-шерінг, антивірус/скануючі агенти. -
Шосте: лише потім порівнюйте bind mount vs volume.
Якщо обидва вказують на ту саму файлову систему, різниця в продуктивності зазвичай — математична похибка, якщо ви не змінили опції або пристрої.
Практичні завдання: команди, вихід та яке рішення приймати
Це реальні операційні завдання. Кожне містить команди, що означає їхній вихід і яке рішення приймати далі.
Запускайте їх на хості та всередині контейнерів за потреби.
Завдання 1: Доведіть, чи БД на overlay2 чи на реальному монтуванні
cr0x@server:~$ docker inspect -f '{{.GraphDriver.Name}} {{json .Mounts}}' pg01
overlay2 [{"Type":"volume","Name":"pgdata","Source":"/var/lib/docker/volumes/pgdata/_data","Destination":"/var/lib/postgresql/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
Що це означає: GraphDriver — overlay2 для шару контейнера, але директорія даних — це том, змонтований на хост-шлях під директорією томів Docker. Це добре: дані БД обходять overlay2.
Рішення: Якщо ви не бачите datadir у .Mounts (або він вказує на файлову систему контейнера), ви на overlay2. Перенесіть datadir на bind mount або том негайно.
Завдання 2: З’ясуйте тип файлової системи, що підтримує ваш bind mount/volume
cr0x@server:~$ df -T /var/lib/docker/volumes/pgdata/_data
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Що це означає: Ваші Docker томи розміщені на XFS під /. Це може бути NVMe (добре) або спільний root-диск (часто погано).
Рішення: Якщо це не диск, який ви планували, перемістіть сховище БД на виділений монтувальний пункт (bind mount або драйвер томів). Root-диски швидко стають шумними.
Завдання 3: Перевірте, чи ваш «том» насправді є loopback-файлом (тиха катастрофа)
cr0x@server:~$ mount | grep -E '/var/lib/docker|/var/lib/kubelet|loop'
/dev/loop0 on /var/lib/docker type ext4 (rw,relatime)
/dev/nvme0n1p2 on / type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)
Що це означає: Docker розміщений на loop-пристрої. Часто це означає «файловий бекенд для блочного пристрою» (наприклад devicemapper у файлі або інше вкладене сховище).
Loopback додає накладні витрати і може перетворити fsync на жарт продуктивності.
Рішення: Не запускайте серйозні бази даних на Docker-сховищі, що ґрунтується на loopback. Виправте макет хостового сховища спочатку.
Завдання 4: Підтвердіть опції монтування, що впливають на надійність і затримку
cr0x@server:~$ findmnt -no TARGET,FSTYPE,OPTIONS /var/lib/docker/volumes/pgdata/_data
/ xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota
Що це означає: Ви бачите, чи є підозрілі опції на кшталт nobarrier (небезпечно), дивні журнальні налаштування або важкі метадані опції.
Рішення: Якщо знайдете опції монтування, що зменшують безпеку заради швидкодії — зупиніться. Виправляйте продуктивність, покращуючи пристрій і макет, а не знімаючи ремені безпеки.
Завдання 5: Перевірте налаштування PostgreSQL, що прямо впливають на поведінку сховища
cr0x@server:~$ docker exec -it pg01 psql -U postgres -c "SHOW data_directory; SHOW wal_level; SHOW synchronous_commit; SHOW full_page_writes; SHOW checkpoint_timeout;"
data_directory
-------------------------------
/var/lib/postgresql/data
(1 row)
wal_level
-----------
replica
(1 row)
synchronous_commit
-------------------
on
(1 row)
full_page_writes
------------------
on
(1 row)
checkpoint_timeout
--------------------
5min
(1 row)
Що це означає: Ця конфігурація орієнтована на надійність. WAL потрібен для реплік, коміти синхронні, і повні записи сторінок увімкнені (безпека проти розірваних сторінок).
Рішення: Якщо продуктивність погана, не починайте з відключення надійності. Почніть з переміщення WAL на швидше сховище і виправлення затримки.
Завдання 6: Перевірте перемикачі надійності MariaDB/InnoDB, що впливають на fsync
cr0x@server:~$ docker exec -it mariadb01 mariadb -uroot -psecret -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; SHOW VARIABLES LIKE 'sync_binlog'; SHOW VARIABLES LIKE 'innodb_doublewrite';"
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog | 1 |
+---------------+-------+
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| innodb_doublewrite| ON |
+-------------------+-------+
Що це означає: Ви платите за «реальну надійність». Кожен коміт скидає редо-лог; binlog синхронізується; doublewrite увімкнений.
Рішення: Якщо вам потрібна така надійність — купіть сховище, що зможе її витримати. Якщо вам це не потрібно (рідко у продакшені), будьте явними щодо ризику і документуйте його.
Завдання 7: Спостерігайте за затримкою на пристрої та глибиною черги (вигляд хоста)
cr0x@server:~$ iostat -x 1 3
Linux 6.5.0 (server) 12/31/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
6.12 0.00 2.31 8.44 0.00 83.13
Device r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1 221.0 842.0 7824.0 29812.0 74.1 9.82 11.6 4.2 13.5 0.6 64.3
Що це означає: await ~11.6ms і avgqu-sz ~9.8 вказують на черги записів. Для навантаженої БД записова затримка 10–15ms абсолютно може проявитися як повільні коміти.
Рішення: Якщо await високий і %util високий — ви обмежені сховищем. Виправляйте макет сховища або мігруйте на швидше середовище перед налаштуванням запитів БД.
Завдання 8: Виявляйте writeback-сплески файлової системи (тиск пам’яті, що маскується під сховище)
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 81232 65488 9231440 0 0 124 980 2840 6120 7 2 86 5 0
3 1 0 42112 65284 9150020 0 0 118 8450 3120 7450 6 3 63 28 0
4 2 0 23840 65110 9121000 0 0 102 16820 3401 8012 6 4 52 38 0
Що це означає: Збільшення b (blocked), зростання bo (blocks out) і підвищення wa вказують на скидання забруднених сторінок під сильним тиском. Це може виглядати як «сховище повільне», але тригер — тиск пам’яті.
Рішення: Якщо пам’яті мало — додайте вузлу RAM, зменшіть конкуренцію за ресурси або налаштуйте пам’ять БД, щоб ядро не було змушене до панічного скидання.
Завдання 9: Підтвердіть обмеження CPU через cgroup (фальшиве вузьке місце сховища)
cr0x@server:~$ docker inspect -f '{{.HostConfig.NanoCpus}} {{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}' pg01
0 200000 100000
Що це означає: Квота CPU — 2 ядра (200000/100000). Якщо ви досягаєте цього ліміту, фонові процеси (контрольні точки, скидання, vacuum) сповільнюються і сховище починає виглядати «дивно».
Рішення: Якщо БД обмежена CPU — підвищте ліміти перед міграцією сховища. БД, позбавлена CPU, не може ефективно генерувати I/O.
Завдання 10: Виміряйте поведінку комітів та контрольних точок PostgreSQL зі статистики
cr0x@server:~$ docker exec -it pg01 psql -U postgres -c "SELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend, maxwritten_clean FROM pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | buffers_checkpoint | buffers_backend | maxwritten_clean
------------------+-----------------+-------------------+----------------+------------------
118 | 43 | 9823412 | 221034 | 1279
(1 row)
Що це означає: Високий buffers_checkpoint вказує на запис, спричинений контрольними точками. Якщо сплески затримки співпадають з контрольними точками, причетні пропускна здатність сховища та поведінка writeback.
Рішення: Розгляньте можливість розподілення I/O контрольних точок (налаштування checkpoint), але тільки після підтвердження, що сховище не є фундаментально недопотужним.
Завдання 11: Перевірте розташування WAL PostgreSQL і подумайте про його ізоляцію
cr0x@server:~$ docker exec -it pg01 bash -lc "ls -ld /var/lib/postgresql/data/pg_wal && df -T /var/lib/postgresql/data/pg_wal"
drwx------ 1 postgres postgres 4096 Dec 31 09:41 /var/lib/postgresql/data/pg_wal
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Що це означає: WAL розділяє ту саму файлову систему з даними. Це звично, але не завжди ідеально.
Рішення: Якщо латентність комітів — ваша проблема, змонтуйте WAL на швидший або менш зайнятий пристрій (окремий том/bind mount). WAL невеликий, але вимогливий.
Завдання 12: Підтвердіть розміщення datadir MariaDB і файлову систему
cr0x@server:~$ docker exec -it mariadb01 bash -lc "mariadb -uroot -psecret -e \"SHOW VARIABLES LIKE 'datadir';\" && df -T /var/lib/mysql"
+---------------+----------------+
| Variable_name | Value |
+---------------+----------------+
| datadir | /var/lib/mysql/|
+---------------+----------------+
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Що це означає: Дані MariaDB на XFS в корені. Добре, якщо root швидкий і виділений; погано, якщо root ділиться з іншим навантаженням.
Рішення: Якщо це лежить на загальному root-диску — перемістіть на виділений монтувальний пункт з відомими характеристиками продуктивності.
Завдання 13: Швидка перевірка реальності fsync за допомогою fio (шлях хоста, що використовує bind/volume)
cr0x@server:~$ fio --name=fsync-test --directory=/var/lib/docker/volumes/pgdata/_data --rw=write --bs=4k --size=256m --ioengine=sync --fsync=1 --numjobs=1 --runtime=20 --time_based --group_reporting
fsync-test: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=sync, iodepth=1
...
write: IOPS=820, BW=3280KiB/s (3360kB/s)(64.0MiB/20003msec)
clat (usec): min=220, max=42000, avg=1216.55, stdev=3100.12
Що це означає: Це наближає «малі записи з fsync». Середня затримка ~1.2ms, але максимум 42ms: цей хвіст затримки — саме те, що стає «випадковими повільними комітами».
Рішення: Якщо максимальні затримки погані — виправляйте конкуренцію за сховище або переходьте на краще середовище. Налаштування БД не домовляться з фізикою.
Завдання 14: Перевірте, чи БД мовчки не досягає обмежень файлової системи (іноди, місце, зарезервовані блоки)
cr0x@server:~$ df -h /var/lib/docker/volumes/pgdata/_data && df -i /var/lib/docker/volumes/pgdata/_data
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 447G 118G 330G 27% /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 224M 3.2M 221M 2% /
Що це означає: Місця й інодів вистачає. Коли вони забруднюються — БД поводяться дивно: autovacuum падає, тимчасові файли не створюються, аварії при встановленні розширень тощо.
Рішення: Якщо вільного місця мало або іноди закінчуються — виправляйте це перед роботою з продуктивністю. Повні диски роблять продуктивність «цікавою» в найгіршому сенсі.
Завдання 15: Переконайтеся, що ніхто «дружньо» не робить rsync живої директорії даних
cr0x@server:~$ ps aux | grep -E 'rsync|tar|pg_basebackup|mariabackup' | grep -v grep
root 19344 8.2 0.1 54360 9820 ? Rs 09:44 0:21 rsync -aH --delete /var/lib/docker/volumes/pgdata/_data/ /mnt/backup/pgdata/
Що це означає: Хтось робить rsync живої директорії даних. Це і податок на продуктивність, і (для PostgreSQL) некоректний метод бекапу, якщо зроблено без дотримання правил.
Рішення: Припиніть файлові копії живої БД, якщо ви не використовуєте правильні снапшотні семантики та координовані методи бекапу БД.
Типові помилки: симптом → корінь проблеми → виправлення
1) Симптом: «Контейнерна БД повільна, але хост швидкий»
Корінь проблеми: Дані БД на overlay2/змінному шарі контейнера, а не на монтуванні.
Виправлення: Змонтуйте datadir як bind mount або іменований том на реальну хостову файлову систему. Підтвердіть через docker inspect mounts.
2) Симптом: Випадкові стрибки комітів 200–2000ms
Корінь проблеми: Хвости затримки сховища від шумних сусідів, джиттер мережевих PV або поведінка кешу пристрою. Часто проявляється як fsync-зависання.
Виправлення: Перемістіть WAL/redo на менш затримкове сховище, ізолюйте диски БД і вимірюйте розподіл затримок (не лише середні показники).
3) Симптом: Періодичні обриви латентності PostgreSQL кожні кілька хвилин
Корінь проблеми: Контрольні точки викликають бурі записів; накопичуються забруднені буфери і скидаються пачками.
Виправлення: Плавно налаштуйте pacing контрольних точок і переконайтеся, що сховище може витримати тривалі записи. Якщо сховище марґінальне — налаштування тільки маскує проблему.
4) Симптом: MariaDB зависає під навантаженням, потім відновлюється
Корінь проблеми: Фонове скидання InnoDB не встигає; редо-лог чи doublewrite ампліфікують записи; відбуваються тимчасові затримки.
Виправлення: Покращте латентність/IOPS сховища, перевірте налаштування надійності і уникайте спільного диска з іншими інтенсивними записами.
5) Симптом: «Переключення на Docker volumes зробило повільніше»
Корінь проблеми: Том лежить на кореневій файловій системі з іншими опціями монтування, можливо повільнішою фізикою або контендним Docker-доступом.
Виправлення: Розміщуйте томи на виділеному сховищі (bind mount на виділену точку монтування або змініть data-root Docker на швидший диск).
6) Симптом: «Bind mounts повільні на моєму Mac/Windows ноутбуці»
Корінь проблеми: Docker Desktop здійснює file sharing через віртуалізаційний кордон; семантика файлової системи емульована і повільна.
Виправлення: Для локальної розробки віддавайте перевагу Docker volumes всередині Linux VM. У продакшені на Linux ця проблема зникає.
7) Симптом: Корупція бази після падіння хоста, незважаючи на «fsync увімкнено»
Корінь проблеми: Стек зберігання брехав про довговічність записів (кеш запису без захисту від втрати живлення, небезпечні опції монтування, неправильно налаштований RAID-контролер).
Виправлення: Використовуйте підприємницьке сховище з захистом від втрати живлення; перевіряйте політики кешування; не вимикайте бар’єри; тестуйте відновлення після аварій.
8) Симптом: Високий iowait але низький відсоток завантаження диска
Корінь проблеми: Часто мережеве сховище, CPU-обмеження cgroup або блокування на рівні файлової системи. Диск не «зайнятий», він «далеко».
Виправлення: Вимірюйте кінець-до-кінця затримку (статистика блочного пристрою, метрики мережевого PV) та перевіряйте CPU-обмеження і тиск пам’яті.
Три міні-історії з корпоративного життя
Міні-історія 1: Інцидент через хибне припущення
Середня компанія мігрувала платіжний сервіс з VM у Kubernetes. Команда зробила на папері логічно: станні навантаження отримали PVC, безстанні — emptyDir.
Вони припустили, що PVC означає «реальний диск». Також припустили, що клас сховища з назвою «fast» означає «низька латентність».
Сервіс використовував PostgreSQL з синхронними комітами. Під час вікна міграції все пройшло функціональні тести. Але в понеділок ранкова навантаження вдарила, і p99 латентності транзакцій полетіла вгору.
Поди БД не були обмежені CPU чи пам’яттю. Вузли виглядали здоровими. Команда звинувачувала Postgres «в контейнерах».
Насправді: «швидкий» клас сховища був рішенням на основі мережевої файлової системи, яке підходило для веб-завантажень і логів, але мало жахливі хвости затримок під fsync-навантаженням.
PVC зробив саме те, що обіцяв: персистентне сховище. Він не обіцяв передбачуваної латентності комітів.
Виправлення було нудне та структурне: перемістити WAL на клас блочного сховища з нижчою латентністю, тримати файли даних на існуючому класі (або також перемістити, залежно від вартості), і прикріпити pod’и БД до вузлів з кращими мережевими шляхами.
Найбільше покращення дало визнання того, що «персистентне» не означає «придатне для баз даних».
Міні-історія 2: Оптимізація, що повернулась бумерангом
Інша організація запускала MariaDB в Docker на флоті Linux-хостів. Продуктивність була прийнятною, але іноді виникали затримки запису.
Хтось помітив, що підсистема дисків має кеш запису і подумав: «Зробимо fsync дешевшим».
Вони змінили опції монтування та налаштування контролера, щоб зменшити бар’єри та поведінку флашу. Бенчмарки покращились. Усі потиснули руки.
Через два тижні один хост перезавантажився несподівано. MariaDB відновилась, але кілька таблиць мали тонку корупцію: не катастрофічно одразу, але достатньо, щоб давати некоректні результати.
Постмортем був болісний, бо в метриках «не було очевидної проблеми». Помилка жила в розриві між тим, що БД просила (fsync означає довговічність) і тим, що сховище доставляло (швидке підтвердження, пізніше збереження).
Інцидент не з’явився в щасливому шляху бенчмарка. Він з’явився через фізику плюс непередбачені відключення живлення.
Вони відкотили небезпечні налаштування, впровадили бекапи з репетиціями відновлення і оновили контролери з захистом від втрати живлення.
Продуктивність трохи впала. Бізнес прийняв це рішення. Некоректні дані дорожчі за повільні коміти — і бухгалтери це розуміють.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Третя команда мала звичку, що виглядала майже старомодно: перед будь-якою зміною сховища вони запускали невеликий набір тестів і записували результати.
Нічого складного. Просто послідовно. Кілька fio-профілів, специфічне для БД навантаження на запис і репетиція снапшоту/відновлення.
Вони впроваджували нову конфігурацію рантайма контейнера і переміщували Docker data-root на інший диск.
Хтось сказав «це ж лише метадані Docker, томи не постраждають». Команда все одно протестувала.
Тести показали регресію у хвостах fsync після переміщення. Не великі середні — просто гірші аутлайєри.
Виявилось, що новий диск також містив системні логи і агента моніторингу, який час від часу сплескував запис.
Оскільки вони протестували перед розгортанням, проблему виявили рано, перемістили томи БД на виділені монтувальні точки і уникли повільного інциденту.
Це не було героїчно. Це було по-дорослому.
Жарт №2: Єдина річ більш персистентна, ніж том бази даних — це інженер, який наполягає, що його останній бенчмарк «точно представницький».
Чеклісти / покроковий план
Покроково: вибір сховища для Postgres або MariaDB у контейнерах
- Визначте, що вам потрібно: сувора надійність чи «можемо втратити останню секунду». Запишіть це. Нехай бізнес затвердить це документально (або хоча б тикетом).
- Розмістіть дані БД поза overlay2: використовуйте bind mount або іменований том, але переконайтеся, що бекендова файлова система відома і відповідає очікуванням.
- Вибирайте файлову систему свідомо: ext4 або XFS — поширені дефолти. ZFS може бути чудовим за умови обережності, але не вважайте його чарівним.
- Використовуйте виділене сховище для БД: окрема точка монтування. Уникайте спільного root-диска. Уникайте loopback-пристроїв.
- Ізолюйте WAL/redo логи, якщо важлива латентність комітів: окремий пристрій/том може бути найпростіший і найефективніший крок.
- Бенчмаркуйте шлях, у якому будете працювати: включіть fsync. Захоплюйте хвости затримок. Запускайте з реалістичною паралельністю.
- Підтвердіть операційні процеси: бекапи, відновлення, тренування відновлення після краху і як ви мігруєте/масштабуєте томи.
- Впровадьте моніторинг, пов’язаний із режимами відмови: затримка комітів, тривалість контрольних точок, часи fsync, disk await і тиск вузла.
- Документуйте точки монтування і правка власності: права доступу, SELinux-мітки і точний хостовий шлях → шлях у контейнері.
- Перетестуйте після кожної зміни платформи: оновлення ядра, зміна класу сховища, зміна рантайма або оновлення образу вузла.
Чекліст: рішення «bind mount vs volume»
- Використовуйте bind mount, коли бажаєте явний контроль над вибором диска, опціями монтування та інструментами операцій на хості.
- Використовуйте іменований том, коли хочете, щоб Docker керував життєвим циклом і ви довіряєте розташуванню data root Docker (або використовуєте належний драйвер томів).
- Уникайте обох, якщо вони опиняються на мережевому сховищі з непередбачуваною латентністю і вам потрібна сувора продуктивність комітів; замість цього оберіть кращий клас/пристрій.
Питання та відповіді
1) Чи Docker volumes швидші за bind mounts на Linux?
Зазвичай вони ідентичні, якщо лежать на тій самій підлягаючій файловій системі та пристрої. Різниця у продуктивності виникає через те, де вони розміщені і що їх підпирає, а не через тип монтування.
2) Чи коли-небудь припустимо запускати Postgres/MariaDB на overlay2?
Для одноразової розробки та тестів — так. Для всього, що вам важливо — ні. OverlayFS оптимізований для шарування образів, а не для надійності баз даних і патернів запису.
3) Чому PostgreSQL здається більш чутливим до затримок сховища, ніж MariaDB?
Шлях коміту PostgreSQL часто одразу доходить до fsync WAL (при синхронних комітах). Якщо латентність сховища стрибає — ваші коміти теж стрибають. InnoDB може буферизувати і потім пізніше застопоритися.
4) Якщо я вимкну fsync або увімкну async commit, чи «виправиться» продуктивність?
Вона стане швидшою, але менш коректною. Ви можете купити швидкість ціною майбутньої корупції або втрати транзакцій після збою. Якщо ви обираєте цей компроміс — оформіть його як контрольоване рішення.
5) Чи слід розділяти WAL і дані на різні томи?
Часто так, коли важлива латентність комітів і у вас змішане I/O. WAL — послідовний і чутливий до латентності; записи даних можуть бути сплесками під час контрольних точок. Ізоляція знижує конкуренцію.
6) У Kubernetes, чи хороша ідея HostPath для баз даних?
Може бути швидко і передбачувано, бо це локально. Але це прив’язує вас до вузла і ускладнює failover. Local PV — більш структурований варіант цієї ідеї.
7) Чому bind mounts повільно працювали на моєму ноутбуці?
Docker Desktop використовує VM. Bind mounts перетинають кордон між хостом і VM і можуть бути повільними. Томи всередині VM зазвичай швидші для розробки.
8) Яка найпоширеніша «пастка продуктивності томів» в продакшені?
Припущення, що персистентний том означає низьку латентність і гарну поведінку fsync. Персистентність стосується виживання, а не швидкості. Вимірюйте латентність і хвости.
9) ext4 чи XFS для контейнерних баз даних?
Обидві можуть бути відмінними. Важливіше якість пристрою, опції монтування, версія ядра і уникнення патологічних шарів (loopback, overlay для даних, контенція root-диска).
10) Який найшвидший спосіб вирішити суперечку «bind mount швидший» проти «volume швидший»?
Покладіть обидва на ту саму підлягаючу файлову систему/пристрій і запустіть робоче навантаження з включеним fsync. Якщо вони відрізняються істотно — ви знайшли екологічну різницю, а не філософську істину.
Наступні кроки, які можна зробити цього тижня
- Аудит розміщення: перелічіть всі контейнери/поди БД і доведіть, що їхні datadir’и на монтуваннях, а не на overlay2.
- Змоделюйте реальність сховища: для кожної БД запишіть тип файлової системи, пристрій, опції монтування і чи це локально чи мережеве.
- Виміряйте хвости fsync: запускайте невеликий fio fsync-тест на точному шляху datadir/WAL/redo.
- Виділіть логи, якщо потрібно: ізолюйте WAL PostgreSQL або логи MariaDB на швидше сховище, коли латентність комітів є проблемою.
- Стабілізуйте вузол: прибирайте з диска БД конкуренти з інтенсивними записами і перевіряйте, що CPU/пам’ять не обмежують БД у створенні I/O.
- Запишіть вашу позицію щодо надійності: Postgres і MariaDB дозволяють жертвувати безпекою заради швидкості. Зробіть це рішення явним і переглядайте щокварталу.
Правда продуктивності не в «bind mount проти volume». Вона в «який шлях зберігання ви насправді побудували, і чи він поважає fsync під навантаженням?»
Зробіть це правильно, і MariaDB та PostgreSQL поведуться як дорослі системи — передбачувані, достатньо швидкі та безжально чесні щодо фізики.