etcd-io/etcd
cache
A buffered watch / read fan-out cache. Source: cache/.
Purpose
The cache module is a relatively new addition (introduced in 2025 in the run-up to v3.6) that buffers a single etcd watch per key prefix and fans out events to many local watchers. Workloads with thousands of clients that all watch overlapping prefixes — Kubernetes API servers being the canonical example — can use the cache to dramatically reduce server-side watch fan-out cost while keeping client semantics intact.
Directory layout
cache/
├── cache.go # Cache struct + New constructor
├── config.go # Config with prefix, history size, request progress
├── store.go, store_test.go # last-observed snapshot
├── ringbuffer.go, ringbuffer_test.go # bounded event history
├── demux.go # fans events from the upstream watch out to local watchers
├── progress_requestor.go # periodic RequestProgress calls to keep the watch fresh
├── ready.go # tri-state ready/not-ready/closed
├── snapshot.go # initial materialization from KV.Get
├── watcher.go # exposed Watcher interface
├── predicate.go # key-range / option matching
├── clock.go, fake_clock_test.go
└── go.modKey abstractions
| Symbol | File | Description |
|---|---|---|
Cache |
cache/cache.go |
Top-level shard. Owns the upstream watch, the snapshot store, the ring buffer, the demuxer. |
Config |
cache/config.go |
Prefix, HistorySize, RequestProgressInterval, etc. |
store |
cache/store.go |
Last-observed snapshot of the prefix. |
ringBuffer |
cache/ringbuffer.go |
Bounded event ring used to satisfy resumed watches. |
demux |
cache/demux.go |
Routes events to local subscribers and arbitrates resync after gaps. |
progressRequestor |
cache/progress_requestor.go |
Issues periodic WatchRequest_ProgressRequest to keep the upstream watch advancing. |
Errors:
ErrUnsupportedRequest— the option combination isn't yet handled (e.g.WithPrevKVforWatch,WithCountOnlyforGet).ErrKeyRangeInvalid— request scope falls outside the cache's prefix.ErrCacheTimeout— the cache could not catch up to the requested revision in time.
How it works
graph TD
user[Local watchers/getters] -->|Watch / Get| cache[cache.Cache]
cache -->|single upstream Watch| etcd[etcd cluster]
etcd -->|events| demux
cache --> demux
cache --> store[store]
cache --> ring[ringBuffer]
demux -->|fan-out| user- On construction, the cache opens a
clientv3.Watcher.Watchagainst its prefix and primes thestorefrom aKV.Get. - Every event is appended to the ring buffer and pushed through the demux to active local watchers.
- New local watchers are bootstrapped from the snapshot (if asking from
0) or from the ring buffer (if asking from a recent revision). - The progress requestor periodically asks the upstream for
RequestProgress, so the watch's "last delivered revision" stays current even on quiet keys.
Note: The cache relies on
RequestProgresssemantics, which the gRPC proxy does not forward. Don't run cache behind a grpc-proxy.
Integration points
- Imports
client/v3to talk to etcd; this is the only public client dependency. - Tests live in
tests/integration/cache_test.go(one of the largest single test files in the repo at 1,715 lines).
Entry points for modification
- New supported
clientv3.OpOption→cache/predicate.goandcache/cache.go(Get,Watchpaths). - New event source / different upstream → swap the
clientv3.Watcherincache.goand updateprogress_requestor.go. - Larger histories or different eviction →
cache/ringbuffer.go.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.