use super::*;
use crate::canonical::CanonicalSegmentManifest;
struct Fixture {
directory: PathBuf,
store: GraphStore,
catalog: Catalog,
expected: NodeRecord,
}
impl Fixture {
fn new() -> Self {
let directory = std::env::temp_dir().join(format!(
"hawdb-checkpoint-captured-metadata-{}",
hawdb_core::generate_uuidv7().unwrap()
));
let mut catalog = Catalog::default();
let mut store = GraphStore::open_with_durability_and_replay_config(
&directory,
&mut catalog,
DurabilityPolicy::default(),
WalReplayConfig {
residency_mode: StorageResidencyMode::OutOfCore,
max_out_of_core_delta_bytes: Some(32 * 1024 * 1024),
..WalReplayConfig::default()
},
)
.unwrap();
let properties = (0..1057)
.map(|i| {
(
format!("k{i:04}-{}", "x".repeat(1024)),
Value::Int(i as i64),
)
})
.collect();
let id = store
.create_node(&mut catalog, "Source", properties)
.unwrap();
store.checkpoint(&catalog).unwrap();
assert!(store.is_out_of_core());
let expected = store.node_owned(id).unwrap().unwrap();
assert_eq!(expected.properties.len(), 1057);
assert_eq!(store.commit_epoch(), 1);
Self {
directory,
store,
catalog,
expected,
}
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.directory);
}
}
fn manifests(store: &GraphStore) -> [&CanonicalSegmentManifest; 2] {
[
store.canonical_base.as_ref().unwrap().manifest(),
store
.durable
.as_ref()
.unwrap()
.canonical_segments
.as_ref()
.unwrap()
.manifest(),
]
}
fn physical_dictionary_bytes<'a>(
dictionaries: impl IntoIterator<Item = &'a CanonicalSegmentManifest>,
) -> usize {
let mut allocations = BTreeMap::new();
for manifest in dictionaries {
let keys = &manifest.property_keys;
assert_eq!(keys.len(), 1057);
let entries = keys.capacity() * std::mem::size_of::<String>();
if let Some(previous) = allocations.insert(keys.as_ptr() as usize, entries) {
assert_eq!(previous, entries);
}
for key in keys {
assert!(!key.is_empty());
if let Some(previous) = allocations.insert(key.as_ptr() as usize, key.capacity()) {
assert_eq!(previous, key.capacity());
}
}
}
allocations.values().sum()
}
#[test]
fn checkpoint_units_capture_metadata_does_not_multiply_the_existing_dictionary_working_set() {
let fixture = Fixture::new();
let before = physical_dictionary_bytes(manifests(&fixture.store));
assert!(before > 1024 * 1024);
let fds = fixture
.store
.durable
.as_ref()
.unwrap()
.file_descriptor_metrics();
let sources = (0..8)
.map(|_| fixture.store.checkpoint_source())
.collect::<Vec<_>>();
let actual = physical_dictionary_bytes(
manifests(&fixture.store)
.into_iter()
.chain(sources.iter().flat_map(manifests)),
);
assert_eq!(
actual,
before,
"eight real captures added {} unadmitted dictionary bytes",
actual - before
);
for source in &sources {
assert_eq!(
source.checkpoint_source_identity(),
fixture.store.checkpoint_source_identity()
);
assert_eq!(
source.node_owned(fixture.expected.id).unwrap(),
Some(fixture.expected.clone())
);
}
let after = fixture
.store
.durable
.as_ref()
.unwrap()
.file_descriptor_metrics();
assert_eq!(after.open, fds.open);
assert_eq!(after.reserved, fds.reserved);
assert_eq!(fixture.catalog.labels().count(), 1);
}
#[test]
fn checkpoint_units_capture_metadata_public_reader_clones_retain_one_dictionary_allocation() {
let fixture = Fixture::new();
let reader = fixture.store.canonical_base.as_ref().unwrap();
let before = physical_dictionary_bytes([reader.manifest()]);
assert!(before > 1024 * 1024);
let copies = (0..8).map(|_| reader.clone()).collect::<Vec<_>>();
let actual = physical_dictionary_bytes(
[reader.manifest()]
.into_iter()
.chain(copies.iter().map(|reader| reader.manifest())),
);
assert_eq!(
actual,
before,
"public reader clones added {} dictionary bytes",
actual - before
);
for reader in &copies {
assert_eq!(
reader.manifest(),
fixture.store.canonical_base.as_ref().unwrap().manifest()
);
}
}
#[path = "checkpoint_capture_metadata_related_tests.rs"]
mod related;