use dbsp::Circuit;
use dbsp::circuit::{CircuitConfig, CircuitStorageConfig};
use dbsp::operator::Generator;
use dbsp::typed_batch::OrdZSet;
use dbsp::utils::Tup2;
use dbsp::{DBSPHandle, Runtime};
use feldera_storage::metrics::{COMMIT_ALL_LATENCY_MICROSECONDS, SYNC_LATENCY_MICROSECONDS};
use feldera_types::checkpoint::CheckpointMetadata;
use feldera_types::config::{
FileBackendConfig, StorageBackendConfig, StorageCacheConfig, StorageConfig, StorageOptions,
StorageSyncMode,
};
fn counts() -> (u64, u64) {
let total = |histogram: &feldera_storage::histogram::ExponentialHistogram| {
histogram
.snapshot()
.iter_buckets()
.map(|bucket| bucket.count)
.sum()
};
(
total(&SYNC_LATENCY_MICROSECONDS),
total(&COMMIT_ALL_LATENCY_MICROSECONDS),
)
}
fn circuit_with_storage(path: &std::path::Path) -> DBSPHandle {
let config = CircuitConfig::with_workers(2).with_storage(Some(
CircuitStorageConfig::for_config(
StorageConfig {
path: path.to_string_lossy().into_owned(),
cache: StorageCacheConfig::default(),
},
StorageOptions {
min_storage_bytes: Some(0),
backend: StorageBackendConfig::File(Box::new(FileBackendConfig {
sync_mode: Some(StorageSyncMode::PerFile),
..FileBackendConfig::default()
})),
..StorageOptions::default()
},
)
.unwrap(),
));
let (handle, ()) = Runtime::init_circuit(config, |circuit| {
let source = circuit.add_source(Generator::new(|| {
let keys: Vec<Tup2<u64, i64>> = (0..256)
.map(|k| Tup2(k * 7 + Runtime::worker_index() as u64, 1i64))
.collect();
OrdZSet::from_keys((), keys)
}));
source.integrate_trace().apply(|_| ());
Ok(())
})
.unwrap();
handle
}
#[derive(serde::Deserialize)]
struct CheckpointDependencies {
batches: Vec<String>,
state_files: Vec<String>,
}
fn checkpoint_dependencies(
storage: &std::path::Path,
metadata: &CheckpointMetadata,
) -> CheckpointDependencies {
let path = storage
.join(metadata.uuid.to_string())
.join("dependencies.json");
serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap()
}
#[test]
fn prepare_does_not_sync() {
let tempdir = tempfile::tempdir().unwrap();
let mut handle = circuit_with_storage(tempdir.path());
handle.transaction().unwrap();
handle.checkpoint().run().unwrap();
handle.transaction().unwrap();
let before = counts();
let committer = handle.checkpoint().prepare().unwrap();
assert_eq!(
counts(),
before,
"the blocking phase of a checkpoint must not sync"
);
let publisher = committer.commit().unwrap();
let (synced, commit_alls) = counts();
let dependencies = checkpoint_dependencies(tempdir.path(), publisher.metadata());
let expected = dependencies.state_files.len() + dependencies.batches.len() + 1;
assert_eq!(
synced - before.0,
expected as u64,
"commit must sync exactly the checkpoint's {} state files, {} batches \
and dependencies.json",
dependencies.state_files.len(),
dependencies.batches.len()
);
assert!(
commit_alls > before.1,
"commit must be timed as one batch, saw {commit_alls} vs {}",
before.1
);
publisher.publish().unwrap();
handle.kill().unwrap();
}