Stage 7 · Master
Caching and Rate Limiting
What to Cache and the Invalidation Strategy
Meridian's Redis chapter is a forward-looking cache design: cache only tenant-scoped read models that are expensive to recompute and cheap to invalidate.
Status of Caching in the Repo
No Redis integration exists in meridian today. .env.example reserves REDIS_PASSWORD and Chapter 1 places Redis in the target architecture, but none of the services currently open a Redis connection. This lesson therefore specifies the cache contract that should be implemented once identity-service, community-service, and billing-service expose real read-heavy endpoints.
The examples below describe the intended Redis key layout and invalidation rules for Meridian. They are grounded in the real service boundaries and tenant model, but they do not claim that any cache adapter already exists in apps/gateway or the three domain services.
Cache Eligibility
| Candidate | Why it is cacheable | Invalidation trigger |
|---|---|---|
| gateway slug -> tenant UUID lookup | High reuse on every request; small payload | Tenant slug or status change in identity-service |
| Membership and permission snapshot | Read on most protected requests; changes infrequent | Membership role change, user removal, tenant suspension |
| Community resident/unit summaries | Read-heavy dashboard and directory queries | Resident, unit, or building write in community-service |
| Billing invoice summary lists | Expensive aggregation, bounded staleness acceptable | Invoice creation, payment posting, write-off, or cancellation |
| Pending payment webhook state | Authoritative mutable workflow record | Do not cache; keep in the source of truth |
The decision rule is simple: cache a value only if it is derived, read-heavy, tenant-scoped, and recoverable from the owning service's datastore. Cache misses are acceptable; stale authoritative writes are not. That rule immediately excludes workflow state such as in-flight payment reconciliation, but it includes lookup tables, membership snapshots, and aggregate invoice summaries.
Tenant-Aware Key Design
gateway:tenant-slug:{slug}
identity:tenant:{tenant_id}:user:{user_id}:permissions
community:tenant:{tenant_id}:building:{building_id}:unit-summary
community:tenant:{tenant_id}:resident-search:{query_hash}
billing:tenant:{tenant_id}:resident:{resident_id}:invoice-summary
billing:tenant:{tenant_id}:dashboard:aging-bucketsEvery cache key carries tenant_id unless the lookup happens before tenant resolution. The only acceptable exception is the gateway's public slug lookup, whose entire purpose is to discover the tenant UUID.
Prefixing keys by service boundary matters almost as much as including tenant_id. The same resident ID or invoice ID can appear in different application contexts over time, but the key namespace should still communicate ownership clearly: gateway owns slug resolution, identity-service owns permissions, community-service owns resident and unit lookups, billing-service owns invoice summaries. That keeps cache invalidation local to the service that understands the data.
Invalidation Rules
- Use cache-aside for read-heavy queries: look up Redis, query the datastore on miss, then populate the value with a bounded TTL.
- Delete keys on writes rather than attempting broad in-place mutation of aggregate payloads.
- Keep TTLs short for permission snapshots and invoice summaries so operational mistakes self-heal quickly.
- Never share a cache key across tenants, even if the payload shape is identical.
apps/gateway/internal/cache/tenant_lookup.gocreateResponsibility: Caches slug-to-tenant UUID resolution at the public edge before downstream routing begins.
Why now: Every request starts here, so even a small cache hit rate removes repeated tenant-resolution work from the hot path.
Connects to: Uses the X-Tenant-Id propagation contract from libs/platform/tenancy once the gateway resolves the tenant.
apps/identity-service/internal/cache/permissions.gocreateResponsibility: Caches tenant-scoped membership and permission snapshots for authenticated requests.
Why now: Identity lookups are a common read path and change far less often than they are consulted.
Connects to: Supports the roles and memberships described in docs/architecture/services.md.
apps/billing-service/internal/cache/invoice_summary.gocreateResponsibility: Caches invoice dashboard and resident-summary reads that are expensive to aggregate repeatedly.
Why now: Billing data produces natural read models that are useful to cache, provided invalidation stays tied to payment and invoice writes.
Connects to: Aligns with billing-service ownership of invoices and payments.
The Decision
- Redis is planned, but not yet integrated; this chapter documents the intended contract rather than existing code.
- Cache only tenant-scoped, read-heavy, reproducible data; keep authoritative workflow state in the primary datastore.
- Every cache key carries both a service prefix and tenant_id, except the pre-resolution slug lookup at the gateway edge.
- Invalidate by deleting tenant-scoped keys on successful writes; let later reads repopulate them.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.