Co-authored-by: Copilot <copilot@github.com>
2.4 KiB
DuckDB Concurrency and Storage
Single Writer Per Process
DuckDB allows only one process to open the database file in read-write mode at a time. The FastAPI backend must be run with a single worker (uvicorn --workers 1). Running multiple workers against the same DuckDB file will cause startup errors.
asyncio.Lock for Writes
All database write operations (INSERT, UPDATE, DELETE) in the FastAPI async context are wrapped in a single asyncio.Lock (get_write_lock() from backend/app/db.py). This prevents concurrent coroutines from issuing overlapping writes within the single process, which would otherwise raise DuckDB optimistic concurrency errors.
Read operations (SELECT) do not require the lock — DuckDB's MVCC provides consistent read snapshots.
Schema
CREATE TABLE users (
id UUID DEFAULT uuid() PRIMARY KEY,
email VARCHAR NOT NULL UNIQUE,
password_hash VARCHAR NOT NULL,
role VARCHAR DEFAULT 'user',
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now()
);
CREATE TABLE refresh_tokens (
jti UUID DEFAULT uuid() PRIMARY KEY,
user_id UUID NOT NULL, -- soft FK to users.id
issued_at TIMESTAMP DEFAULT now(),
expires_at TIMESTAMP NOT NULL,
revoked BOOLEAN DEFAULT false
);
The
REFERENCES users(id)foreign key is intentionally omitted fromrefresh_tokens. DuckDB fires FK checks onUPDATEof the parent table (including email changes), causing false constraint violations. Referential integrity is enforced manually: deleting a user also deletes their refresh tokens in the same write transaction.
Access Tokens
Access tokens are stateless JWTs — not stored in the database. They are validated by signature and expiry claim only. The short TTL (15 minutes) limits the blast radius if a token is leaked.
Refresh Tokens
Refresh tokens store a JTI (JWT ID) UUID in the refresh_tokens table. On each use the old JTI is revoked and a new one issued (rotation). On logout the JTI is immediately revoked. Expired and revoked tokens can be purged via POST /admin/tokens/purge.
Future: AI Generation History
AI generation metadata (model, prompt, cost, result URLs) can be stored as JSON columns in a future generation_history table in DuckDB, enabling per-user analytics and usage dashboards at zero extra infrastructure cost.