Status: Accepted Date: 2026-04-13
Context: Cloud mode needs persistent storage for projects, workspaces, and sessions. The data model is simple (three entities with CRUD), and the runner is a single-binary deployment — external database dependencies are undesirable.
Decision: Use SQLite via the sqlx crate with async runtime, WAL mode, and compile-time query checking.
Alternatives considered:
- PostgreSQL — full-featured but adds an external dependency, requires a running database server, and is overkill for the data volume (hundreds of rows, not millions). Complicates single-binary deployment.
- Embedded key-value store (sled, rocksdb) — no SQL, no migrations, no relational integrity. Would require hand-rolling queries and schema evolution.
- In-memory only — simple but loses state on restart. Reconciliation would need to reconstruct everything from Docker state, which is lossy.
- File-based JSON — no concurrency guarantees, no atomic transactions, fragile under crashes.
Consequences:
- Zero external dependencies — SQLite is embedded in the binary
- WAL mode enables concurrent async readers without blocking writers
- Foreign key enforcement catches referential integrity bugs at the DB level
- Compile-time query checking (
sqlx::query!macros) catches SQL errors at build time - Optimistic concurrency via
updated_atWHERE clauses prevents lost updates - Migrations in
runner/migrations/provide schema evolution - Trade-off: SQLite doesn't scale to multi-node deployments. If Relay ever needs horizontal scaling, this decision must be revisited. For a single-runner deployment, SQLite is the right choice.
SQLX_OFFLINE=trueallows building without a live database (CI)