AcornMemoryWorker Storage
In-memory content-addressable storage with nanosecond-scale synchronous reads and optional LFU eviction.
.package(url: "https://github.com/treehauslabs/AcornMemoryWorker.git", from: "1.0.0")
MemoryCASWorker
actor AcornCASWorker
public actor MemoryCASWorker: AcornCASWorker {
public init(
capacity: Int? = nil,
maxBytes: Int? = nil,
halfLife: Duration = .seconds(300),
sampleSize: Int = 5,
timeout: Duration? = nil
)
}
Parameters
capacity
Int?
Maximum number of entries. When exceeded, LFU eviction kicks in. Nil for unbounded.
maxBytes
Int?
Maximum total bytes in memory. Nil for unbounded.
halfLife
Duration
Time for LFU scores to decay to 50%. Default 300 seconds.
sampleSize
Int
Number of random candidates to sample during eviction. Default 5.
timeout
Duration?
Timeout for chain traversal operations. Nil for no timeout.
Dual API
MemoryCASWorker provides two ways to access data: async methods (via the actor, ~7.5µs) and sync methods (via internal lock, 3–28ns). Both access the same underlying state and are safe to use concurrently.
Sync API
Marked nonisolated — bypasses the actor executor and accesses the internal OSAllocatedUnfairLock directly. Use these on hot paths.
| Method | Return | Latency |
|---|---|---|
syncHas(cid:) |
Bool |
3–11 ns |
syncGet(cid:) |
Data? |
6–28 ns |
syncStore(cid:data:) |
Void |
142 ns (733 ns with eviction) |
syncDelete(cid:) |
Void |
78 ns |
let worker = MemoryCASWorker(capacity: 10_000)
// No async/await needed
worker.syncStore(cid: cid, data: payload)
if let data = worker.syncGet(cid: cid) {
process(data)
}
if worker.syncHas(cid: cid) {
// exists
}
Async API
Standard AcornCASWorker protocol methods. These go through the actor executor (~7µs overhead) but compose naturally with the chain.
| Method | Behavior |
|---|---|
has(cid:) async |
Check existence. Same logic as syncHas. |
getLocal(cid:) async |
Retrieve data from local store. Updates LFU scores and metrics. |
storeLocal(cid:data:) async |
Store data locally. Handles eviction if at capacity. |
delete(cid:) |
Remove an entry. Updates metrics, byte tracking, and LFU cache. |
get(cid:) async |
Protocol default. Walks chain: near → local → backfill. |
store(cid:data:) async |
Protocol default. Stores locally, propagates to near. |
Properties
| Property | Type | Description |
|---|---|---|
metrics | CASMetrics | Hit/miss/store/eviction/deletion counts |
totalBytes | Int | Current total bytes stored in memory |
timeout | Duration? | Timeout for chain operations |
near | (any AcornCASWorker)? | Faster adjacent worker in chain |
far | (any AcornCASWorker)? | Slower adjacent worker in chain |
CASMetrics
public struct CASMetrics: Sendable, Equatable {
public var hits: Int
public var misses: Int
public var stores: Int
public var evictions: Int
public var deletions: Int
}
Eviction Behavior
When either capacity or maxBytes is exceeded during a store:
- The LFU cache samples
sampleSizerandom entries - The entry with the lowest time-decayed score is evicted
- Eviction repeats until limits are satisfied
metrics.evictionsis incremented for each eviction
Unbounded mode
Passing nil for both capacity and maxBytes creates an unbounded cache with no eviction overhead. Useful when you know your dataset fits in memory.
Performance
Benchmarked on Apple Silicon in release mode:
| Operation | Sync | Async (Actor) | Ratio |
|---|---|---|---|
| Get hit (17B) | 28 ns | 7.5 µs | 268x |
| Get miss | 6 ns | 7.4 µs | 1233x |
| Has (hit) | 11 ns | 7.3 µs | 664x |
| Has (miss) | 3 ns | — | — |
| Store | 142 ns | 7.6 µs | 54x |
| Store + evict | 733 ns | 7.6 µs | 10x |
| Delete | 78 ns | 7.2 µs | 92x |
The ~7µs actor overhead is fixed regardless of data size. For the sync API, data size is irrelevant for gets (dictionary stores references, not copies).