Skip to main content

Redis Caching

packages/core/src/redis/ — 8 source files, 7 test files, consumed through the @cona/core/redis subpath (46 imports). Two keyspaces coexist and they behave differently. Getting them confused is the main hazard here.

Two keyspaces

atomic.ts:22 and :338 both state it explicitly: rate-limit and JTI keys are stored without the REDIS_ENV prefix.

Generation-based invalidation

Every cache key carries a v{version}: prefix. Bumping the version moves reads to a fresh keyspace and lets the previous generation expire through its own TTLs.
FLUSHDB is banned (CONA-1090). The generation prefix exists precisely so nobody needs it — a flush in a shared Redis takes out every other service’s keys too.
Two ways to roll a generation (redis/index.ts:60-101): Bump DEFAULT_CACHE_VERSION when a cached payload’s shape changes. The env override is for rolling a generation without a deploy — but because it takes precedence, a service that pins it will not follow a code-level bump.

Stamps

A stamp names a group of caches that are always invalidated together, so one write invalidates all of them in a single round trip. Currently one scope (constants.ts:13-20): The comment explains the grouping: every write path invalidates both together via deletAllGlDimensionsCache, so splitting them would add a round trip for no extra precision. Stamp keys are raw — they never carry the generation prefix, because a stamp that moved with the generation could not invalidate the previous one.

Files

Atomic primitives

atomic.ts backs rate limiting and locking: The portal’s three rate limiters are built on these — PLZ brute-force (per access token), download (per IP), and Shopify entry (per IP). See Portal.

What is cached

36 dynamic key builders. The hot paths:
  • organizationDetails — read on nearly every request
  • chartOfAccountsListOrderedAsc — parameterised by organisation, reconcile-only flag, and classification filter
  • postingMatrixRules — read once per dimension per document during accounting impact creation, which makes it the highest-volume cached read. See Accounting Engine.

Environment

Local development runs redis:7.4 from docker-compose.yml on host port 6380 — deliberately not 6379, to avoid colliding with a system Redis.

Notes

Redis has an in-degree of 8 inside coreaccounting_periods, general_ledger, reconciliation, reconciliation-agent, services, statuses, tax_codes, and vat all cache. Cache generation is observable. observe.ts and reportCacheGeneration emit the active generation so a mismatched service is visible rather than silently serving stale reads. Workers cache too. apps/temporal-workers/src/redis-logger-adapter.ts imports @cona/core, and the accounting queue depends on cached posting matrix rules.