refactor(db): typed read and write connection types#668
Merged
toksdotdev merged 3 commits intodb/split-pools-retryfrom May 4, 2026
Merged
refactor(db): typed read and write connection types#668toksdotdev merged 3 commits intodb/split-pools-retryfrom
toksdotdev merged 3 commits intodb/split-pools-retryfrom
Conversation
split sea_orm::DatabaseConnection into two newtypes in microsandbox-db: DbReadConnection (multi-conn read pool) and DbWriteConnection (single-conn write pool, retries built in). both implement ConnectionTrait so existing query builders work transparently; the type system enforces which pool a given operation hits. add DbPools::open as the canonical constructor for paired pools, plus DbReadConnection::open and DbWriteConnection::open for stand-alone single-pool callers (e.g. the in-VM runtime). DbWriteConnection::transaction wraps begin/commit/rollback with SQLITE_BUSY retry. generic over the closure error type via a new IsSqliteBusy trait so transaction bodies can return app-level errors (MicrosandboxError, RuntimeError) directly. retry_on_busy in microsandbox-db is now generic over the same trait. migrate microsandbox-runtime to DbWriteConnection end-to-end: connect_db returns the typed handle, insert_run / mark_run_failed / run_metrics_sampler / persist_sample take it. implement IsSqliteBusy for MicrosandboxError and RuntimeError. the host microsandbox crate is intentionally left on the old &DatabaseConnection API in this commit; the call-site sweep follows.
migrate every microsandbox call site from sea_orm::DatabaseConnection to the typed DbReadConnection / DbWriteConnection wrappers introduced in the previous commit. function signatures now declare intent (read vs write) and writes go through DbWriteConnection::transaction so retry-on-busy is consistent. db::init_global / db::init_project now return &'static DbPools and read all tuning (max_connections, connect_timeout_secs, busy_timeout_secs) from the global config rather than taking a per-call override. drops the unused max_connections parameter that every caller was setting to the same config value. internal helpers split by responsibility: - read-only (load_active_run, load_active_pids, load_sandbox_record, build_handle, latest_metric, metrics_for_sandbox) take &DbReadConnection - write-only (update_sandbox_status, mark_sandbox_runtime_stale, mark_sandbox_stopped_for_replacement, insert_sandbox_record, persist_oci_manifest_pin) take &DbWriteConnection - mixed read+write (reconcile_sandbox_runtime_state, load_sandbox_record_reconciled, prepare_create_target, stop_sandbox_for_replacement) take &DbPools removes the local with_retry_transaction shim and the write_pool_for pointer-equality lookup; both are now obviated by the typed handles. mark_sandbox_runtime_stale and mark_sandbox_stopped_for_replacement gain SQLITE_BUSY retry by virtue of moving onto DbWriteConnection::transaction. build_pool in microsandbox-db is tightened back to pub(crate) since constructors (DbPools::open, DbWriteConnection::open, DbReadConnection::open) are now the public surface. test boilerplate consolidated behind a small open_test_pools helper.
init_project, project(), and the PROJECT_POOL OnceCell had no callers in the workspace. the path was carved out for a future <project>/.microsandbox/db/msb.db use case but never wired up; the host always reads/writes the global DB at ~/.microsandbox/db/msb.db. remove the dead code along with the now-unused PathBuf import. easy to add back if/when project-local databases land.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
stacks on #666.
summary
we already split each database into a read pool and a write pool, but both were just
sea_orm::DatabaseConnection. nothing in the type system stopped a write from sneaking onto the read pool, and the retry-on-busy logic had to look up the right pool dynamically. this pr makes the read/write split a property of the type.DbReadConnectionandDbWriteConnectionare newtypes over the underlying connection. both implementConnectionTraitso existing query builders work unchanged, but write transactions are only available onDbWriteConnection. every host call site now declares its intent in its signature: read helpers take&DbReadConnection, write helpers take&DbWriteConnection, and the few that do both take&DbPools.with the typed handles in place, the dynamic write-pool lookup and the per-call max-connections override are gone, and two writes that were managing their own transactions pick up retry-on-busy for free.
a 200-concurrent-boot benchmark shows a ~18% throughput improvement over the previous design (21.3 sandboxes/sec vs 18.2), with median wall time falling from ~6992 ms to ~5950 ms. zero
SQLITE_BUSYerrors observed in either run.test plan