Trovella Wiki

Key Conventions

Cache key naming patterns, tenant scoping rules, and collision avoidance for the @repo/cache layer.

Key Format

All cache keys follow a colon-delimited hierarchical pattern:

{scope}:{identifier}:{resource}:{qualifier}

Segments

SegmentPurposeExample
scopeIsolation boundary (usually tenant)tenant
identifierThe scoping ID (org ID, user ID, etc.)org_abc123
resourceWhat is being cachedsettings
qualifierOptional sub-resource or parametertheme, page:3

Examples

tenant:{orgId}:settings                    — Organization settings
tenant:{orgId}:members:count               — Member count for an org
tenant:{orgId}:research:{planId}:status    — Research plan status
user:{userId}:preferences                  — User-level preferences
ref:plan-statuses                          — Reference data (no tenant scope)

Tenant Scoping Rule

Cache keys for tenant-scoped data must include the tenant identifier as the second segment. This is the caching equivalent of the RLS rule: just as every database query uses withTenantContext(), every tenant-scoped cache key includes the org ID.

// Correct — tenant-scoped key
const key = `tenant:${orgId}:settings`;

// Wrong — missing tenant scope risks cross-tenant data leakage
const key = `settings:${settingsId}`;

This convention also enables efficient pattern-based invalidation when a tenant's data changes — you can clear all of a tenant's cache entries with tenant:{orgId}:*.

Global Keys

Some data is not tenant-scoped:

  • Reference data — enum values, plan statuses, feature flags. Use the ref: prefix.
  • System-wide counters — use the sys: prefix.
ref:plan-statuses          — Cached reference data
sys:health:last-check      — System-level operational data

Global keys should be rare. Most application data is tenant-scoped.

Key Length and Characters

  • Keep keys under 200 characters. Long keys waste memory and slow lookups.
  • Use only lowercase alphanumeric characters, colons, and hyphens. Avoid spaces, special characters, and user-controlled input in key segments.
  • If a segment value comes from user input (search queries, etc.), hash it:
import { createHash } from "node:crypto";

function hashSegment(value: string): string {
  return createHash("sha256").update(value).digest("hex").slice(0, 16);
}

const key = `tenant:${orgId}:search:${hashSegment(query)}`;

Key Registry

As the number of cached resources grows, maintain a registry of key patterns in the code that produces them. This helps with:

  • Discoverability — developers can find all cache keys for a resource.
  • Invalidation — knowing the key patterns makes it easy to write correct invalidation logic.
  • Debugging — you can scan Redis for keys matching a known pattern.

Currently, key construction is inline at the call site. If the number of distinct key patterns grows beyond a handful, consider extracting a cacheKeys utility:

// Future pattern — not yet implemented
export const cacheKeys = {
  tenantSettings: (orgId: string) => `tenant:${orgId}:settings`,
  memberCount: (orgId: string) => `tenant:${orgId}:members:count`,
  researchStatus: (orgId: string, planId: string) => `tenant:${orgId}:research:${planId}:status`,
} as const;

This is a recommended evolution, not a current requirement.

On this page