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 av{version}: prefix. Bumping the version moves reads to a fresh
keyspace and lets the previous generation expire through its own TTLs.
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 requestchartOfAccountsListOrderedAsc— parameterised by organisation, reconcile-only flag, and classification filterpostingMatrixRules— 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 core —accounting_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.