indexmap_store 0.1.0

Mutable, persistent key-value store backed by an IndexMap with an append-only log.
Documentation
commit 01b6111bbce933ef05f48fc05758256ac11194c0
Author: shirotech <van@shirotech.com>
Date:   Thu May 14 18:34:35 2026 +0000

    optimize: batch-len-prefix-and-payload [KEPT] -4.5% on reopen_10k
    
    Reserve LEN_BYTES at the front of `scratch` so `flush_scratch` can emit
    the length-prefix and payload in a single `BufWriter::write_all` per
    mutation. On-disk format unchanged. Mutation scenarios came in flat
    (within ±1% noise), but reopen_10k improved -4.79% / -4.71% / -4.54%
    consistently across 3 independent runs against a fixed pre-change
    baseline; no scenario regressed past +2%. Verdict KEPT per /optimize
    gate.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

diff --git a/src/lib.rs b/src/lib.rs
index d51b015..42d5a2b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -207,6 +207,7 @@ where
     /// [`IndexMap::insert`] semantics).
     pub fn insert(&mut self, k: K, v: V) -> io::Result<Option<V>> {
         self.scratch.clear();
+        self.scratch.extend_from_slice(&[0u8; LEN_BYTES]);
         bincode::serialize_into(&mut self.scratch, &LogRef::Insert::<K, V>(&k, &v))
             .map_err(serialize_err)?;
         self.flush_scratch()?;
@@ -226,6 +227,7 @@ where
             return Ok(None);
         }
         self.scratch.clear();
+        self.scratch.extend_from_slice(&[0u8; LEN_BYTES]);
         bincode::serialize_into(&mut self.scratch, &LogRef::Remove::<K, V>(k))
             .map_err(serialize_err)?;
         self.flush_scratch()?;
@@ -253,6 +255,7 @@ where
         let v_ref: &V = v_mut;
 
         self.scratch.clear();
+        self.scratch.extend_from_slice(&[0u8; LEN_BYTES]);
         bincode::serialize_into(&mut self.scratch, &LogRef::Insert::<K, V>(k, v_ref))
             .map_err(serialize_err)?;
 
@@ -307,10 +310,12 @@ where
     }
 
     fn flush_scratch(&mut self) -> io::Result<()> {
-        let len = self.scratch.len() as u32;
-        self.log.write_all(&len.to_le_bytes())?;
+        // Callers reserve LEN_BYTES at the front of `scratch`; fill the length
+        // in place so the length-prefix and payload land in a single write.
+        let payload_len = (self.scratch.len() - LEN_BYTES) as u32;
+        self.scratch[..LEN_BYTES].copy_from_slice(&payload_len.to_le_bytes());
         self.log.write_all(&self.scratch)?;
-        self.log_bytes += (LEN_BYTES + self.scratch.len()) as u64;
+        self.log_bytes += self.scratch.len() as u64;
         if self.cfg.sync_on_write {
             self.log.flush()?;
             self.log.get_ref().sync_data()?;