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

Во-вторых, если база лежит на 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 с резервным копированием или репликацией наружу

Если вы вообще используете WAL, сейчас разумно обновиться как минимум до SQLite 3.51.3 или поддерживаемого backport, потому что в марте 2026 SQLite исправил редкий, но реальный WAL-reset bug
- Как WAL должен работать
- Почему SMB и CIFS ломают WAL
- Диагностика
- Быстрая шпаргалка
- Практические решения и обходы
- Опции монтирования CIFS и SMB
- PRAGMA и режимы работы SQLite
- Периодический внешний checkpoint
- Переключение на DELETE или TRUNCATE journal_mode
- Локальный SSD плюс backup или rsync вместо live-БД на SMB
- URI-параметры SQLite и примеры на Python и shell
- Права, UID, GID и клиентские проверки доступа
- Сравнение стратегий
- Сценарий воспроизведения
- Мониторинг и предупреждения
- Рекомендации для продакшена
- Краткий чек-лист быстрого исправления
Как 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дополнительно ждет окончания чтений из WALTRUNCATEпосле успешного завершения еще и укорачивает 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, либо разные клиенты видят разное состояние базы

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

Проблема не только в 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, а реальная проблема: либо с висящими ридерами, либо с самой сетевой файловой системой

Быстрая шпаргалка
| Проверка | Нормально | Красный флаг |
|---|---|---|
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|0 | busy=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, снижайте порог осознанно, например до 100–500 страниц, и наблюдайте за 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илиTRUNCATEcheckpoint несколько раз подряд 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()

Краткий чек-лист быстрого исправления
- Проверьте, что проблема не мнимая: наличие
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



