Architecture decision ADR-005

Defer Redis to Phase 2; Use Next.js Fetch Cache and PostgreSQL in Phase 1

Accepted · 2026-05-10

Historical decision text is preserved. Accepted describes the decision, not deployment of every capability discussed. Consult the Architecture and Infrastructure tabs for the verified running system.

Date: 2026-05-10
Status: Accepted
Deciders: Daniel Meza


Context and Problem Statement

A financial comparison platform has predictable caching needs: product lists and detail pages change infrequently (once per ingestion run, typically every 6–24 hours), but must serve quickly to many concurrent visitors. Redis is the conventional solution for application-level caching. However, adding Redis to the Phase 1 infrastructure introduces a Railway managed service with associated cost, an additional connection pool to manage, a cache invalidation protocol to implement, and a new operational component to monitor.

We need to decide: when is Redis necessary, and what do we use before that threshold?


Decision Drivers


Considered Options

  1. Redis from day one — Deploy Railway managed Redis at launch, implement full cache invalidation protocol, use Redis for product lists, comparison snapshots, and rate-limit counters.
  2. Next.js fetch cache + ISR only (no Redis, Phase 1) — Use Next.js built-in caching for all product data. Add Redis at a defined load threshold.
  3. PostgreSQL-only (no caching at any layer) — Read from the database on every request, no cache.
  4. In-process LRU (node-lru-cache) in the API service — Add an in-process cache to the Backend API Service, bypassing both Redis and Next.js cache.

Decision Outcome

Option 2: Next.js fetch cache + ISR as the Phase 1 caching strategy is adopted.

No Railway Redis service is deployed until the threshold defined below is reached.

Phase 1 caching layers (no Redis):

Layer Mechanism What it caches TTL
CDN/Edge (Railway) Next.js ISR Rendered HTML for category, filter, issuer, and product detail pages 1 h (category), 4 h (product detail)
Next.js fetch cache fetch() with next: { revalidate } in RSC (React Server Components) Backend API responses for SSR (Server-Side Rendering) pages (comparison pages, dashboard) 15 min (comparison), 1 min (user data skipped)
In-process (API) node-lru-cache (max 500 entries, LRU (Least Recently Used) eviction) Product list query results by vertical + filter hash 1 h
Database PostgreSQL B-tree indexes on vertical, issuer_id, status, updated_at N/A — query optimization N/A

The in-process LRU cache in the API service is a lightweight addition using lru-cache (no external dependency, runs in the Node.js process). It is explicitly designed to be replaced by Redis without interface changes: the Cache Layer component in the Product Comparison Service has a provider interface that supports both implementations.

interface CacheProvider {
  get(key: string): Promise<string | null>;
  set(key: string, value: string, ttlSeconds: number): Promise<void>;
  del(key: string): Promise<void>;
}

// Phase 1: InProcessLRUCache implements CacheProvider
// Phase 2: RedisCache implements CacheProvider

Cache invalidation in Phase 1:

Phase 2 Redis introduction threshold — introduce Redis when ANY of the following is true:

  1. p95 PostgreSQL query latency on product list queries exceeds 100 ms (measured from Railway metrics dashboard).
  2. Peak concurrent active sessions exceed 200 (measured from Next.js server memory usage on Railway).
  3. The Backend API Service requires horizontal scaling (multiple Railway instances) — in-process LRU caches are not shared across instances, making Redis necessary for consistency.
  4. Rate-limit counters need to be shared across multiple API instances (in-process counters do not work with horizontal scaling).

When Redis is introduced, only two changes are required:

  1. Provision Railway Redis and inject REDIS_URL as an environment variable.
  2. Swap InProcessLRUCache for RedisCache in the dependency injection configuration. No changes to callers.

Positive Consequences

Negative Consequences / Trade-offs


Pros and Cons of the Options

Option Ops Complexity Cost Effectiveness at Phase 1 Scale Upgrade Path
Next.js fetch cache + ISR (chosen) Low Zero (no Redis) High (ISR eliminates DB hits for SEO traffic) Clear (CacheProvider interface)
Redis from day one Medium ~$10/month High N/A
No caching Very low Zero Low at scale N/A
In-process LRU only (no ISR) Low Zero Medium (no edge caching) Partial

References