mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-10-03 00:55:14 -07:00
perf(db): enable WAL and busy timeout for SQLite
The generation worker and request handlers race on the database — enough that orphan-recovery code exists in two places to clean up after 'database is locked' failures. WAL lets readers proceed during a write, synchronous=NORMAL is the recommended pairing, and the 30s sqlite3 timeout waits on a locked database instead of raising immediately.
This commit is contained in:
@@ -3,20 +3,20 @@
|
|||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine, event
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
from .. import config
|
from .. import config
|
||||||
|
from .migrations import run_migrations
|
||||||
from .models import (
|
from .models import (
|
||||||
Base,
|
|
||||||
AudioChannel,
|
AudioChannel,
|
||||||
|
Base,
|
||||||
EffectPreset,
|
EffectPreset,
|
||||||
Generation,
|
Generation,
|
||||||
GenerationVersion,
|
GenerationVersion,
|
||||||
ProfileChannelMapping,
|
ProfileChannelMapping,
|
||||||
VoiceProfile,
|
VoiceProfile,
|
||||||
)
|
)
|
||||||
from .migrations import run_migrations
|
|
||||||
from .seed import backfill_generation_versions, seed_builtin_presets
|
from .seed import backfill_generation_versions, seed_builtin_presets
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -36,9 +36,22 @@ def init_db() -> None:
|
|||||||
|
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
f"sqlite:///{_db_path}",
|
f"sqlite:///{_db_path}",
|
||||||
connect_args={"check_same_thread": False},
|
# timeout is sqlite3's busy handler: wait up to 30s on a locked
|
||||||
|
# database instead of raising "database is locked" immediately.
|
||||||
|
connect_args={"check_same_thread": False, "timeout": 30},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@event.listens_for(engine, "connect")
|
||||||
|
def _set_sqlite_pragmas(dbapi_connection, _connection_record):
|
||||||
|
# WAL lets readers proceed while a writer holds the lock, which is
|
||||||
|
# the main source of lock racing between the generation worker and
|
||||||
|
# request handlers. synchronous=NORMAL is the recommended pairing
|
||||||
|
# (durable across app crashes, fsyncs only on checkpoint).
|
||||||
|
cursor = dbapi_connection.cursor()
|
||||||
|
cursor.execute("PRAGMA journal_mode=WAL")
|
||||||
|
cursor.execute("PRAGMA synchronous=NORMAL")
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
run_migrations(engine)
|
run_migrations(engine)
|
||||||
@@ -47,7 +60,7 @@ def init_db() -> None:
|
|||||||
# Create default audio channel if it doesn't exist
|
# Create default audio channel if it doesn't exist
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
default_channel = db.query(AudioChannel).filter(AudioChannel.is_default == True).first()
|
default_channel = db.query(AudioChannel).filter(AudioChannel.is_default).first()
|
||||||
if not default_channel:
|
if not default_channel:
|
||||||
default_channel = AudioChannel(
|
default_channel = AudioChannel(
|
||||||
id=str(uuid.uuid4()),
|
id=str(uuid.uuid4()),
|
||||||
|
|||||||
Reference in New Issue
Block a user