#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
use std::{
sync::Arc,
time::{Duration, Instant},
};
use moraine::{Catalog, CatalogOptions, ColumnDef};
use object_store::{ObjectStore, aws::AmazonS3Builder};
fn s3_store() -> Arc<dyn ObjectStore> {
let endpoint = std::env::var("MORAINE_S3_ENDPOINT")
.expect("MORAINE_S3_ENDPOINT must be set (see this module's doc comment)");
let bucket = std::env::var("MORAINE_S3_BUCKET")
.expect("MORAINE_S3_BUCKET must be set (see this module's doc comment)");
Arc::new(
AmazonS3Builder::new()
.with_endpoint(endpoint)
.with_bucket_name(bucket)
.with_access_key_id("minioadmin")
.with_secret_access_key("minioadmin")
.with_region("us-east-1")
.with_allow_http(true)
.build()
.expect("S3 store from test configuration"),
)
}
fn options_at(path: &str) -> CatalogOptions {
let mut options = CatalogOptions::default();
options.path = path.to_string();
options
}
#[tokio::test]
#[ignore = "needs a live S3 endpoint; run through `cargo xtask s3`"]
async fn bootstrap_commit_and_reopen_on_s3() {
let store = s3_store();
let catalog = Catalog::open(store.clone(), options_at("reopen"))
.await
.unwrap();
catalog
.commit(|tx| tx.create_schema("sales").map(|_| ()))
.await
.unwrap();
catalog.close().await.unwrap();
let catalog = Catalog::open(store, options_at("reopen")).await.unwrap();
let head = catalog.snapshot().await.unwrap();
assert!(head.schema_by_name("sales").is_some());
catalog.close().await.unwrap();
}
#[tokio::test]
#[ignore = "needs a live S3 endpoint; run through `cargo xtask s3`"]
async fn read_only_catalog_reads_s3_state() {
let store = s3_store();
let catalog = Catalog::open(store.clone(), options_at("reader"))
.await
.unwrap();
catalog
.commit(|tx| tx.create_schema("analytics").map(|_| ()))
.await
.unwrap();
catalog.close().await.unwrap();
let reader = Catalog::open_read_only(store, options_at("reader"))
.await
.unwrap();
let head = reader.snapshot().await.unwrap();
assert!(head.schema_by_name("analytics").is_some());
reader.close().await.unwrap();
}
#[tokio::test]
#[ignore = "needs a live S3 endpoint; run through `cargo xtask s3`"]
async fn measure_commit_latency_against_the_endpoint() {
const COMMITS: usize = 20;
let intervals = [1u64, 25, 100];
let store = s3_store();
println!(
"\n# 0004 durable-commit latency against {}",
std::env::var("MORAINE_S3_ENDPOINT").unwrap_or_default()
);
println!("# {COMMITS} sequential one-table commits per row, each await_durable\n");
println!(
"{:>10} {:>11} {:>9} {:>9}",
"flush_ms", "median_ms", "min_ms", "max_ms"
);
for (row, flush_ms) in intervals.iter().enumerate() {
let mut options = options_at(&format!("latency-{flush_ms}-{row}"));
options.flush_interval = Duration::from_millis(*flush_ms);
let catalog = Catalog::open(store.clone(), options).await.unwrap();
let mut samples = Vec::with_capacity(COMMITS);
for i in 0..COMMITS {
let start = Instant::now();
catalog
.commit(move |tx| {
let schema = tx.schema_by_name("main").expect("bootstrap").id;
tx.create_table(
schema,
&format!("t{i}"),
&[ColumnDef {
name: "a".into(),
column_type: "BIGINT".into(),
nulls_allowed: true,
default_value: None,
}],
)?;
Ok(())
})
.await
.unwrap();
samples.push(start.elapsed());
}
catalog.close().await.unwrap();
samples.sort();
let ms = |d: Duration| d.as_secs_f64() * 1_000.0;
println!(
"{flush_ms:>10} {:>11.1} {:>9.1} {:>9.1}",
ms(samples[samples.len() / 2]),
ms(samples[0]),
ms(samples[samples.len() - 1])
);
}
println!();
}