Skip to content

Commit 2adc22a

Browse files
feat(db): implement WAL mode for SQLite to improve concurrency and error handling
1 parent 9c6646f commit 2adc22a

1 file changed

Lines changed: 14 additions & 2 deletions

File tree

app/db/base.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any
22

3-
from sqlalchemy import MetaData
3+
from sqlalchemy import MetaData, event
44
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
55
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass
66

@@ -33,7 +33,7 @@ def build_engine_options(settings: DatabaseSettings) -> dict[str, Any]:
3333
def database_pool_summary(settings: DatabaseSettings, process_count: int = 1) -> str:
3434
"""Describe the effective connection budget without exposing the database URL."""
3535
if settings.is_sqlite:
36-
return "Database pool: SQLite driver-managed connections (QueuePool settings do not apply)."
36+
return "Database pool: SQLite driver-managed connections in WAL mode (QueuePool settings do not apply)."
3737

3838
per_process = settings.connection_ceiling()
3939
service_ceiling = settings.connection_ceiling(process_count)
@@ -47,6 +47,18 @@ def database_pool_summary(settings: DatabaseSettings, process_count: int = 1) ->
4747

4848
engine = create_async_engine(database_settings.url, **build_engine_options(database_settings))
4949

50+
if database_settings.is_sqlite:
51+
# WAL mode lets readers proceed concurrently with the single writer, preventing
52+
# "database is locked" errors under async workloads. busy_timeout makes SQLite
53+
# retry for up to 5 s on a locked write instead of failing immediately.
54+
@event.listens_for(engine.sync_engine, "connect")
55+
def _set_sqlite_pragmas(dbapi_conn, _connection_record):
56+
cursor = dbapi_conn.cursor()
57+
cursor.execute("PRAGMA journal_mode=WAL")
58+
cursor.execute("PRAGMA busy_timeout=5000")
59+
cursor.close()
60+
61+
5062
SessionLocal = async_sessionmaker(autocommit=False, autoflush=False, expire_on_commit=False, bind=engine)
5163

5264
naming_convention = {

0 commit comments

Comments
 (0)