DBRaven
Architecture Decision RecordProposed

Use Content Management Platform as the Foundational Architecture Pattern

Deterministic ADR derived from topology, simulation, and advisor intelligence for Content Management Platform. Traceable to YAML knowledge entities.

Context

Content management systems have a deceptively simple data model that hides several operational traps. A content item references authors, categories, tags, related articles, embedded media, and locale variants: and a single content API response may need all of them. Naive ORM usage produces N+1 queries that are invisible at small catalog sizes but become table scans at 100K+ article catalogs. Published content is immutable between editorial changes, making it an excellent Redis cache candidate: except that cache invalidation on publish must propagate before the first reader after publication, or stale content is served. Preview of draft content must bypass all caches without polluting them. Translation management adds a second correctness dimension: a published English article with an in-review French translation must serve the last approved French version, not the draft. Every read path decision (cached vs. live, primary vs. replica) must encode the correct freshness requirement for each content state. Primary operational risks include: N+1 query cascade on content relationship traversal: a content list API that fetches 20 articles, then issues one query per article to fetch the author, one per article to fetch categories, and one per article to fetch related articles produces 60+ queries per API request; at 100 concurrent readers this is 6,000+ queries per second against PostgreSQL from an endpoint that looks like light traffic in application metrics; the ORM-level fix (eager loading with joins or batch fetching) must be applied as a matter of policy, not discovered by observing database saturation; Cache stampede on content publish: when a high-traffic content item is published, the first step deletes or updates the Redis cache key; all concurrent readers that hit the API in the interval between cache invalidation and the first successful re-population miss the cache and query PostgreSQL simultaneously; for a popular article serving 10,000 req/minute at steady state, the cache invalidation window can generate 200+ simultaneous database reads before the cache repopulates; Draft preview cache contamination: if a CMS editor previewing a draft content item uses the same cache key as published content (e.g., keyed only on content_id), the draft version populates the cache and is served to public readers; editorial preview must use a completely separate cache namespace or bypass Redis entirely.

Decision

We will adopt the **Content Management Platform** architecture pattern. This is a moderate-complexity architecture appropriate for teams at experienced backend team level or above. The advisor rates this pattern as 'intermediate' operational maturity.

Rationale

A CMS for publishing and serving structured content: articles, documentation, product pages, and localized variants: where read APIs serve 50–100x more traffic than editorial write APIs. PostgreSQL stores the content graph (articles, authors, categories, taxonomy) and workflow state (draft, in-review, scheduled, published). Redis caches published content objects for read APIs. Elasticsearch powers full-text content search with faceting and relevance ranking. Cache invalidation on publish must be fast and complete; N+1 query patterns on content relationship traversal are the dominant database performance risk during reads. Core technology stack: postgresql, redis, elasticsearch.

Accepted Tradeoffs

  • Redis caching of published content eliminates database reads for the vast majority of traffic (published content is read-only between editorial changes) but requires an explicit invalidation strategy on publish that is synchronous enough to prevent stale content from being served after the editorial workflow completes
  • Read replicas reduce primary database load for analytical and search-adjacent queries but introduce replication lag that means a reader may see a pre-publish version of a content item for the duration of the replication lag window after publication; the read routing logic must distinguish between "eventual is fine" (analytics, search indexing) and "must be fresh" (preview, post-publish verification)
  • Materialized views for frequently-queried content aggregations (recent articles per category, author article counts) eliminate expensive GROUP BY queries but require explicit refresh after content updates; synchronous refresh blocks the write transaction; asynchronous refresh introduces a staleness window in the aggregation
  • Elasticsearch provides rich faceted search and relevance ranking that PostgreSQL full-text search cannot match at scale, but adds a second data store that must be kept synchronized with the primary content store; index lag during high-volume publishing events means search results can lag editorial publish by 5–30 seconds depending on indexer throughput and Elasticsearch indexing queue depth

Risks

highThundering Herd (Cache Stampede)

When a popular cached key expires or a service recovers from downtime, all requests that were waiting or arrive simultaneously miss the cache and hit the origin database concurrently, producing a request spike that can overwhelm the database within seconds.

highCache Stampede (Dog-Pile)

When a widely-shared cached value expires or is invalidated, all concurrent requests that miss simultaneously trigger identical expensive database queries, overwhelming the origin store before any single result can be computed and cached: a positive feedback loop that can collapse the database within seconds.

highReplication Lag Cascade

Asynchronous replicas fall behind the primary under write load and serve reads from an older version of the data. Reads keep succeeding, so nothing errors; what breaks is one of three specific consistency guarantees (read-after-write, monotonic reads, or consistent prefix), each with a distinct user-visible anomaly.

moderateN+1 Query Problem

Application code issues one query to fetch N parent records, then issues N individual queries to fetch each child record, executing N+1 round trips to the database instead of 1–2, multiplying database load proportionally to the result set size.

moderateMissing Index Query Degradation

A query executes without an appropriate index, causing a sequential scan that is orders of magnitude slower than an indexed lookup and saturates CPU and I/O for all other queries on the same database instance.

Alternatives Considered

AI Retrieval-Augmented Generation Platform was not selected because its high operational complexity exceeds the current team's readiness relative to Content Management Platform's moderate complexity.

Analytics Data Platform was not selected because its high operational complexity exceeds the current team's readiness relative to Content Management Platform's moderate complexity.

API Gateway Platform was not selected because its high operational complexity exceeds the current team's readiness relative to Content Management Platform's moderate complexity.

Audit and Compliance Platform was not selected because its high operational complexity exceeds the current team's readiness relative to Content Management Platform's moderate complexity.

Scaling Thresholds

Signals indicating the architecture is approaching its scaling limits:

Tier 1: N+1 Query Amplification

Signal: PostgreSQL pg_stat_statements showing > 10 distinct query patterns with high call counts from the content list API path; database queries per second growing linearly with API request rate for content list endpoints (should be sub-linear with proper batch fetching); p99 for content list API > 200ms during moderate traffic

Evolution: Audit every content API response with query logging enabled and count queries per request for each content type; implement eager loading for all included relationships (JOIN for 1:1, IN-clause batch for 1:N); validate that content list endpoints produce a fixed number of queries regardless of list size (O(1) queries, not O(N)); add a query count assertion to integration tests for content list endpoints to prevent regression

Tier 2: Cache Invalidation Thundering Herd

Signal: PostgreSQL read replica CPU spike correlated exactly with publish events; Redis cache hit rate dropping to near 0% immediately after publish for popular content; content API p99 spiking from < 20ms to > 500ms during the 1–3 second window after a high-traffic content item is published

Evolution: Implement cache-aside with probabilistic early expiration (PER): before the cache TTL expires, a fraction of reads proactively refresh the cache value while other reads continue serving the cached value; this eliminates the hard expiry boundary that causes simultaneous misses; alternatively, on publish, write the new content value directly into the cache key before invalidating the old one (update-in-place rather than delete-and-miss) to eliminate the invalidation gap

Tier 3: Elasticsearch Index Throughput During Bulk Publish

Signal: Elasticsearch indexing queue depth > 10,000 during a scheduled content release event; search results for newly published content not appearing within 30 seconds of publish; Elasticsearch bulk index API returning 429 (too many requests) from the indexing worker

Evolution: Implement index write buffering in the indexing worker: batch Elasticsearch bulk API calls at 100–500 documents per request instead of indexing one document per publish event; configure Elasticsearch index.refresh_interval to 30 seconds during bulk ingest (extend from default 1 second) and reset to 1 second after ingest completes; use index aliases so a bulk re-index can be built on a new index and alias-swapped atomically without search downtime

Tier 4: Translation Variant Volume

Signal: Content catalog growing to > 10 locale variants per article; Redis cache memory growing proportional to locale count (one key per content_id per locale); PostgreSQL join queries for translation state machine spanning 5+ tables and running > 50ms on the editorial API

Evolution: Evaluate separating the translation store from the primary content store: a lightweight key-value store or PostgreSQL JSONB column for translation content vs. the structured relationship graph for content metadata; for Redis caching, use a single key per locale (content:{locale}:{content_id}) with LRU eviction and size the cache for the top 3 most-served locales, not all locales equally

Migration Path

1

PostgreSQL primary serving all content reads directly (no caching)Redis cache-aside for published content with explicit publish invalidation

Content read API p99 > 100ms during peak traffic; PostgreSQL read IOPS exceeding 80% of provisioned capacity; read replica falling behind on heavy read workloads

2

PostgreSQL full-text search (tsvector, GIN index)Elasticsearch with incremental indexing via CDC or outbox

Full-text search p99 > 500ms; inability to implement faceted filtering (filter by category, date range, author simultaneously) with acceptable PostgreSQL query performance; relevance ranking quality insufficient for user-facing search

3

Single PostgreSQL instance serving reads and writesRead replica routing with CQRS separation for analytics and search

Month-end content performance reports generating sequential scan queries that compete with live content reads; analytics queries running > 30 seconds on the primary causing write latency spikes

Operational Requirements

  • Minimum team maturity: Experienced Backend Team: This scenario has moderate operational complexity. It is recommended for Experienced Backend Team teams or higher.
  • Runbooks and alerting for high-severity risks: 3 high-severity risks identified. Each requires a documented runbook, alerting threshold, and on-call response procedure before running in production.
  • Cache sizing and eviction policy configuration: Redis or equivalent cache requires correct maxmemory configuration, eviction policy selection (allkeys-lru is common), and cold-start warming strategy after restarts.
  • Elasticsearch: scenario uses Elasticsearch as a primary datastore: Elasticsearch is a search index, not a source of truth: add a durable primary store and sync to ES
DBRaven knowledge base: deterministic, YAML-backed, traceable

Export