Open-Source Wikis

/

etcd

/

Modules

/

cache

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.mod

Key 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. WithPrevKV for Watch, WithCountOnly for Get).
  • 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
  1. On construction, the cache opens a clientv3.Watcher.Watch against its prefix and primes the store from a KV.Get.
  2. Every event is appended to the ring buffer and pushed through the demux to active local watchers.
  3. New local watchers are bootstrapped from the snapshot (if asking from 0) or from the ring buffer (if asking from a recent revision).
  4. 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 RequestProgress semantics, which the gRPC proxy does not forward. Don't run cache behind a grpc-proxy.

Integration points

  • Imports client/v3 to 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.OpOptioncache/predicate.go and cache/cache.go (Get, Watch paths).
  • New event source / different upstream → swap the clientv3.Watcher in cache.go and update progress_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.

cache – etcd wiki | Factory