База данных Django/Celery SQLite заблокирована при одновременном доступе
У меня есть локальный проект на Django 5.1/Celery 5.4, который использует SQLite. Я единственный пользователь.
Определенные сохранения модели запускают задачу Celery, которая запрашивает (SELECT
) обновленную запись (используя Django ORM), затем запускает вызов API для обновления удаленной записи на основе локальных данных, а затем запускает другую UPDATE
локально. Задача заключает все это в with transaction.atomic():
.
(Celery worker настроен на последовательный запуск задач.)
Во время выполнения этой задачи любые попытки записи в базу данных приводят к ошибке "база данных заблокирована".
Я настроил Django/SQLite с последними настройками "готовности к работе":
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': DB_DIR / 'db.sqlite3',
'OPTIONS': {
'init_command': """
PRAGMA foreign_keys=ON;
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA busy_timeout = 5000;
PRAGMA temp_store = MEMORY;
PRAGMA mmap_size=134217728;
PRAGMA journal_size_limit=67108864;
PRAGMA cache_size=2000;
""",
'transaction_mode': 'IMMEDIATE',
'timeout': 20,
},
},
}
У меня сложилось впечатление, что при таких настройках возможен одновременный доступ. "SQLite in Production" - это последнее новшество, и эти настройки, особенно новые для Django 5.1 'transaction_mode': 'IMMEDIATE'
в настройках, позволят записывать данные в очередь. Что я упускаю?
Хотя SQLite может обрабатывать некоторый параллелизм с WAL, он по-прежнему ограничен в многопроцессорных средах (таких как Django + Celery). Я бы посоветовал либо использовать отдельную базу данных для Celery, либо перейти на полноценную базу данных, такую как PostgreSQL.
Если в ваших задачах Celery не требуется выполнять локально сложные операции с SQL, вы можете попробовать использовать отдельную базу данных для Celery, прежде чем переходить на более удобную конфигурацию базы данных:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': DB_DIR / 'db.sqlite3',
'OPTIONS': {...}, # Your app DB
},
'celery': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': DB_DIR / 'celery.sqlite3',
'OPTIONS': {...}, # A dedicated instance for Celery
},
}
А затем вызовите:
from django.db import connections
def your_task():
with connections['celery'].cursor() as cursor:
cursor.execute("YOUR QUERY")
Подробнее об ошибке “База данных заблокирована” в разделе Django docs
Решение в данном конкретном случае состояло в том, чтобы сократить время моей транзакции, т.е. не удерживать транзакцию при выполнении внешнего вызова API. Это означает, что я должен быть более осторожен и не позволять представлениям и задачам наступать друг другу на пятки.
Я все еще озадачен тем, что так называемые настройки "готовности к работе" не допускают одновременного доступа, даже без очереди + тайм-аута!