-
Notifications
You must be signed in to change notification settings - Fork 0
Database and Migrations
Guruprasath Annadurai edited this page Jul 3, 2026
·
1 revision
| Database | Driver | Use case |
|---|---|---|
| SQLite | aiosqlite |
Development, single-server deployments |
| PostgreSQL | asyncpg |
Production, multi-server, horizontal scaling |
Priority order for database URL resolution:
-
RAI_DB_URL— full SQLAlchemy-style URL (takes priority) -
RAI_DATABASE_URL— alias forRAI_DB_URL -
RAI_DB_PATH— SQLite file path (converted tosqlite+aiosqlite:///{path}) - Default:
governance.dbin the current directory
# Default (governance.db in cwd)
uvicorn responsibleai.dashboard.app:app --port 8765
# Custom path
RAI_DB_PATH=/var/lib/rai/governance.db uvicorn ...
# In-memory (tests / ephemeral)
RAI_DB_PATH=:memory: uvicorn ...RAI_DB_URL=postgresql://user:password@host:5432/responsibleai uvicorn ...
# Or with the standard Heroku-style URL
RAI_DATABASE_URL=postgresql://user:password@host:5432/responsibleai uvicorn ...The async engine is configured with:
-
pool_size=10— base connection pool -
max_overflow=20— burst capacity -
pool_pre_ping=True— detect stale connections -
pool_recycle=3600— recycle connections hourly
ResponsibleAI uses Alembic for versioned schema management. The initial migration (0001) creates all 8 tables with their indexes.
# SQLite
RAI_DB_PATH=/var/lib/rai/governance.db alembic upgrade head
# PostgreSQL
RAI_DB_URL=postgresql://user:pass@host:5432/db alembic upgrade head
# In-memory (for testing only — no permanent effect)
RAI_DB_PATH=:memory: alembic upgrade headalembic current # currently applied revision
alembic history # full revision chain
alembic heads # latest available revisionsalembic downgrade -1 # one step back
alembic downgrade 0001 # back to specific revision
alembic downgrade base # full rollback (drops all tables)After changing src/responsibleai/db/engine.py:
alembic revision --autogenerate -m "add_new_column"Review the generated file in migrations/versions/ before applying.
Stores per-request token cost records.
| Column | Type | Notes |
|---|---|---|
| id | INTEGER PK | autoincrement |
| request_id | VARCHAR(64) UNIQUE | idempotency key |
| provider | VARCHAR(50) | openai / anthropic / ... |
| model | VARCHAR(100) | model identifier |
| team | VARCHAR(100) | cost centre |
| application | VARCHAR(100) | service name |
| input_tokens | INTEGER | prompt tokens |
| output_tokens | INTEGER | completion tokens |
| cached_tokens | INTEGER | cached prompt tokens |
| input_cost | FLOAT | USD |
| output_cost | FLOAT | USD |
| total_cost | FLOAT | USD |
| recorded_at | VARCHAR(32) | ISO-8601 timestamp |
Persists webhook delivery attempts for retry recovery.
| Column | Type | Notes |
|---|---|---|
| id | VARCHAR(36) PK | UUID |
| webhook_id | VARCHAR(36) | references registered webhook |
| event | VARCHAR(64) | event name |
| payload | TEXT | JSON |
| status | VARCHAR(20) | pending / retrying / delivered / failed |
| attempts | INTEGER | attempt count |
| max_retries | INTEGER | configured maximum |
| status_code | INTEGER | last HTTP response code |
| last_error | TEXT | last error message |
| next_retry_at | VARCHAR(32) | ISO-8601, NULL when not retrying |
| delivered_at | VARCHAR(32) | ISO-8601, NULL until success |
When SQLite is detected, the engine runs:
PRAGMA journal_mode=WAL; -- concurrent reads during writes
PRAGMA synchronous=NORMAL; -- durable without fsync on every writeThese run on every connection in DatabaseEngine.init().
All tests use :memory: by default. The fixture pattern:
@pytest.fixture()
async def db_engine():
engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
async with engine.begin() as conn:
await conn.run_sync(metadata.create_all)
yield DatabaseEngine(engine)
await engine.dispose()