Trovella Wiki

Caching Overview

Architecture and design of the Trovella caching layer — Upstash Redis, the @repo/cache wrapper, and how caching fits into the request lifecycle.

Role in the System

Trovella uses Upstash Redis as its caching layer. Redis serves two primary purposes today:

  1. Cache-aside reads — expensive or repeated query results are cached with a TTL to reduce database load.
  2. Health signaling — the /api/health endpoint pings Redis as one of three infrastructure health checks (alongside Cloud SQL and Typesense).

Future uses (planned but not yet implemented) include rate limiting at the API layer and session token caching.

The @repo/cache Package

All Redis access flows through the @repo/cache wrapper package. No other package in the monorepo may import ioredis directly — this is enforced by ESLint no-restricted-imports rules defined in packages/config-eslint/restrictions.js.

The package sits in the infra layer of the dependency hierarchy:

app (apps/web)
  -> service (@repo/api, @repo/mcp)
    -> infra (@repo/cache, @repo/ai, @repo/auth, @repo/logger, @repo/search)
      -> core (@repo/db)
        -> leaf (@repo/utils, @repo/validators, @repo/design-tokens)

Exports

ExportPurpose
cacheGet<T>Cache-aside wrapper — returns cached value or calls fetcher
cacheInvalidateDelete a single cache key
cacheInvalidatePatternDelete all keys matching a glob pattern via SCAN
getRedisAccess the singleton IORedis client
disconnectRedisGraceful shutdown — closes connection, nulls singleton
checkRedisHealth check — returns { ok, latencyMs }

Dependencies

The package depends on a single runtime library:

  • ioredis ^5.10.1 (via pnpm catalog)

Connection Model

The Redis client is a lazy singleton. The first call to getRedis() creates an IORedis instance from the REDIS_URL environment variable. Subsequent calls return the same instance. The client is configured with:

  • maxRetriesPerRequest: 3 — automatically retries failed commands up to three times
  • lazyConnect: true — the TCP connection is not established until the first command

Calling disconnectRedis() sends a QUIT command and nulls the singleton. The next getRedis() call creates a fresh connection, which means the application can recover from transient Redis outages without a restart.

Environment Configuration

VariableLocal (Docker Compose)Production
REDIS_URLredis://localhost:6379Upstash Redis connection URL

Local development uses a redis:8-alpine container defined in docker-compose.yml with a persistent redis-data volume. Production uses Upstash Redis, with the connection URL stored as upstash-redis-url in GCP Secret Manager. See Infrastructure — Cloud Resources for provisioning details.

On this page