SQLite на SMB-диске: почему WAL не сливается в основную БД и что с этим делать

Если у вас на SMB-диске "не сливается" WAL в основную SQLite-базу, это часто означает не одну проблему, а смесь из двух разных явлений. Во-первых, само наличие файла db-wal не доказывает неисправность. В нормальном режиме SQLite обычно не обрезает WAL после каждого checkpoint, а переиспользует его. Кроме того, финальный checkpoint и удаление -wal и -shm обычно происходят при закрытии последнего соединения

Вся рубрика SQLite: уроки, инструменты и примеры

Во-вторых, если база лежит на SMB или CIFS, то именно для WAL это архитектурно плохое место. SQLite официально пишет, что WAL не работает по сети из-за зависимости от общего wal-index и shared memory, а также потому, что сетевые файловые системы по-разному реализуют блокировки и flush или sync

Главные риски на SMB или CIFS связаны не только с медленной работой. Это еще и ложная когерентность кэша, рассинхронизация oplocks и leases, несовпадение семантики POSIX advisory locks и SMB mandatory locks, сомнительная надежность fsync() и Flush, а также особенности mmap() и page cache

В результате checkpoint может не доходить до конца, -wal может бесконечно расти или постоянно оставаться грязным, а в худшем случае можно получить I/O errors, зависшие блокировки или повреждение данных

Практический вывод простой. Для продакшена активную SQLite-базу в WAL-режиме на SMB лучше не держать. Если отказаться нельзя, то первым выбором обычно становится rollback journal в режимах DELETE или TRUNCATE, либо перенос live-БД на локальный SSD с резервным копированием или репликацией наружу

Схема проблемы SQLite на SMB: db-wal может быть нормальным файлом, но растущий backlog и busy checkpoint являются красным флагом
Сам факт наличия db-wal еще не доказывает поломку. Красный флаг — когда backlog растет, checkpoint не проходит и клиенты видят разные состояния базы

Если вы вообще используете WAL, сейчас разумно обновиться как минимум до SQLite 3.51.3 или поддерживаемого backport, потому что в марте 2026 SQLite исправил редкий, но реальный WAL-reset bug

Как WAL должен работать

В WAL-режиме SQLite не пишет изменения сразу в основной файл базы. Коммит сначала попадает в файл db-wal, а читатели видят согласованный снимок, комбинируя основной файл БД и нужные страницы из WAL. Чтобы читатели не сканировали весь WAL при каждом чтении, SQLite поддерживает отдельную структуру wal-index, которая хранится в shared memory и обычно реализована как memory-mapped файл db-shm рядом с базой

Именно из-за этой общей памяти SQLite и пишет, что WAL требует, чтобы все участники были на одной машине

Checkpoint переносит уже зафиксированные страницы из db-wal обратно в основной файл базы. SQLite поддерживает варианты PASSIVE, FULL, RESTART, TRUNCATE и NOOP

  • PASSIVE делает максимум без ожидания других читателей и писателей
  • FULL ждет, пока останутся только читатели актуального снимка, и затем дописывает все возможные страницы
  • RESTART дополнительно ждет окончания чтений из WAL
  • TRUNCATE после успешного завершения еще и укорачивает WAL до нуля
  • NOOP полезен для диагностики backlog

PRAGMA wal_checkpoint(...) возвращает три числа: статус busy или ok, число страниц в WAL и число страниц, реально перенесенных в БД

Авточекпоинт включен по умолчанию и срабатывает, когда WAL достигает примерно 1000 страниц. По умолчанию это PASSIVE-checkpoint, и тот же поток, который сделал коммит, пересекший порог, может его запустить. Но важно понимать тонкость: обычный checkpoint не обязан обрезать файл -wal

SQLite часто просто начинает переиспользовать существующий файл, потому что запись поверх уже созданного файла обычно быстрее, чем удаление и создание заново. Обрезка контролируется TRUNCATE или PRAGMA journal_size_limit

В нормальном случае последний открытый коннект при закрытии делает еще один checkpoint и затем удаляет -wal и -shm. Но если последний процесс завершился аварийно или закрылся неаккуратно, WAL может остаться на диске. Следующий открывающий базу процесс запустит recovery. Поэтому после kill -9 увидеть оставшийся db-wal — это ожидаемое поведение, а не автоматический признак поломки

Проблемой это становится тогда, когда backlog в WAL не уменьшается, FULL или TRUNCATE постоянно blocked, либо разные клиенты видят разное состояние базы

Схема нормальной работы SQLite WAL: commit записывает данные в db-wal, checkpoint переносит страницы обратно в app.db
На нормальной файловой системе WAL живет по понятному циклу: commit -> db-wal -> checkpoint -> app.db

Почему SMB и CIFS ломают WAL

Официальная позиция SQLite здесь жесткая: WAL не работает по network filesystem. Причина, указанная в документации, — необходимость общей памяти для wal-index. Поверх этого отдельная страница SQLite про работу через сеть предупреждает, что у сетевых ФС различается надежность fsync() и file locking, и из-за этого remote-file-I/O канал может приводить уже не просто к ошибкам транзакции, а к повреждению базы

Карта рисков SQLite WAL на SMB: shared memory, byte-range locks, fsync, mmap и проблемы oplocks, cache mismatch, mandatory locks
WAL рассчитывает на shared memory, корректные блокировки и честный flush. На SMB эти гарантии зависят от клиента, сервера, mount options и нагрузки

Проблема не только в shared memory, но и в кэшировании SMB. В Linux-клиенте CIFS или SMB локальное кэширование завязано на oplocks и leases. cache=strict старается соблюдать протокол строже и при отсутствии oplock ходит на сервер напрямую

Но cache=loose специально ослабляет семантику ради производительности, и официальная man page прямым текстом предупреждает, что при нескольких читателях и писателях это может приводить к data corruption. Для SQLite это именно тот класс эффектов, который особенно болезненен для WAL

Дальше вступает в игру семантика блокировок. На Unix SQLite опирается на POSIX advisory locks. Документация SQLite прямо говорит, что если реализация системных блокировок работает не так, как обещано, возможна corruption. SMB, в свою очередь, ближе к mandatory locking, а документация Linux CIFS отдельно напоминает, что некоторые приложения ломаются именно из-за mandatory byte-range locks

Уже это показывает, что модель блокировок SMB и то, чего ожидает типичное Unix-приложение, не совпадают один в один

Есть и еще одна неприятная деталь. В документации Microsoft для Win32 сказано, что byte-range locks игнорируются при memory-mapped file access. А mount.cifs отдельно предупреждает, что page cache в сочетании с byte-range locks проблематичен, потому что Windows-style locking mandatory, а mmap()-записи гарантированно уходят на сервер только после msync() или close()

Для SQLite это важно по двум причинам сразу: wal-index — это mmap()-файл -shm, и кроме того некоторые сборки или обертки могут включать memory-mapped I/O для самой БД

Отдельный риск — sync и flush semantics. SQLite исходит из того, что fsync() на Unix и FlushFileBuffers() на Windows действительно означают "данные дошли туда, куда нужно". Но SQLite же предупреждает, что именно на сетевых ФС эти предположения могут не выполниться

В Linux CIFS это не теоретическая угроза: опция nostrictsync прямо говорит "не просить сервер flush при fsync()" и позиционируется как выигрыш производительности ценой consistency. Для SQLite это почти всегда плохая идея

Наконец, не стоит смешивать NFS-советы и SMB-советы. Для SQLite важна не вывеска "сетевая ФС", а конкретные гарантии по locking + sync + mmap. На SMB с WAL эти гарантии слишком часто оказываются не теми, на которые рассчитывает SQLite

Диагностика

Первое, что нужно сделать на практике, — отделить нормальное удержание WAL от реально застрявшего checkpoint. Для этого полезно не смотреть только на наличие db-wal, а опросить сам SQLite: текущий journal_mode, locking_mode, synchronous, размер auto-checkpoint и состояние checkpoint backlog. И еще одна важная мелочь: SQLite официально пишет, что неизвестные PRAGMA тихо игнорируются, так что любая опечатка в диагностике может дать ложную картину

DB=/mnt/smbdb/app.db
sqlite3 "$DB" "PRAGMA journal_mode;
               PRAGMA locking_mode;
               PRAGMA synchronous;
               PRAGMA wal_autocheckpoint;"
sqlite3 "$DB" "PRAGMA wal_checkpoint(NOOP);"
sqlite3 "$DB" "PRAGMA wal_checkpoint(FULL);"
sqlite3 "$DB" "PRAGMA wal_checkpoint(TRUNCATE);"
sqlite3 "$DB" "PRAGMA integrity_check;"

Интерпретация этих команд опирается на официальные PRAGMA-описания и WAL-документацию. В частности, wal_autocheckpoint по умолчанию равен 1000 страницам, wal_checkpoint(...) возвращает busy|log_frames|checkpointed_frames, TRUNCATE укорачивает WAL до нуля только при успешном завершении, а integrity_check должен вернуть ok

Если FULL или TRUNCATE снова и снова дают busy=1, а число неслитых страниц растет, это уже не обычная жизнь WAL, а реальная проблема: либо с висящими ридерами, либо с самой сетевой файловой системой

Диагностический playbook SQLite WAL на SMB: PRAGMA journal_mode, wal_checkpoint, integrity_check и lsof для поиска backlog
Диагностику лучше строить вокруг checkpoint backlog: busy, log frames, checkpointed frames и процессов, которые держат файлы открытыми

Быстрая шпаргалка

ПроверкаНормальноКрасный флаг
PRAGMA journal_mode;wal или ожидаемый rollback-modeВозвращается не тот режим, который вы думаете, что включили
PRAGMA wal_autocheckpoint;1000 по умолчанию или ваше значение0 без осознанной причины
PRAGMA wal_checkpoint(NOOP);backlog не бесконечный, цифры меняются разумноbacklog монотонно растет часами
PRAGMA wal_checkpoint(TRUNCATE);0|N|N или 0|0|0busy=1 снова и снова
PRAGMA integrity_check;okЛюбой другой вывод

Следующий слой диагностики — понять, кто реально держит БД, -wal и -shm открытыми и какие системные вызовы происходят. lsof показывает открытые файлы, fuser — PID, использующие конкретные пути, а strace позволяет увидеть openat, fcntl, fsync, fdatasync, mmap, msync и close

lsof "$DB" "${DB}-wal" "${DB}-shm"
fuser -v "$DB" "${DB}-wal" "${DB}-shm"
strace -ff -tt \
  -e trace=openat,fcntl,fsync,fdatasync,mmap,msync,close \
  -o /tmp/sqlite.trace \
  sqlite3 "$DB" "PRAGMA wal_checkpoint(TRUNCATE);"

Если у вас сервер — это Samba, полезно смотреть и серверную сторону. smbstatus -L -B выводит список locks, включая byte-range locks. Это особенно полезно, когда клиент уверен, что никто ничего не держит, а сервер все еще видит зависшие или активные блокировки

# На Samba-сервере
smbstatus -L -B
# С клиента
smbclient -L server.example.com -m SMB3 -d 3 -U user

Для Linux CIFS-клиента отдельный обязательный пласт — диагностика самого mount-а и ядра

findmnt -no SOURCE,FSTYPE,OPTIONS /mnt/smbdb
cat /proc/fs/cifs/DebugData
cat /proc/fs/cifs/Stats
cat /proc/fs/cifs/open_files
cat /proc/fs/cifs/mount_params
modinfo cifs | less
dmesg -T | egrep 'CIFS|SMB|sqlite'

Практические решения и обходы

Опции монтирования CIFS и SMB

Сначала главное правило: нет такого набора mount options, который сделает WAL на SMB официально хорошей архитектурой. Но есть опции, которые помогают поставить диагноз и уменьшить вероятность кэш- и flush-проблем

ОпцияПрактическая рекомендацияЭффектЦена и caveat
vers=3.1.1Предпочтительно, если обе стороны умеютСовременный SMB dialectЕсли несовместимо, будет fallback
vers=3.0Нормальный fallbackСовременный SMB3 без SMB1Хуже, чем 3.1.1 по современным возможностям
cache=noneХорошо для диагностики и консервативного режимаУбирает обычный data cache клиентаНе убирает все mmap()-аспекты
cache=strictЕсли cache=none слишком дорогБолее строгая coherency при oplock или leaseВсе еще сложнее local FS
cache=looseДля SQLite избегатьБыстрее, но слабее coherencyДокументация прямо предупреждает о corruption
actimeo=0Хорошо для диагностикиВыключает attribute cacheДополнительные RTT и накладные расходы
actimeo=1Разумный балансКороткий metadata cacheКэширование все равно остается
noleaseДиагностический или крайний вариантУбирает часть локального cachingБьет по производительности
nostrictsyncДля SQLite избегатьНе посылает серверу flush на fsync()Выигрыш скорости ценой consistency
nounixТолько как last-resort workaroundВыключает Unix ExtensionsСильно меняет семантику
nobrlДля SQLite избегатьНе отправляет byte-range locks на серверПодрывает locking story
uid=, gid=Почти всегда задаватьПредсказуемый владелец на клиентеЭто только часть общей картины
file_mode=, dir_mode=Задавать явноПредсказуемые mode bitsНе заменяет серверные ACL

Консервативный диагностический mount, с которого удобно начинать расследование:

mount -t cifs //server/share /mnt/smbdb \
  -o credentials=/etc/smb-db.creds,vers=3.1.1,cache=none,actimeo=0,nolease,uid=1000,gid=1000,file_mode=0640,dir_mode=0750

Более боевой, но все еще осторожный вариант:

mount -t cifs //server/share /mnt/smbdb \
  -o credentials=/etc/smb-db.creds,vers=3.1.1,cache=strict,actimeo=1,uid=1000,gid=1000,file_mode=0640,dir_mode=0750

PRAGMA и режимы работы SQLite

Если база все же живет на SMB, то значения PRAGMA synchronous, PRAGMA wal_autocheckpoint и PRAGMA journal_size_limit нужно выставлять осознанно, а не угадывать. В WAL-режиме synchronous=FULL добавляет дополнительный sync WAL после каждого commit и повышает durability. synchronous=NORMAL в WAL остается consistent, но может терять durability при power loss или system crash

На SMB это не магия: если сама сетевая ФС врет про flush, PRAGMA не спасет, но как минимум не надо усугублять ситуацию слишком легкими настройками

wal_autocheckpoint по умолчанию — 1000 страниц. Если ваш -wal раздувается и читатели чувствительны к размеру WAL, снижайте порог осознанно, например до 100500 страниц, и наблюдайте за latency и throughput. journal_size_limit=0 заставляет SQLite после reset или checkpoint укорачивать WAL или rollback-journal до минимального размера

Если сценарий у вас строго одно-процессный и вы все равно хотите оставить WAL, существует узкий workaround: SQLite умеет работать в WAL без shared memory, если до первого доступа выставить PRAGMA locking_mode=EXCLUSIVE. Это убирает -shm, но работает только там, где вы гарантированно единственный процесс, касающийся базы

sqlite3 "$DB" <<'SQL'
PRAGMA locking_mode=EXCLUSIVE;
PRAGMA journal_mode=WAL;
PRAGMA synchronous=FULL;
PRAGMA wal_autocheckpoint=200;
PRAGMA journal_size_limit=0;
SQL

Если вы подозреваете, что конкретная сборка или wrapper поднимает memory-mapped I/O выше нуля, на время диагностики можно вернуть его в ноль, чтобы убрать одну переменную из уравнения

sqlite3 "$DB" "PRAGMA mmap_size=0;"

Периодический внешний checkpoint

SQLite сам пишет, что при synchronous=NORMAL checkpoint — это единственное место, где происходит sync или barrier, и что checkpoint можно вынести в отдельный поток или процесс. На SMB это удобно по двум причинам: вы получаете явную операцию, которую можно логировать, и вы можете регулярно пытаться TRUNCATE-checkpoint-ить WAL, вместо того чтобы надеяться на автоповедение библиотеки

#!/usr/bin/env bash
set -euo pipefail
DB="${1:-/srv/app/app.db}"
OUT="$(sqlite3 -batch "$DB" "PRAGMA wal_checkpoint(TRUNCATE);")"
IFS='|' read -r BUSY LOG CKPT <<<"$OUT"
printf 'ts=%s db=%s busy=%s log=%s checkpointed=%s\n' \
  "$(date -Is)" "$DB" "$BUSY" "$LOG" "$CKPT"
if [[ "$BUSY" != "0" ]]; then
  exit 1
fi

Пример systemd-юнитов:

# /etc/systemd/system/sqlite-checkpoint.service
[Unit]
Description=SQLite WAL checkpoint
[Service]
Type=oneshot
ExecStart=/usr/local/bin/sqlite-wal-checkpoint.sh /srv/app/app.db
# /etc/systemd/system/sqlite-checkpoint.timer
[Unit]
Description=Periodic SQLite WAL checkpoint
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target

Или через cron:

*/5 * * * * /usr/local/bin/sqlite-wal-checkpoint.sh /srv/app/app.db >> /var/log/sqlite-checkpoint.log 2>&1

Переключение на DELETE или TRUNCATE journal_mode

Если база активна на SMB, то наиболее практичный компромисс почти всегда — выйти из WAL и вернуться к rollback journal. В документации SQLite DELETE — режим по умолчанию. TRUNCATE похож, но коммитит транзакцию усечением журнала до нуля, что на некоторых системах быстрее удаления. Это не дает магической поддержки сети, но убирает самую хрупкую для SMB часть — wal-index и shared-memory choreography

sqlite3 "$DB" "PRAGMA journal_mode=DELETE; PRAGMA synchronous=FULL;"
# или
sqlite3 "$DB" "PRAGMA journal_mode=TRUNCATE; PRAGMA synchronous=FULL;"

После любого такого переключения разумно проверить целостность:

sqlite3 "$DB" "PRAGMA integrity_check;"

Локальный SSD плюс backup или rsync вместо live-БД на SMB

Это наиболее практичная схема для большинства случаев. Live-БД живет на локальном SSD, а на SMB вы отправляете уже копию, backup или snapshot. SQLite прямо различает "сеть в канале API-вызовов" и "сеть в канале file I/O" и говорит, что второй вариант несет существенно больший риск corruption

#!/usr/bin/env bash
set -euo pipefail
DB=/srv/app/app.db
TS=$(date +%F-%H%M%S)
BACKUP=/var/backups/app-${TS}.db
sqlite3 "$DB" "PRAGMA wal_checkpoint(TRUNCATE);"
sqlite3 "$DB" "VACUUM INTO '$BACKUP'"
rsync -a "$BACKUP" /mnt/backup-share/

URI-параметры SQLite и примеры на Python и shell

SQLite URI-параметры полезны не только для удобства, но и чтобы явно задать модель доступа. mode=rw предотвращает случайное создание пустой БД, mode=ro хорош для read-only открытия, immutable=1 позволяет открывать truly immutable snapshot без блокировок и проверки изменений, а nolock=1 — крайне опасный флаг, который допустим только если вы гарантируете сериализацию записей сами

Важно не путать cache=private и cache=shared в URI SQLite с SMB-кэшем. Это не про SMB, а про shared-cache mode внутри SQLite. Здесь как раз полезно помнить, что shared-cache mode в SQLite считается устаревшим и нежелательным, поэтому в обычной эксплуатации лучше задавать cache=private

Пример на Python:

import sqlite3
# Основной рабочий доступ: не создавать пустую БД по ошибке, без shared cache
uri = "file:/srv/app/app.db?mode=rw&cache=private"
con = sqlite3.connect(uri, uri=True, timeout=30.0)
con.execute("PRAGMA busy_timeout=30000")
# Read-only immutable snapshot
snap_uri = "file:/srv/snapshots/app-2026-05-06.db?mode=ro&immutable=1&cache=private"
ro = sqlite3.connect(snap_uri, uri=True)

Shell-примеры:

sqlite3 "file:/srv/app/app.db?mode=rw&cache=private" "PRAGMA journal_mode;"
sqlite3 "file:/srv/snapshots/app-2026-05-06.db?mode=ro&immutable=1&cache=private" "SELECT count(*) FROM sqlite_schema;"

Права, UID, GID и клиентские проверки доступа

На CIFS mount полезно сразу выставлять uid=, gid=, file_mode=, dir_mode= и держать credentials=-файл под 0600. Это не решает проблему WAL, но убирает целый класс странных эффектов, когда соседний процесс неожиданно не может создать -wal или -shm, либо получает доступ слишком широко

Осторожно с noperm. Эта опция отключает локальные клиентские permission checks и может открыть файлы другим локальным пользователям. Также в mount.cifs есть неприятная особенность: без negotiated Unix Extensions chmod и chown могут выглядеть успешными, но реально ничего на сервере не менять. Поэтому права лучше проектировать через серверные ACL и предсказуемые mount options

Сравнение стратегий

СтратегияНадежностьПроизводительностьСложностьКогда уместно
Оставить WAL на SMB как естьНизкаяНепредсказуемаяНизкаяПочти никогда
WAL на SMB плюс внешний checkpointНизкая или средняяСредняяСредняяВременный костыль
WAL на SMB плюс locking_mode=EXCLUSIVEСредняя в очень узком случаеСредняяСредняяТолько один процесс и один хост
Переключиться на DELETE или TRUNCATE на SMBСредняяНиже по concurrencyНизкаяЕсли локальный диск невозможен
Держать live-БД локально, на SMB только backupВысокаяХорошаяСредняяЛучший компромисс без миграции СУБД
Перейти на PostgreSQLВысокаяВысокаяВышеМногопользовательская запись по сети
Использовать rqlite или LiteFSСредняя или высокаяЗависит от кластераВышеНужна SQLite-ориентированная HA или репликация

Сценарий воспроизведения

Ниже — минимальный сценарий, который помогает отличить обычное оставление -wal после грубого kill от сетевой патологии. В этом примере mount специально делается более рискованным, с cache=loose, чтобы проблему было легче спровоцировать

# 1. Смонтировать SMB-шару на Linux-клиенте
mount -t cifs //server/share /mnt/smbdb \
  -o credentials=/etc/smb-db.creds,vers=3.1.1,cache=loose,actimeo=1,uid=1000,gid=1000
DB=/mnt/smbdb/repro.db
# 2. Создать БД и включить WAL
sqlite3 "$DB" "PRAGMA journal_mode=WAL; CREATE TABLE IF NOT EXISTS t(id INTEGER PRIMARY KEY, ts TEXT NOT NULL);"
# 3. Запустить писателя в фоне
python3 - <<'PY' &
import sqlite3, time
db = "/mnt/smbdb/repro.db"
con = sqlite3.connect(db, timeout=5)
con.execute("PRAGMA journal_mode=WAL")
while True:
    con.execute("INSERT INTO t(ts) VALUES(datetime('now'))")
    con.commit()
    time.sleep(0.1)
PY
WRITER_PID=$!
# 4. Дать ему поработать и посмотреть состояние WAL
sleep 5
ls -lh "$DB"*
sqlite3 "$DB" "PRAGMA wal_checkpoint(NOOP);"
# 5. Убить процесс без нормального close
kill -9 "$WRITER_PID"
# 6. Проверить, что WAL остался
ls -lh "$DB"*
# 7. Повторно открыть БД и попробовать явный checkpoint
sqlite3 "$DB" "PRAGMA wal_checkpoint(TRUNCATE);"
sqlite3 "$DB" "SELECT count(*) FROM t;"
sqlite3 "$DB" "PRAGMA integrity_check;"

На здоровой локальной ФС после повторного открытия SQLite выполнит recovery, SELECT count(*) увидит все зафиксированные транзакции, integrity_check вернет ok, а wal_checkpoint(TRUNCATE) даст что-то вроде 0|N|N или 0|0|0. На проблемном SMB или CIFS mount возможны повторяющиеся busy, постоянный backlog в wal_checkpoint, I/O errors, отставание видимости данных между клиентами или сообщения ядра и CIFS в dmesg

Мониторинг и предупреждения

Для эксплуатационного мониторинга важнее не сам факт наличия -wal, а размер WAL, динамика backlog и успешность checkpoint. Правильные метрики — это минимум размер db-wal, значения busy|log|checkpointed из wal_checkpoint(NOOP) и клиентские CIFS counters из /proc/fs/cifs/Stats

#!/usr/bin/env bash
set -euo pipefail
DB=/srv/app/app.db
WAL="${DB}-wal"
PROM=/var/lib/node_exporter/textfile_collector/sqlite_wal.prom
SIZE=0
[[ -f "$WAL" ]] && SIZE=$(stat -c %s "$WAL")
CKPT="$(sqlite3 "$DB" "PRAGMA wal_checkpoint(NOOP);" 2>/dev/null || echo "1|-1|-1")"
IFS='|' read -r BUSY LOG CKPTD <<<"$CKPT"
cat > "$PROM" <<EOF
sqlite_wal_size_bytes{db="$DB"} $SIZE
sqlite_wal_busy{db="$DB"} $BUSY
sqlite_wal_log_frames{db="$DB"} $LOG
sqlite_wal_checkpointed_frames{db="$DB"} $CKPTD
EOF

Практически полезны четыре сигнала:

  • WAL больше ожидаемого размера дольше заданного окна
  • FULL или TRUNCATE checkpoint несколько раз подряд blocked
  • разница log_frames - checkpointed_frames монотонно растет
  • в dmesg или /proc/fs/cifs/Stats появляются CIFS reconnects, non-zero return codes или другие ошибки

Хорошей дополнительной проверкой после любого инцидента остается PRAGMA integrity_check;. Если после штатных условий, kill -9, reconnect или неудачного checkpoint вы хоть раз получаете не ok, эксперимент с SMB и WAL лучше прекращать и переходить на более безопасную схему

Рекомендации для продакшена

Для продакшена правило простое: если несколько машин напрямую открывают одну и ту же SQLite-БД по SMB, это уже плохая архитектура. SQLite сам советует в таких случаях брать client/server СУБД. Если нужен многопользовательский доступ по сети с нормальной записью, переходите на PostgreSQL

Если вы хотите остаться ближе к SQLite-модели, но убрать общий сетевой файл, смотрите на решения, где каждая нода работает со своей локальной копией, например rqlite или LiteFS

Если нужны network block devices или clustered filesystems, относитесь к этому как к отдельному инженерному проекту, а не к готовому лечению SMB. Требования SQLite остаются теми же: корректные locking semantics, честный fsync() и предсказуемое поведение mmap()

Решения для SQLite на SMB: временный checkpoint, rollback journal DELETE или TRUNCATE, live база на local SSD и backup на SMB
Для продакшена лучший вариант — держать live-БД локально, а на SMB отправлять backup. Если это невозможно, rollback journal обычно безопаснее WAL на сетевой шаре

Краткий чек-лист быстрого исправления

  • Проверьте, что проблема не мнимая: наличие db-wal само по себе нормально
  • Зафиксируйте реальное состояние БД через PRAGMA journal_mode, locking_mode, synchronous, wal_autocheckpoint и integrity_check
  • Посмотрите, кто держит файлы, через lsof, fuser и на стороне Samba через smbstatus -L -B
  • Снимите реальные CIFS-параметры и debug через findmnt, /proc/fs/cifs/DebugData, Stats и dmesg
  • На время диагностики remount-ните консервативно: vers=3.1.1, cache=none, actimeo=0, без nostrictsync, без cache=loose, без nobrl
  • Если база должна жить на SMB и ее трогают несколько процессов или хостов, уходите из WAL в DELETE или TRUNCATE
  • Если процесс ровно один и перенос невозможен, можно попробовать locking_mode=EXCLUSIVE до входа в WAL, но это workaround, а не поддержка SMB
  • Лучший практический вариант без миграции СУБД: live-БД на локальном SSD, наружу — backup, VACUUM INTO или репликация
  • Если нужен настоящий сетевой многопользовательский write workload, переходите на PostgreSQL или на SQLite-ориентированную репликацию
  • Обновите SQLite хотя бы до 3.51.3 или соответствующего backport, если вы вообще используете WAL
Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x