Коротко — ошибка sqlite3.OperationalError: "unable to open database file" означает, что в момент обращения к файлу БД ОС/файловая система не позволила открыть файл. Разрыв и последующее восстановление указывает на временную проблему в системе (правами, доступностью файла, монтированием, исчерпанием дескрипторов и т.п.), а не на «слет» самого sqlite как такового. Возможные причины и шаги диагностики/исправления:
Возможные причины
- Неправильный путь или относительный путь: рабочая директория процесса меняется, файл не находится по указанному относительному пути.
- Права доступа/владелец: процесс не имеет прав читать/писать файл или директорию.
- Файловая система стала «недоступной» на время: диск отмонтирован/перемонтирован, смонтирован как read‑only (kernel remount), или файл находится на NFS/SMB, где блокировки не работают корректно.
- Недостаток дискового пространства или инодов (df -h, df -i).
- Лимит открытых файлов (ulimit -n) или утечка дескрипторов: процесс исчерпал fd и не может открыть новый файл.
- Конкуренция/блокировки: большой параллелизм (много процессов/потоков) и sqlite не справляется с одновременными записями; синхронные блокировки приводят к ошибкам.
- Бэкапы/логика ротации: скрипт/backup/логротейт перемещает или блокирует файл в момент обращения.
- SELinux/AppArmor/антивирус: политики запретили доступ временами.
- Файловая система временная (tmpfs) или очищается cron’ом (например /tmp).
- Повреждение файла/файловой системы или OOM/kernel remount read‑only — тогда FS может становиться недоступной на некоторое время.
Диагностика (что проверить прямо сейчас)
- Проверить свободное место и иноды:
- df -h /path/to/db
- df -i /path/to/db
- Проверить права и владельца:
- ls -l /path/to/db
- ls -ld /path/to (директория)
- Проверить как смонтирован диск:
- mount | grep "$(dirname /path/to/db)"
- или findmnt /path/to/db
- если NFS/SMB — вероятная проблема блокировок.
- Проверить логи ядра на ошибки диска/ремонта:
- dmesg | tail -n 50
- journalctl -k -n 200
- Проверить логи systemd/syslog на remount/read-only, OOM и т.п.
- Проверить лимиты и открытые файлы:
- sudo ls -l /proc/$(pidof python)/fd | wc -l
- cat /proc/$(pidof python)/limits (или ulimit -n)
- lsof | grep /path/to/db
- Посмотреть, не перемещают ли файл/директорию бэкап/логрутейты/cron:
- grep -R "/path/to/db" /etc/cron* /etc/logrotate.d и т.п.
- Если в контейнере/Docker — проверить mounts/volumes и логи контейнера.
- Проверить SELinux:
- sestatus или journalctl | grep AVC
Краткие быстрые исправления (быстро применить)
- Использовать абсолютный путь к файлу БД в коде (без относительных путей).
- Убедиться, что файл и папка имеют корректного владельца/права (пользователь, от которого запускается бот, должен иметь rwx на директорию и rw на файл).
- Если проблема — на NFS/SMB, перенести SQLite-файл на локальный диск (ext4) или использовать СУБД клиент‑сервер (Postgres/MySQL).
- Увеличить timeout при подключении sqlite: sqlite3.connect(path, timeout=30) — это заставит ждать при блокировках, вместо мгновенной ошибки.
- Включить WAL-режим для лучшей работы с параллельным чтением: PRAGMA journal_mode=WAL; но на NFS WAL не рекомендован.
- Добавить повторные попытки подключения с экспоненциальным бэкофом в коде (retry при OperationalError).
- Закрывать соединения корректно и переиспользовать соединение вместо многократного открытия/закрытия (connection pooling в пределах процесса).
Архитектурные/долгосрочные решения
- Если это многопроцессное/высоконагруженное приложение с частыми записями — перейти на клиент‑серверную СУБД (Postgres/MySQL). SQLite хорошо для встраиваемых/низконапряжённых сценариев, но не для высокого параллелизма.
- Если хотите оставить SQLite: держать одну «writer» задачи (через очередь) и позволять остальным только читать; или использовать единую библиотеку/модуль, координирующий доступ.
- Настроить мониторинг диска/инодов и лимитов fd, чтобы заранее видеть проблему.
Пример мелкой доработки в Python (повтор попытки + абсолютный путь + timeout)
- Использовать абсолютный путь:
- db = sqlite3.connect("/var/lib/mybot/mydb.sqlite", timeout=30, check_same_thread=False)
- Пример retry:
- for i in range(5):
try:
conn = sqlite3.connect(db_path, timeout=30)
break
except sqlite3.OperationalError as e:
time.sleep(2 ** i)
- if conn is None: лог / аварийное завершение
Что делать дальше / если хотите — присылайте
- Вывод результата команд: df -h, df -i, ls -l /path/to/db, mount | grep /path/to, dmesg | tail, ulimit -n или /proc/<pid>/limits, а также информацию о том, запускается ли бот в контейнере, на NFS, или есть ли множественные процесс/потоки, и я помогу конкретнее.
Если коротко: сначала проверьте права, диск/иноды, монтирование (особенно NFS) и лимиты дескрипторов; чаще всего в таких случаях причина — файл на сетевом/эпhemeral хранилище или исчерпание ресурсов/прав.