Expand description
A prunable cache for ordered data with index-based lookups.
Data is stored in crate::journal::variable::Journal (an append-only log) and the location of written data is tracked in-memory by index to enable single-read lookups for cached data.
Unlike crate::archive::Archive, the Cache is optimized for simplicity and does not support key-based lookups (only index-based access is provided). This makes it ideal for caching sequential data where you know the exact index of the item you want to retrieve.
§Memory Overhead
Cache maintains a single in-memory map to track the location of each index item. The memory
used to track each item is 8 + 4 + 4
bytes (where 8
is the index, 4
is the offset, and
4
is the length). This results in approximately 16
bytes of memory overhead per cached item.
§Pruning
Cache supports pruning up to a minimum index
using the prune
method. After prune
is
called on a section
, all interaction with a section
less than the pruned section
will
return an error. The pruning granularity is determined by items_per_blob
in the configuration.
§Single Operation Reads
To enable single operation reads (i.e. reading all of an item in a single call to commonware_runtime::Blob), Cache stores the length of each item in its in-memory index. This ensures that reading a cached item requires only one disk operation.
§Compression
Cache supports compressing data before storing it on disk. This can be enabled by setting
the compression
field in the Config
struct to a valid zstd
compression level. This setting
can be changed between initializations of Cache, however, it must remain populated if any
data was written with compression enabled.
§Querying for Gaps
Cache tracks gaps in the index space to enable the caller to efficiently fetch unknown keys
using next_gap
. This is a very common pattern when syncing blocks in a blockchain.
§Example
use commonware_runtime::{Spawner, Runner, deterministic, buffer::PoolRef};
use commonware_storage::cache::{Cache, Config};
use commonware_utils::{NZUsize, NZU64};
let executor = deterministic::Runner::default();
executor.start(|context| async move {
// Create a cache
let cfg = Config {
partition: "cache".into(),
compression: Some(3),
codec_config: (),
items_per_blob: NZU64!(1024),
write_buffer: NZUsize!(1024 * 1024),
replay_buffer: NZUsize!(4096),
buffer_pool: PoolRef::new(NZUsize!(1024), NZUsize!(10)),
};
let mut cache = Cache::init(context, cfg).await.unwrap();
// Put data at index
cache.put(1, 100u32).await.unwrap();
// Get data by index
let data: Option<u32> = cache.get(1).await.unwrap();
assert_eq!(data, Some(100));
// Check for gaps in the index space
cache.put(10, 200u32).await.unwrap();
let (current_end, start_next) = cache.next_gap(5);
assert!(current_end.is_none());
assert_eq!(start_next, Some(10));
// Close the cache (also closes the journal)
cache.close().await.unwrap();
});
Structs§
Enums§
- Error
- Errors that can occur when interacting with the cache.