Skip to main content
Why CacheeHow It Works
All Verticals5G TelecomAd TechAI InfrastructureFraud DetectionGamingTrading
PricingDocsBlogSchedule DemoLog InStart Free Trial
← Back to Blog
Engineering

Zero-Copy L0: The Cache Tier Faster Than L1

L1 in-process caching is fast. A DashMap lookup with W-TinyLFU admission completes in about 1.5 microseconds. For most workloads, that is more than enough. But there is a class of workload where 1.5 microseconds is not fast enough — and where the real problem is not speed but architecture. Python ML serving. Gunicorn workers. Multiprocessing pools. Every one of them runs into the same wall: the GIL forces multi-process architecture, and multi-process architecture forces cache duplication. L0 eliminates both problems with a single primitive: shared memory mapped across processes, readable at sub-nanosecond speed, with zero copies.

The GIL Problem Nobody Talks About

Every serious Python deployment runs multi-process. Not by choice — by necessity. Python's Global Interpreter Lock prevents true multi-threaded parallelism for CPU-bound work. If you need to serve ML inferences across 8 CPU cores, you run 8 processes. Gunicorn pre-forks them. Uvicorn does the same. Celery spawns worker processes. The multiprocessing module forks explicitly.

This works for compute. It does not work for caching.

Each process has its own virtual address space. Each process maintains its own copy of every cached value. If your feature store holds 2GB of embedding vectors, and you run 8 gunicorn workers, you are using 16GB of RAM for 2GB of data. The duplication scales linearly with worker count. At 32 workers — common on production inference servers — you are at 64GB for 2GB of features.

This is not a theoretical problem. It is the reason ML teams over-provision memory by 4–8x, the reason feature stores get moved to external services (adding network latency), and the reason some teams give up on caching features locally altogether and pay the Redis round-trip on every inference.

Current Solutions Are All Compromises

The standard approaches to multi-process caching in Python are all tradeoffs:

None of these give you what you actually need: a proper cache with eviction, concurrency, and hash-based lookups, shared across processes, readable at hardware speed.

L0: Shared Memory, Pointer Dereference Reads

L0 is Cachee's answer. At startup, Cachee allocates a memory-mapped region using shm_open + mmap with MAP_SHARED. The region contains a pre-allocated hash table with open addressing and linear probing. Keys and values are stored inline — no heap pointers, no indirection.

When gunicorn's master process forks workers, each worker inherits the memory mapping. No additional setup. The shared region is live the moment the worker starts.

The read path is three operations: hash the key, index into the shared memory region, return the value. No system call. No memory copy. No serialization. It is a pointer dereference into memory that is already in the process's virtual address space. On modern hardware, this completes in 0.3–0.8 nanoseconds — an L1 cache line hit.

The numbers: L0 shared memory read: 0.3–0.8ns. L1 DashMap read: ~1,500ns. Redis GET: ~1,500,000ns. L0 is 2,000x faster than L1 and 2,000,000x faster than Redis.

Writes use the same MVCC architecture as the L1 engine. A new version is written to the next slot, the pointer is atomically swapped, and old versions remain readable until all readers advance their epoch. Readers are never blocked by writers. This is the same concurrency model that PostgreSQL uses for its shared buffer pool.

The Complete Memory Hierarchy

L0 is not a replacement for L1. It is the tier below it. Together, the four tiers form a complete caching hierarchy:

Tier Mechanism Latency Scope
L0: Zero-Copy Shared Memory Pointer dereference (mmap) <1ns All processes, same machine
L1: In-Process RAM DashMap + W-TinyLFU ~1.5µs Single process
L1.5: NVMe SSD io_uring async read 10–50µs Single machine
L2: Redis / ElastiCache Network round-trip 1–5ms Cluster-wide

Data moves between tiers automatically based on access frequency. The hottest features live in L0, always available at pointer speed. The working set lives in L1 with admission control. Warm data spills to NVMe. The full keyspace is backed by L2. Promotion and demotion happen without application code.

NumPy Views: Zero-Copy All the Way Down

The Python bindings are designed for ML workloads specifically. get_numpy(key, shape, dtype) returns a NumPy array whose underlying buffer is the shared memory region. Not a copy of it. The actual bytes in shared memory become the backing store for the NumPy array.

from cachee import SharedCache
import numpy as np

cache = SharedCache(path="/dev/shm/cachee", size_gb=2)

# Store a 128-dim embedding
cache.set_numpy("embedding:user:123", embedding_vector)

# Read it back: zero copy, zero allocation
features = cache.get_numpy("embedding:user:123", shape=(128,), dtype=np.float32)

# Pass directly to PyTorch — still zero copy
import torch
tensor = torch.from_numpy(features)

The entire chain from shared memory through NumPy to PyTorch involves zero memory copies. The tensor that PyTorch receives shares the same physical memory as the cached value. This is critical for ML serving where you are accessing thousands of features per inference across dozens of workers.

When Every Nanosecond Is Revenue

L0 is not for every workload. If your cache reads are not in the hot path of a latency-critical loop, L1 at 1.5 microseconds is more than sufficient. L0 is for the workloads where the cache read is the hot path:

For these workloads, L0 removes feature access from the latency budget entirely. The features are not "fetched" or "looked up." They are already in your address space. Reading them is indistinguishable from reading a local variable.

L1 was the fastest cache tier. It set the standard for in-process caching: sub-microsecond reads, zero network hops, W-TinyLFU admission. L0 does not replace it. L0 sits below it — 2,000x faster, shared across processes, purpose-built for the workloads where even microseconds are too slow.

Related Reading

Also Read

Cache Features at Hardware Speed. Across Every Worker.

Zero-copy shared memory. Sub-nanosecond reads. Native Python bindings with NumPy views. Purpose-built for ML inference at scale.

Start Free Trial Schedule Demo