pdf-tei-editor / docs /code-assistant /database-connections.md
cmboulanger's picture
disaster-recovery deploy of pdf-tei-editor
6a49f21 verified
|
Raw
History Blame Contribute Delete
3.73 kB

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 database
  • sessions.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.Queue to 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 handles BEGIN, COMMIT, and ROLLBACK.
  • 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)

  • _DatabaseManagerSingleton ensures only one DatabaseManager instance 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

  1. Acquisition: get_connection() attempts to retrieve a connection from the pool. If empty, it creates a new sqlite3.Connection with timeout=60.0 and isolation_level=None.
  2. Usage: The connection is yielded to the caller.
  3. 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 raw sqlite3.connect() calls in business logic.
  • Use transaction() for writes: Ensure atomicity for INSERT/UPDATE/DELETE operations.
  • Pass DatabaseManager instances: 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.