Spaces:
Build error
SQLite Connection Management
Overview
The application uses a robust SQLite connection management strategy designed to handle high concurrency and prevent "database is locked" errors.
Journal Mode Selection
The application uses different journal modes depending on database characteristics:
WAL Mode (Write-Ahead Logging)
Used for databases with high concurrency and frequent reads:
metadata.db- Main file metadata databasesessions.db- User session data
Benefits: Allows concurrent reads during writes, better performance for read-heavy workloads.
Drawback: WAL files can become corrupted under rapid concurrent access during initialization.
DELETE Mode
Used for simple databases with infrequent writes:
locks.db- File locking database
Benefits: Simpler, no WAL file corruption issues, sufficient for low-concurrency use cases.
When to use DELETE mode:
- Small databases with infrequent writes
- Short-lived data (like locks)
- Databases that don't benefit from WAL's read concurrency
- When rapid concurrent access during tests causes WAL corruption
See fastapi_app/lib/core/locking.py for an example of DELETE mode implementation.
Key Components
1. DatabaseManager (fastapi_app/lib/core/database.py)
The core class for database interaction. It implements:
- Connection Pooling: Uses
queue.Queueto reuse connections, reducing the overhead of opening/closing files and avoiding file descriptor exhaustion. - WAL Mode Initialization: Ensures WAL mode is enabled safely using a raw connection and file locking during startup (
_ensure_db_exists). - Transaction Management: Provides a
transaction()context manager that explicitly handlesBEGIN,COMMIT, andROLLBACK. - Autocommit Mode: Connections are opened with
isolation_level=None(autocommit) to allow manual transaction control and prevent implicit transactions from locking the database unexpectedly.
2. Singleton Pattern (fastapi_app/lib/core/dependencies.py)
_DatabaseManagerSingletonensures only oneDatabaseManagerinstance exists per database file.- This allows the connection pool to be shared across the application, preventing multiple pools from competing for the same database file.
3. Locking (fastapi_app/lib/core/sqlite_utils.py)
with_db_lock(db_path): Uses a reentrant lock (threading.RLock) to serialize schema initialization and WAL mode setup per database file.
4. Busy Timeout
All connections set PRAGMA busy_timeout = 30000 (30 seconds) to wait for locks instead of failing immediately with "database is locked" errors.
Connection Lifecycle
- Acquisition:
get_connection()attempts to retrieve a connection from the pool. If empty, it creates a newsqlite3.Connectionwithtimeout=60.0andisolation_level=None. - Usage: The connection is yielded to the caller.
- Release:
conn.rollback()is called to ensure no uncommitted state leaks to the next user.- The connection is put back into the pool.
Best Practices for Code Assistants
- Always use
DatabaseManager: Do not create rawsqlite3.connect()calls in business logic. - Use
transaction()for writes: Ensure atomicity for INSERT/UPDATE/DELETE operations. - Pass
DatabaseManagerinstances: When classes need database access (e.g.,StorageReferenceManager), pass the initialized manager instance, not the file path, to utilize the pool. - Choose the right journal mode: Use WAL for high-concurrency databases, DELETE for simple low-write databases.
- Always set busy_timeout: Use
conn.execute("PRAGMA busy_timeout = 30000")to prevent immediate failures on lock contention.