diff --git a/.gitignore b/.gitignore index 2920123..b4a93f9 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ logs/ # File-Lock-Artefakte (Race-Condition-Schutz) *.seed.lock +*.init.lock # dist/ ist ein lokales Build-Ausgabeverzeichnis. Docker-Images werden # in die Gitea Container Registry gepusht (bin/build-and-push.sh), nicht diff --git a/quivio/database.py b/quivio/database.py index b162c64..d7a8e42 100644 --- a/quivio/database.py +++ b/quivio/database.py @@ -40,7 +40,20 @@ def get_db(): def init_db() -> None: - """Erstellt alle Tabellen, falls noch nicht vorhanden.""" + """Erstellt alle Tabellen, falls noch nicht vorhanden. + + Bei mehreren Workern (gunicorn) starten alle parallel und rufen + ``init_db()`` auf. Ohne Lock rufen beide ``Base.metadata.create_all()`` + parallel auf, was bei SQLite zu 'table X already exists' fuehren kann. + Ein File-Lock serialisiert das prozessuebergreifend. + + Mit ``checkfirst=True`` (Default seit SQLAlchemy 1.4) wuerde SQLAlchemy + zwar pruefen, ob die Tabelle existiert — aber das ist selbst ein SELECT + und kann zwischen den Workern race-anfaellig sein. Lock ist robuster. + """ + from filelock import FileLock from quivio import models # noqa: F401 – registriert Modelle bei Base.metadata - Base.metadata.create_all(bind=engine) + lock = FileLock(str(DB_PATH) + ".init.lock", timeout=30) + with lock: + Base.metadata.create_all(bind=engine)