Codanna uses memory-mapped files for instant loading and high-performance access.
Different access patterns require different cache designs:
- Purpose: Fast symbol lookups by name
- Hash: FNV-1a for distribution
- Access: <10ms response time
- Size: ~100 bytes per symbol
- Storage: Compact symbol representation
- Purpose: Semantic similarity search
- Dimensions: Configurable (384/768/1024 based on model)
- Access: <1μs after OS page cache warm-up
- Storage: Binary-packed floating-point arrays
- Organization: IVFFlat clustering for fast lookup
- No deserialization on load
- OS maps file directly to memory
- Application sees it as regular memory
- First access triggers page loading
- OS manages paging automatically
- Inactive pages can be swapped out
- Multiple processes share same physical memory
- No manual cache management needed
- Data persists between runs
- No rebuild on restart
- Atomic writes prevent corruption
- File system handles durability
struct CompactSymbol {
id: NonZeroU32, // 4 bytes
kind: u8, // 1 byte
file_id: NonZeroU32, // 4 bytes
range: CompactRange, // 8 bytes (start/end)
name_hash: u64, // 8 bytes (FNV-1a)
flags: u8, // 1 byte
// Total: 26 bytes + padding = 32 bytes (cache-line aligned)
}Cache-line alignment: 32 bytes per symbol, 2 symbols fit per 64-byte cache line.
segment_0.vec:
├── Header (metadata)
│ ├── Model name
│ ├── Dimensions
│ ├── Vector count
│ └── Cluster count
├── Cluster metadata
│ ├── Cluster centroids
│ └── Cluster boundaries
└── Vector data
├── Vector 0: [f32; dimensions]
├── Vector 1: [f32; dimensions]
└── ...
Storage format: Binary-packed f32 arrays using bincode for serialization.
Vectors are organized using Inverted File with Flat vectors:
- K-means clustering groups similar vectors
- Centroids represent each cluster
- Search checks nearby clusters first
- Reduces comparisons from N to ~sqrt(N)
Example with 10,000 vectors:
- Without clustering: 10,000 comparisons
- With 100 clusters: ~1,000 comparisons (10x faster)
First access loads pages into OS cache:
Cold start: 100-200ms (loading from disk)
Warm cache: <1μs (already in RAM)
Hot paths warm up quickly - frequent queries benefit from OS caching.
- Build new symbol cache in memory
- Write to temporary file
- Atomic rename to
symbol_cache.bin - OS remaps memory on next access
- Generate new embeddings
- Re-cluster vectors with K-means
- Write new segment file
- Delete old embeddings
- Update metadata
Crash safety: Old files remain valid until new ones are complete.
.codanna/index/
├── symbol_cache.bin # FNV-1a hashed symbols
└── vectors/
├── segment_0.vec # Vector data
├── segment_1.vec # (if needed)
├── metadata.bin # Index metadata
└── clusters.bin # Cluster information
For a project with 100,000 symbols:
Symbol cache:
- 100,000 symbols × 32 bytes = 3.2 MB
Vector cache (384-dim model):
- 100,000 vectors × 384 floats × 4 bytes = 153.6 MB
Total: ~157 MB (plus OS overhead)
Memory-mapped files scale to:
- Millions of symbols
- Gigabytes of vector data
- Multiple concurrent readers
- OS handles paging automatically
- Symbol lookup: O(1) with FNV-1a hash
- Vector search: O(sqrt(N)) with IVFFlat
- No serialization overhead
- Cache-line aligned access
- Batch updates preferred
- Atomic file replacement
- No locking for readers
- Background re-clustering
Using rkyv for zero-copy:
- No parsing on load
- Direct memory access
- Type-safe operations
- Instant availability
- OS maps entire file but doesn't load it all
- Use
vmstatto see actual RAM usage - Inactive pages get swapped naturally
- OS loading pages from disk
- Subsequent searches are fast
- Pre-warm with
cat .codanna/index/vectors/segment_0.vec > /dev/null
- Delete corrupted cache files
- Re-run
codanna indexto rebuild - Atomic writes prevent partial updates
- How It Works - System overview
- Embedding Model - Vector generation
- Performance - Optimization tips