Skip to content

test: add snapshot compact interaction test to validate database size reduction#391

Open
vincent-herlemont wants to merge 1 commit into
mainfrom
bug_snapshot_compact
Open

test: add snapshot compact interaction test to validate database size reduction#391
vincent-herlemont wants to merge 1 commit into
mainfrom
bug_snapshot_compact

Conversation

@vincent-herlemont

@vincent-herlemont vincent-herlemont commented May 31, 2025

Copy link
Copy Markdown
Owner

Result related to

fn test_snapshot_compact_interaction() {
let tf = TmpFs::new().unwrap();
let mut models = Models::new();
models.define::<Item>().unwrap();
let db_path = tf.path("db");
let snapshot_path = tf.path("snapshot.db");
// Create DB and insert many items
let mut db = Builder::new().create(&models, db_path.clone()).unwrap();
let rw = db.rw_transaction().unwrap();
for i in 0..1000 {
rw.insert(Item {
id: i,
name: format!("item_{}", i),
})
.unwrap();
}
rw.commit().unwrap();
// File size before compact
let orig_size = fs::metadata(&db_path).unwrap().len();
println!("Original DB size before compact: {}", orig_size);
// Take snapshot without compacting
let db_snapshot = db.snapshot(&models, snapshot_path.as_std_path()).unwrap();
let snap_size = fs::metadata(&snapshot_path).unwrap().len();
println!("Snapshot size without compact: {}", snap_size);
// Compact the original DB, then snapshot again
db.compact().unwrap();
let compacted_size = fs::metadata(&db_path).unwrap().len();
println!("Original DB size after compact: {}", compacted_size);
let snapshot_path2 = tf.path("snapshot2.db");
let db_snapshot2 = db.snapshot(&models, snapshot_path2.as_std_path()).unwrap();
let snap2_size = fs::metadata(&snapshot_path2).unwrap().len();
println!("Snapshot size after compact: {}", snap2_size);
// Drop DB handles before reopening files
drop(db_snapshot);
drop(db_snapshot2);
// Compact the first snapshot file
let mut db_snap = Builder::new().open(&models, &snapshot_path).unwrap();
db_snap.compact().unwrap();
let snap_compacted_size = fs::metadata(&snapshot_path).unwrap().len();
println!("Snapshot file size after compact: {}", snap_compacted_size);
// Open the compacted snapshot and compact again
drop(db_snap);
let mut db_snap2 = Builder::new().open(&models, &snapshot_path).unwrap();
db_snap2.compact().unwrap();
let snap_compacted_size2 = fs::metadata(&snapshot_path).unwrap().len();
println!(
"Snapshot file size after second compact: {}",
snap_compacted_size2
);
// TODO: The snapshot process does not compact the database file, so the snapshot can be much larger than a compacted database. Compacting the snapshot after creation reduces its size, but not always to the minimum possible. Ideally, snapshot should optionally compact or copy only live data.
// This test demonstrates the issue reported by the user: https://github.com/vincent-herlemont/native_db/issues/XXX
//
// User's report:
// - Compacting the active database reduces its size.
// - Snapshotting a non-compacted DB produces a large file.
// - Compacting the snapshot helps, but not as much as compacting the original and then snapshotting.
// - Only after replacing the active DB with the compacted snapshot and compacting again does the file reach minimum size.
// Assertions:
assert!(
orig_size > compacted_size,
"Compacting should reduce the original DB size"
);
assert_eq!(
orig_size, snap_size,
"Snapshot without compact should match original size"
);
assert!(
snap2_size >= compacted_size,
"Snapshot after compact should be at least as large as compacted DB (but may be larger)"
);
assert!(
snap_compacted_size < snap_size,
"Compacting the snapshot should reduce its size"
);
assert!(
snap_compacted_size2 <= snap_compacted_size,
"Second compaction should not increase size"
);
// The compacted snapshot may still be larger than the compacted original DB
// (implementation detail, but should be close)
// Print all sizes for manual inspection
println!("Sizes summary:");
println!(" Original DB before compact: {}", orig_size);
println!(" Snapshot (no compact): {}", snap_size);
println!(" Original DB after compact: {}", compacted_size);
println!(" Snapshot after compact: {}", snap2_size);
println!(" Snapshot file after compact:{}", snap_compacted_size);
println!(" Snapshot file after 2x cmp: {}", snap_compacted_size2);
// Display for manual inspection
tf.display_dir_entries();
}

$> cargo test --test snapshot -- --nocapture
   Compiling native_db v0.8.1 (/Users/vincentherlemont/Development/projects/native_db)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.46s
     Running tests/snapshot.rs (target/debug/deps/snapshot-284ab476e0b0aa91)

running 2 tests
Original DB size before compact: 1589248
/var/folders/pk/lflgn7g13230ngd5x5272d4c0000gn/T/.tmpK0LFGD
/var/folders/pk/lflgn7g13230ngd5x5272d4c0000gn/T/.tmpK0LFGD/snapshot.db
Snapshot size without compact: 1589248
Original DB size after compact: 876544
test test_snapshot ... ok
Snapshot size after compact: 1589248
Snapshot file size after compact: 1007616
Snapshot file size after second compact: 991232
Sizes summary:
  Original DB before compact: 1589248
  Snapshot (no compact):      1589248
  Original DB after compact:  876544
  Snapshot after compact:     1589248
  Snapshot file after compact:1007616
  Snapshot file after 2x cmp: 991232

@vincent-herlemont vincent-herlemont self-assigned this May 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant