Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Storage

Storage Trait

The Storage trait defines the async interface for all data persistence. Both backends implement all 13 methods:

#![allow(unused)]
fn main() {
pub trait Storage: Send + Sync {
    async fn store_session(&self, session: &RunSession) -> Result<(), NinetyNineError>;
    async fn finish_session(&self, session_id: &Uuid, test_count: u32, flaky_count: u32) -> Result<(), NinetyNineError>;
    async fn store_test_run(&self, run: &TestRun, session_id: &Uuid) -> Result<(), NinetyNineError>;
    async fn store_flakiness_score(&self, score: &FlakinessScore) -> Result<(), NinetyNineError>;
    async fn get_test_runs(&self, test_name: &str, limit: u32) -> Result<Vec<TestRun>, NinetyNineError>;
    async fn get_recent_sessions(&self, limit: u32) -> Result<Vec<RunSession>, NinetyNineError>;
    async fn get_all_scores(&self) -> Result<Vec<FlakinessScore>, NinetyNineError>;
    async fn get_score(&self, test_name: &str) -> Result<Option<FlakinessScore>, NinetyNineError>;
    async fn quarantine_test(&self, test_name: &str, reason: &str, score: f64, auto: bool) -> Result<(), NinetyNineError>;
    async fn unquarantine_test(&self, test_name: &str) -> Result<(), NinetyNineError>;
    async fn get_quarantined_tests(&self) -> Result<Vec<QuarantineEntry>, NinetyNineError>;
    async fn is_quarantined(&self, test_name: &str) -> Result<bool, NinetyNineError>;
    async fn purge_older_than(&self, days: u32) -> Result<u64, NinetyNineError>;
}
}

The StorageBackend enum wraps both backends and dispatches calls via a dispatch! macro:

#![allow(unused)]
fn main() {
pub enum StorageBackend {
    Sqlite(SqliteStorage),
    Postgres(PostgresStorage),
}
}

The open_storage() factory function reads the config to initialize the correct backend.

SQLite Backend (Default)

SQLite is the default backend, using rusqlite with the bundled SQLite library. No external database is required.

Default Location

$XDG_DATA_HOME/ninety-nine/ninety-nine.db

On Linux this is typically ~/.local/share/ninety-nine/ninety-nine.db. The parent directory is created automatically if it does not exist.

Features

  • WAL mode – enabled on open for concurrent read access during detection
  • Foreign keys – enforced via PRAGMA foreign_keys=ON
  • Thread safety – Mutex<Connection> guards all access; async trait methods run synchronously within async signatures
  • Bundled SQLite – no system SQLite dependency required

Configuration

[storage]
backend = "Sqlite"
retention_days = 90

[storage.sqlite]
database_path = "/custom/path/ninety-nine.db"

If storage.sqlite is omitted, the default path is used.

Migrations

Schema migrations use SQLite’s PRAGMA user_version. Each migration increments the version. Migrations are idempotent – running the tool against an already-migrated database is safe.

PostgreSQL Backend

PostgreSQL support uses deadpool-postgres for connection pooling with tokio-postgres for async queries.

Features

  • Connection pooling – configurable pool size via deadpool-postgres
  • Pool timeouts – 30s wait, 10s create, 5s recycle
  • Native async – all queries use async tokio-postgres directly
  • Schema migrations – tracked via a schema_migrations table with version and timestamp

Configuration

[storage]
backend = "Postgres"
retention_days = 90

[storage.postgres]
connection_string = "postgresql://user:password@localhost:5432/ninety_nine"
pool_size = 8

Note: Selecting backend = "Postgres" without providing [storage.postgres] will result in a configuration error at startup.

Migrations

PostgreSQL migrations use a schema_migrations table instead of pragmas. The migration system tracks applied versions and only runs new migrations. The schema is identical to SQLite in structure.

Schema

The database has four tables, identical in structure across both backends.

run_sessions

Tracks each detection run session.

ColumnTypeDescription
idTEXT (UUID)Session identifier
started_atTEXT/TIMESTAMPTZWhen the session started
finished_atTEXT/TIMESTAMPTZWhen the session finished (nullable)
test_countINTEGERTotal tests analyzed
flaky_countINTEGERTests classified as flaky
commit_hashTEXTGit commit at time of run
branchTEXTGit branch at time of run

test_runs

Individual test execution results.

ColumnTypeDescription
idTEXT (UUID)Run identifier
session_idTEXT (FK)Parent session
test_nameTEXTFully qualified test name
test_pathTEXTBinary path
outcomeTEXTpassed, failed, timeout, panic, ignored
duration_msINTEGER/BIGINTExecution time in milliseconds
timestampTEXT/TIMESTAMPTZWhen this run occurred
commit_hashTEXTGit commit
branchTEXTGit branch
retry_countINTEGERNumber of retries used
error_messageTEXTStderr/stdout on failure (nullable)
stack_traceTEXTStack trace if available (nullable)
env_osTEXTOperating system
env_rust_versionTEXTRust toolchain version
env_cpu_countINTEGERCPU core count
env_memory_gbREAL/DOUBLE PRECISIONSystem memory in GB
env_is_ciINTEGER/BOOLEANWhether running in CI
env_ci_providerTEXTCI provider name (nullable)

Indexed on test_name, timestamp, and session_id.

flakiness_scores

Latest computed flakiness scores, upserted on test_name.

ColumnTypeDescription
test_nameTEXT (PK)Fully qualified test name
probability_flakyREALBayesian P(flaky)
confidenceREAL1 - credible interval width
pass_rateREALPasses / total
fail_rateREALFailures / total
total_runsINTEGERNumber of runs
consecutive_failuresINTEGERTrailing failures
last_updatedTEXT/TIMESTAMPTZLast computation time
alphaREALBeta distribution alpha parameter
betaREALBeta distribution beta parameter
posterior_meanREALPosterior mean
posterior_varianceREALPosterior variance
ci_lowerREAL95% credible interval lower bound
ci_upperREAL95% credible interval upper bound

quarantine

Quarantined test records.

ColumnTypeDescription
test_nameTEXT (PK)Fully qualified test name
quarantined_atTEXT/TIMESTAMPTZWhen quarantined
reasonTEXTReason for quarantine
flakiness_scoreREALP(flaky) at time of quarantine
auto_quarantinedINTEGER/BOOLEANWhether auto-quarantined

Indexed on quarantined_at.

Data Retention

Old test runs are automatically purged based on the retention_days config (default: 90 days). Purging happens after each test session. Only the test_runs table is purged – flakiness_scores and quarantine entries persist.

[storage]
retention_days = 30