Skip to content

Cache

The internal/cache package owns the two hot-path data stores that every Envoy request touches:

StorePurposeLookup cost
BlocklistCacheFully-replicated map of blocked entities with TTLsO(1)
AccountingCachePer-entity request counters (owner-node only)O(1)

Both caches are backed by Otter, a high-concurrency, GC-friendly cache library that avoids the lock contention of sync.Map and the GC overhead of pointer-heavy data structures.

BlocklistCache

The blocklist is the primary enforcement mechanism. Every DRL node maintains a complete replica of the cluster-wide blocklist so that any node can reject a blocked entity instantly, without network round-trips.

flowchart LR
    E[Envoy gRPC Request] --> C{BlocklistCache\nContains?}
    C -->|Yes| D[Return OVER_LIMIT]
    C -->|No| O[Return OK]
    O --> A[Async accounting\nin background]

API

// NewBlocklistCache creates a new blocklist cache with a maximum item count.
func NewBlocklistCache(maxItems int64) (*BlocklistCache, error)

// Add inserts or refreshes an entity key with the given TTL.
func (c *BlocklistCache) Add(key string, ttl time.Duration) error

// Contains reports whether the key is currently blocked.
func (c *BlocklistCache) Contains(key string) bool

// Delete removes an entity key (for manual unblock via the Internal API).
func (c *BlocklistCache) Delete(key string)

// GetSnapshot returns a point-in-time copy of all active entries with their
// remaining TTLs. Used by StateDelegate for Push/Pull state sync.
func (c *BlocklistCache) GetSnapshot() map[string]time.Duration

// MergeSnapshot hydrates the cache from a snapshot received during warm-bootstrap.
func (c *BlocklistCache) MergeSnapshot(snap map[string]time.Duration) error

Size configuration

cache {
    blocklist-size-mb 64   // Maximum RAM for blocklist entries (default: 64 MB)
}

Environment variable: DRL_CACHE_BLOCKLIST_SIZE_MB

Otter enforces the size limit through its internal admission policy — entries are evicted when the limit is approached. Because the blocklist is replicated, an entry evicted from one node is still held by peers until the TTL expires naturally.

TTL behaviour

Entries are inserted with explicit TTLs. The TTL source depends on the operation:

OperationTTL source
Rate-limit breach (automatic)accounting.rules.<name> blocklist-ttl (TBD per-rule)
Manual block via Internal APIcache.blocklist-default-ttl-seconds (default: 300 s) unless overridden by ?ttl= query param
Warm-bootstrap mergeOriginal TTL from the snapshot
cache {
    blocklist-default-ttl-seconds 300   // Default TTL for admin-API blocks
}

AccountingCache

The accounting cache stores per-entity request counters on the owner node only. Non-owner nodes forward increments to the owner via the Flusher (see Accounting).

// NewAccountingCache creates a new accounting cache sized by the config.
func NewAccountingCache(cfg config.AccountingConfig) (*AccountingCache, error)

// Increment atomically adds delta to the counter and returns the new value.
func (c *AccountingCache) Increment(key string, delta int64) (int64, error)

// Get returns the current counter value.
func (c *AccountingCache) Get(key string) (int64, bool)

// Set overwrites the counter (used during handover merge).
func (c *AccountingCache) Set(key string, value int64) error

// Reset removes a counter (e.g. after blocking the entity).
func (c *AccountingCache) Reset(key string)

// Close releases resources.
func (c *AccountingCache) Close()

Size configuration

cache {
    accounting-size-mb 128   // Maximum RAM for accounting counters (default: 128 MB)
}

Environment variable: DRL_CACHE_ACCOUNTING_SIZE_MB

State sync timeout

cache {
    sync-timeout-seconds 30   // Max wait for initial Memberlist state sync (default: 30 s)
}

Environment variable: DRL_CACHE_SYNC_TIMEOUT_SECONDS

If the sync does not complete within this timeout, the node proceeds to serve traffic anyway. This prevents stuck deployments when the cluster is empty (first node) or when a peer is unreachable.