michal/tit
Browse tree · Show commit · Download archive
Blob: tests/sqlite_workload.rs
#[allow(
dead_code,
reason = "the M1A workload does not use Git operation intents"
)]
#[path = "../src/store/mod.rs"]
mod store;
use std::fs;
use std::time::{Duration, Instant};
use rusqlite::{Connection, params};
use store::Store;
use tempfile::TempDir;
const V1_FIXTURE: &str = include_str!("fixtures/sqlite/v1.sql");
const ISSUE_COUNT: i64 = 10_000;
const EVENTS_PER_ISSUE: i64 = 100;
const EVENT_COUNT: i64 = ISSUE_COUNT * EVENTS_PER_ISSUE;
const MAX_DATABASE_BYTES: u64 = 1_073_741_824;
const MAX_OPERATION_TIME: Duration = Duration::from_secs(120);
const MAX_P99_QUERY_TIME: Duration = Duration::from_millis(250);
#[test]
#[ignore = "run this workload explicitly"]
fn measures_the_m1a_workload() {
let directory = TempDir::new().expect("create a temporary directory");
let database_path = directory.path().join("workload.sqlite");
let backup_path = directory.path().join("workload.backup.sqlite");
create_version_one_workload(&database_path);
let migration_started = Instant::now();
let store = Store::open(&database_path).expect("back up and migrate the workload");
let migration_time = migration_started.elapsed();
store.checkpoint().expect("checkpoint the workload");
let database_bytes = fs::metadata(&database_path)
.expect("inspect the workload database")
.len();
let query_times = measure_queries(&store);
let p50_query_time = percentile(&query_times, 50);
let p95_query_time = percentile(&query_times, 95);
let p99_query_time = percentile(&query_times, 99);
let backup_started = Instant::now();
store.backup(&backup_path).expect("back up the workload");
let backup_time = backup_started.elapsed();
let restored = Store::open(&backup_path).expect("restore the workload backup");
restored
.integrity_check()
.expect("check restored workload integrity");
let restored_counts: (i64, i64) = restored
.connection()
.query_row(
"SELECT (SELECT count(*) FROM m1a_parent), \
(SELECT count(*) FROM m1a_child)",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.expect("count restored workload records");
assert_eq!(restored_counts, (ISSUE_COUNT, EVENT_COUNT));
eprintln!(
"M1A workload: database_bytes={database_bytes} migration_ms={} backup_ms={} query_p50_us={} query_p95_us={} query_p99_us={}",
migration_time.as_millis(),
backup_time.as_millis(),
p50_query_time.as_micros(),
p95_query_time.as_micros(),
p99_query_time.as_micros()
);
assert!(database_bytes <= MAX_DATABASE_BYTES);
assert!(migration_time <= MAX_OPERATION_TIME);
assert!(backup_time <= MAX_OPERATION_TIME);
assert!(p99_query_time <= MAX_P99_QUERY_TIME);
}
fn create_version_one_workload(path: &std::path::Path) {
let mut connection = Connection::open(path).expect("open the workload database");
connection
.execute_batch(V1_FIXTURE)
.expect("create the version-one schema");
connection
.execute_batch("DELETE FROM m1a_child; DELETE FROM m1a_parent;")
.expect("remove the small fixture records");
connection
.pragma_update(None, "foreign_keys", true)
.expect("enable foreign keys");
let transaction = connection.transaction().expect("start the workload");
{
let mut insert_issue = transaction
.prepare("INSERT INTO m1a_parent (id, name, created_at) VALUES (?1, ?2, ?3)")
.expect("prepare the issue insert");
let mut insert_event = transaction
.prepare(
"INSERT INTO m1a_child (id, parent_id, sequence, body) VALUES (?1, ?2, ?3, ?4)",
)
.expect("prepare the event insert");
let body = "A representative issue event records a bounded plain-text change.";
for issue_id in 1..=ISSUE_COUNT {
insert_issue
.execute(params![issue_id, format!("issue-{issue_id}"), issue_id])
.expect("insert an issue");
for sequence in 0..EVENTS_PER_ISSUE {
let event_id = (issue_id - 1) * EVENTS_PER_ISSUE + sequence + 1;
insert_event
.execute(params![event_id, issue_id, sequence, body])
.expect("insert an event");
}
}
}
transaction.commit().expect("commit the workload");
}
fn measure_queries(store: &Store) -> Vec<Duration> {
let mut times = Vec::with_capacity(1_000);
let mut statement = store
.connection()
.prepare(
"SELECT id, sequence, body FROM m1a_child \
WHERE state = ?1 AND parent_id = ?2 ORDER BY sequence LIMIT 25",
)
.expect("prepare the representative query");
for sample in 0_i64..1_000 {
let issue_id = sample % ISSUE_COUNT + 1;
let started = Instant::now();
let mut rows = statement
.query(params!["open", issue_id])
.expect("query issue events");
let mut row_count = 0;
while let Some(row) = rows.next().expect("read an issue event") {
let _: (i64, i64, String) = (
row.get(0).expect("read the event ID"),
row.get(1).expect("read the sequence"),
row.get(2).expect("read the body"),
);
row_count += 1;
}
assert_eq!(row_count, 25);
times.push(started.elapsed());
}
times.sort_unstable();
times
}
fn percentile(times: &[Duration], percentile: usize) -> Duration {
times[(times.len() - 1) * percentile / 100]
}