Senior 4 min · March 17, 2026

Polyglot Persistence — Dual Write Failure Omits Search

A PostgreSQL write succeeded but Elasticsearch timed out — no rollback left products missing from search.

N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Drawn from code that ran under real load.

Follow
Production
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Polyglot persistence: each access pattern gets its own database type.
  • Relational (PostgreSQL) for transactional data with ACID guarantees.
  • Redis for sub-millisecond session and cache lookups.
  • Elasticsearch for full-text search with typo tolerance.
  • Neo4j for graph traversal (recommendations, social features).
  • Hardest part: keeping data consistent across these systems — dual writes fail silently.
✦ Definition~90s read
What is Polyglot Persistence?

Polyglot persistence is the practice of using multiple, specialized database technologies within a single application or service, each chosen for its specific strengths rather than forcing everything into a single relational database. You might use PostgreSQL for transactional data with strong ACID guarantees, Elasticsearch for full-text search, Redis for caching and session state, and Neo4j for graph relationships.

Polyglot persistence means using different types of databases for different jobs.

The core idea is that no single database excels at every workload—relational databases handle joins and consistency well but struggle with flexible schemas or high-velocity writes, while NoSQL stores like MongoDB offer horizontal scaling at the cost of transactional guarantees. Companies like Uber and Netflix run dozens of database types in production, but this comes at a steep price: you now own the consistency between them, and that's where the article's titular failure mode lives.

The fundamental problem polyglot persistence introduces is that writes across these systems are not atomic. When your application writes a new order to PostgreSQL and then attempts to index it in Elasticsearch, a crash or network partition between those two operations leaves your search index stale or missing data entirely—a dual write failure.

This isn't a theoretical edge case; it's the default behavior of distributed systems. The CAP theorem and the fallacies of distributed computing aren't abstract concepts here—they're the reason your search results are incomplete and your users are confused.

Without a distributed transaction coordinator (which most NoSQL systems don't support), you're left with eventual consistency at best, and silent data loss at worst.

This pattern is appropriate when your query patterns are genuinely heterogeneous—think an e-commerce platform where product catalog search needs full-text indexing, inventory needs strong consistency, and recommendation engines need graph traversals. But you should never adopt polyglot persistence just because it's trendy or because you read a blog post about how Netflix does it.

The operational complexity is real: you now manage multiple backup strategies, monitoring dashboards, connection pools, and failure modes. The sweet spot is when the cost of maintaining separate systems is outweighed by the performance or query capability gains—typically at a scale where a single database's query planner can't optimize across all your access patterns.

For most teams below that scale, a single PostgreSQL instance with extensions (PostGIS for geospatial, pg_trgm for fuzzy search, or even a dedicated read replica) will serve you better than stitching together three different databases and praying your application code never crashes mid-write.

Plain-English First

Polyglot persistence means using different types of databases for different jobs. Think of a toolbox: you wouldn't use a hammer for every task — you use a screwdriver for screws, a wrench for bolts. Similarly, you use a relational database for orders, a cache for sessions, a search engine for product search, and a graph database for recommendations. Each database is optimised for a specific type of work.

Why Polyglot Persistence Fails When Writes Aren't Atomic

Polyglot persistence means using different storage technologies for different data needs within the same system — PostgreSQL for transactions, Elasticsearch for search, Redis for caching. The core mechanic is that each write operation must succeed in every store for the system to remain consistent. In practice, this introduces a dual-write problem: when you update a record in both a relational database and a search index, a failure in one leaves the system in an inconsistent state. The search index may miss the update, causing stale or missing results. Use polyglot persistence when query patterns demand specialized engines — e.g., full-text search, geospatial queries, or high-speed caching — that a single database cannot serve efficiently. It matters because it optimizes read performance and scalability, but only if you handle write failures with patterns like outbox, change data capture, or distributed transactions. Without these, your search becomes silently incomplete.

Dual Write Failure Is Silent
A failed write to the search index doesn't throw an error to the user — it just returns missing results. You won't know unless you monitor write success rates.
Production Insight
A team updated user profiles in PostgreSQL but the Elasticsearch write timed out. Users searching for 'Jane Doe' got no results for 30 minutes until a reconciliation job ran.
Symptom: search results are missing recently updated records, but no error is logged because the primary write succeeded.
Rule of thumb: never dual-write to two stores in the same request path — use an outbox table or CDC to guarantee eventual consistency.
Key Takeaway
Polyglot persistence optimizes reads at the cost of write complexity.
Dual writes without atomicity guarantee silent data loss in secondary stores.
Always decouple writes using an outbox pattern or change data capture.
Polyglot Persistence Dual Write Failure THECODEFORGE.IO Polyglot Persistence Dual Write Failure How inconsistent writes break search across databases Dual Write to DB & Search Write to Postgres and Elasticsearch in same transaction Write to DB Succeeds Primary database commit completes Write to Search Fails Network error or timeout on search index Data Inconsistency Search missing record, queries return stale results Outbox Pattern Fix Write to outbox table, CDC syncs to search ⚠ Dual writes without coordination cause silent data loss Use transactional outbox or CDC to ensure eventual consistency THECODEFORGE.IO
thecodeforge.io
Polyglot Persistence Dual Write Failure
Polyglot Persistence

Database Types and Their Sweet Spots

Each database type excels at specific access patterns. Relational databases enforce ACID and support complex joins – they're for transactional core. Document databases tolerate schema variations – ideal for catalogs. Key-value stores provide constant-time lookups – perfect for session data. Search engines invert indices for fast full-text queries. Graph databases traverse relationships efficiently. Time-series databases optimize for write-heavy append-only data. Choosing the wrong database for a pattern leads to painful workarounds: for example, using a relational database for full-text search leads to inefficient LIKE queries and poor relevance ranking.

ExamplePYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Package: io.thecodeforge.python.system_design

# Typical polyglot architecture for an e-commerce system:

# PostgreSQL — relational: users, orders, products, payments
# - ACID transactions: order.create() and inventory.decrement() atomically
# - Complex queries: reporting, joins between entities
# - Example: user creates an order

# Redis — key-value: sessions, caching, rate limiting
# - Sub-millisecond reads: session token → user object
# - TTL-based expiry: sessions expire automatically
# - Example: cache product page for 10 minutes

# Elasticsearch — search: product search with relevance
# - Full-text search with typo tolerance
# - Faceted search: filter by price, brand, rating
# - Example: user searches 'wireless headphones'

# MongoDB (document) — product catalogue
# - Flexible schema: different products have different attributes
# - Laptop has CPU, RAM; T-shirt has size, colour
# - Example: store varied product attributes without schema migration

# Neo4j (graph) — social features, recommendations
# - 'Users who bought X also bought Y'
# - Friend-of-friend queries
# - Example: find all users within 3 hops in social graph

# InfluxDB (time-series) — metrics, analytics
# - Write-optimised for timestamped data
# - Example: page views, API response times
Output
# Each database chosen for its access pattern strengths
The Full Toolbox Analogy
  • Relational = socket wrench (precise, standard, can handle many bolts)
  • Document = adjustable wrench (flexible, fits odd shapes)
  • Key-Value = screwdriver (fast, specific, limited range)
  • Search = power drill (specialized for one task but extremely fast)
  • Graph = pliers (excellent for gripping complex relationships)
Production Insight
Using the wrong database type for a workload creates production pain that's hard to undo.
Teams often attempt to 'make it work' with feature X of a relational DB, only to hit scaling limits.
Rule: if you're writing a full-text search engine on top of PostgreSQL FTS, buy yourself time but plan to migrate to a search-specific database.
Key Takeaway
Pick the database that matches the access pattern, not the one you're most comfortable with.
Your PostgreSQL won't become Elasticsearch with enough indices.
The best database is the one that makes your code simpler.
Choosing a Database Type
IfNeed ACID transactions across entities
UseUse a relational database (PostgreSQL, MySQL)
IfData structure varies per record (product attributes differ)
UseUse a document database (MongoDB, Couchbase)
IfSub-millisecond reads with TTL required
UseUse a key-value store (Redis, Memcached)
IfFull-text search with relevance ranking
UseUse a search engine (Elasticsearch, Solr)
IfDeep relationship traversal (friend-of-friend, recommendations)
UseUse a graph database (Neo4j, Amazon Neptune)
IfHigh-write throughput of timestamped data
UseUse a time-series database (InfluxDB, TimescaleDB)

Data Consistency Across Systems

Data consistency across multiple databases is the central challenge of polyglot persistence. When the same logical entity (e.g., a product) lives in PostgreSQL (source of truth) and Elasticsearch (search index), any update must propagate to both. Two common patterns: dual writes (application writes to both) and change data capture (CDC) where the primary database's write-ahead log is streamed to secondary systems. Dual writes are simple but fragile – partial failures leave data permanently inconsistent. The outbox pattern mitigates this by writing a message within the same transaction as the primary write, then having a separate process deliver it asynchronously. CDC avoids application-level dual writes entirely but adds infrastructure complexity (Debezium, Kafka).

ExamplePYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import asyncio

# When a product is created: must update PostgreSQL AND Elasticsearch
# Option 1: Dual write (naive — risks partial failure)
async def create_product_bad(product):
    await postgres.insert('products', product)      # succeeds
    await elasticsearch.index('products', product)  # what if this fails?
    # Product exists in Postgres but not in search — inconsistency

# Option 2: Write to primary, sync via CDC (Change Data Capture)
# Debezium reads PostgreSQL WAL → publishes to Kafka → Elasticsearch consumer
# Primary write is source of truth; Elasticsearch is eventually consistent

# Option 3: Outbox pattern
async def create_product_outbox(product):
    async with postgres.transaction():
        await postgres.insert('products', product)
        await postgres.insert('outbox', {
            'event': 'product_created',
            'data': product,
            'processed': False
        })
    # Separate process reads outbox and syncs to Elasticsearch
    # If it fails, retries safely (at-least-once delivery)

print('Outbox pattern ensures eventual consistency')
Output
Outbox pattern ensures eventual consistency
Production Insight
Dual writes fail silently in production – a timeout on the secondary write doesn't roll back the primary.
The outbox pattern gives you at-least-once delivery guarantees, but requires careful deduplication.
Rule: never dual-write to two databases without a transactional outbox or CDC.
Key Takeaway
Dual writes are the #1 cause of data inconsistency in polyglot systems.
Use the outbox pattern or CDC for reliable synchronization.
Accept eventual consistency – your users will forgive a few seconds of lag, not permanently missing data.

When to Adopt Polyglot Persistence

Polyglot persistence adds operational cost. Adopt it only when a single database clearly fails to meet requirements. Signs you need multiple databases: your relational database has a 3-second full-text search query, your document database can't enforce a unique constraint across documents, your key-value store can't do aggregation queries. Start with one database (usually relational) and add others incrementally. The rule: each new database must solve a problem that the existing stack cannot solve without significant hackery. If you can solve it with a secondary index, a materialized view, or a dedicated read replica, do that first.

Production Insight
Teams often adopt polyglot persistence prematurely, adding five databases to a system that only serves 1000 QPS.
The operational burden of managing multiple DBs – backups, monitoring, patching – grows non-linearly.
Rule: only add a database when you can't make the existing one work, and be ready to hire specialists for each.
Key Takeaway
Polyglot persistence is a solution, not a design goal.
Start with one database – add others only when you hit a measurable wall.
Each additional database is a long-term commitment to operational maintenance.
Should You Add Another Database?
IfCurrent database handles 95% of patterns adequately
UseDo not adopt polyglot – optimize existing database first.
IfSpecific pattern is 10x slower than alternative database
UseConsider adding one more database for that pattern.
IfTwo distinct patterns with conflicting requirements
UseSplit into two databases, each optimized for its pattern.

Operational Complexity Realities

Running a polyglot system means managing multiple database technologies, each with its own backup strategy, monitoring stack, scaling approach, and failure modes. You need expertise in each database – a PostgreSQL DBA might not know Elasticsearch shard sizing. Monitoring must cover each database's health metrics. Incident response becomes more complex because a single business transaction touches multiple systems. Invest in automation: consistent deployment via IaC, standardized monitoring dashboards, and runbooks for each database. The cost of this operational overhead must be justified by the business value gained from using the right tool.

Production Insight
In an outage, diagnosing a polyglot system takes longer because you have to check multiple databases and their sync state.
Example: a product reduction in PostgreSQL not reflecting in search because the CDC consumer crashed 4 hours ago.
Rule: invest heavily in observability (distributed tracing, log aggregation, synthetic transactions) across all databases.
Key Takeaway
Polyglot persistence doesn't eliminate operational complexity – it distributes it.
Your monitoring and alerting must cover every database's health and sync pipeline.
You can't have a polyglot system without a dedicated SRE team or equivalent automation.

Data Synchronization Patterns (Outbox, CDC, Batch)

Three common patterns to synchronize data between databases: 1) Outbox pattern: write to primary and an outbox table in the same transaction. A separate process reads the outbox and pushes to secondary systems. Guarantees at-least-once delivery. Requires deduplication. 2) Change Data Capture: use a tool like Debezium to stream changes from the primary database's transaction log. Decouples application from sync logic. Adds infrastructure components (Kafka, connectors). 3) Batch synchronization: scheduled jobs that periodically compare data between systems. Simple to implement but latency can be minutes to hours. Only suitable for non-critical data. Choose based on latency requirements and infrastructure maturity.

ExamplePYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Package: io.thecodeforge.python.sync_patterns

# Outbox pattern - reliable async sync
# Write to PostgreSQL and outbox in same transaction
async def outbox_create_product(product):
    async with postgres.transaction():
        await postgres.insert('products', product)
        await postgres.insert('outbox', {
            'event': 'product_created',
            'data': product,
            'processed': False
        })

# Worker process (runs periodically or via message queue)
async def outbox_worker():
    events = await postgres.query(
        "SELECT * FROM outbox WHERE processed = false"
    )
    for event in events:
        try:
            await elasticsearch.index('products', event.data)
            await postgres.update('outbox', 
                {'processed': True}, 
                {'id': event.id}
            )
        except Exception:
            # Retry later (exponential backoff)
            pass

print('Outbox pattern ensures eventual consistency with retries')
Output
Outbox pattern ensures eventual consistency with retries
Production Insight
CDC via Debezium + Kafka is the gold standard for low-latency sync, but it introduces operational complexity.
The outbox pattern is simpler and works well for most systems, provided the worker is resilient and idempotent.
Batch sync is the fallback – good for reporting systems where hourly updates are acceptable.
Rule: choose outbox for new systems; CDC for existing ones where you can't modify the application.
Key Takeaway
Outbox pattern is the simplest reliable sync mechanism – start there.
CDC decouples sync from application code but adds infrastructure.
Batch sync is for non-critical, high-latency-tolerant use cases only.
Choosing a Sync Pattern
IfLow latency required (< 1s), can modify application code
UseUse outbox pattern with a dedicated worker.
IfLow latency required, cannot modify application
UseUse CDC (Debezium + Kafka).
IfHigh latency acceptable (minutes to hours)
UseUse batch synchronization (cron job, scheduled query).

Why Your Query Patterns Dictate Storage Before You Write a Line of Code

The most expensive mistake I see is teams choosing a database because it sounds cool, then twisting their queries to fit. That's backwards. Every database makes a bet on access patterns. Document stores assume you read entire aggregates. Graph databases assume you traverse relationships. Key-value stores assume you fetch by single key. If your query pattern doesn't match that bet, you're fighting the tool.

Here's the specific trap: teams start with a relational model because it's familiar, then realize they need complex joins across documents. They switch to Mongo but still write relational patterns—multiple collections, manual joins in app code. The result is worse performance and harder reasoning than a simple Postgres setup.

The fix: before provisioning any store, write out the five most critical query paths. Include frequency, latency requirements, and consistency needs. If most queries are point lookups by ID, use a key-value store. If most traverse entity relationships, use a graph database. If you need ad-hoc aggregations, stick with relational. This decision should be the output of your requirements, not the input.

QueryPatternAnalyzer.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// io.thecodeforge
// This class models the decision: map query patterns to storage types
// before writing any persistence code.

import java.util.*;

enum QueryPattern {
    POINT_LOOKUP,
    RANGE_SCAN,
    RELATIONSHIP_TRAVERSAL,
    AGGREGATION,
    TEXT_SEARCH
}

enum StorageType {
    KEY_VALUE, DOCUMENT, GRAPH, RELATIONAL, SEARCH_INDEX
}

public class QueryPatternAnalyzer {
    private final Map<QueryPattern, StorageType> patternToStore = Map.of(
        QueryPattern.POINT_LOOKUP, StorageType.KEY_VALUE,
        QueryPattern.RELATIONSHIP_TRAVERSAL, StorageType.GRAPH,
        QueryPattern.AGGREGATION, StorageType.RELATIONAL,
        QueryPattern.TEXT_SEARCH, StorageType.SEARCH_INDEX
    );

    public StorageType recommend(List<QueryPattern> topPatterns) {
        Map<StorageType, Integer> votes = new HashMap<>();
        for (QueryPattern p : topPatterns) {
            StorageType s = patternToStore.get(p);
            if (s != null) votes.merge(s, 1, Integer::sum);
        }
        return votes.entrySet().stream()
            .max(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .orElse(StorageType.RELATIONAL); // default for unhandled
    }
}
Output
StorageType.KEY_VALUE for 7 POINT_LOOKUP queries, StorageType.GRAPH for 3 RELATIONSHIP_TRAVERSAL queries -> returns KEY_VALUE
Production Trap:
Don't let devs choose databases based on personal preference. Every database migration costs 2-3 weeks of operational learning. I've seen a team waste six months on Cassandra because 'it scales' when their workload was 95% point lookups and a Redis cluster solved it in two days.
Key Takeaway
Query patterns are the specification for your storage layer. If your database choice doesn't match your top five queries, you've already lost.

The Real Cost of Polyglot Persistence Isn't Code — It's Operational Debt

I've debugged production incidents where the database was fine but the operational chain wasn't. Polyglot persistence multiplies your monitoring surfaces, backup strategies, and disaster recovery plans. Each store has its own failure modes. Redis hits OOM under memory pressure. Mongo can lock under heavy writes. Postgres has vacuum storms. You need distinct runbooks for each.

Here's what nobody tells you: operational debt compounds faster than code debt. Adding a second database type doubles your alerting rules, triples your backup restore tests, and introduces data loss scenarios that span systems. A crash in the document store that doesn't propagate to the relational store creates partial state that's nearly impossible to reconcile.

The rule I enforce: for every new storage technology, the team must write a failure mode document before production deploy. List every planned recovery strategy, backup SLA, and cross-system consistency check. If the team can't produce that in a day, they don't understand the technology well enough to run it.

Do not add a new database because it optimizes one query. Add it because the query's value justifies the operational cost across the entire system lifecycle.

FailureModeChecklist.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// io.thecodeforge
// Validates that before adding a new store, the team has accounted for failure modes.

public class FailureModeChecklist {
    static class StoreChecklist {
        final String storeName;
        final boolean hasBackupStrategy;
        final boolean hasRestoreTest;
        final boolean hasAlertForEachFailure;
        final boolean hasCrossSystemConsistencyCheck;

        StoreChecklist(String name, boolean backup, boolean restore,
                       boolean alerts, boolean consistency) {
            this.storeName = name;
            this.hasBackupStrategy = backup;
            this.hasRestoreTest = restore;
            this.hasAlertForEachFailure = alerts;
            this.hasCrossSystemConsistencyCheck = consistency;
        }

        boolean readyForProduction() {
            return hasBackupStrategy && hasRestoreTest
                && hasAlertForEachFailure && hasCrossSystemConsistencyCheck;
        }
    }

    public static void main(String[] args) {
        StoreChecklist redis = new StoreChecklist(
            "Redis", true, true, true, false
        );
        System.out.println("Redis ready: " + redis.readyForProduction());
        // Output: Redis ready: false — missing cross-system consistency
    }
}
Output
Redis ready: false
Production Trap:
Never assume a database will recover cleanly. I've seen a Redis cluster come back from an auto-failover with stale data that poisoned downstream aggregations for 48 hours. The team didn't have a reconciliation script. They had to rebuild from backups.
Key Takeaway
Each database type adds a unique operational burden. If you can't write a failure mode document for it, you can't run it in production.
● Production incidentPOST-MORTEMseverity: high

Lost Products in Search After Dual Write Failure

Symptom
Users reporting that newly added products do not appear in search results. Old products are searchable.
Assumption
Search indexing is near-real-time; any delay should be seconds.
Root cause
Dual write pattern without transactional guarantees. PostgreSQL write succeeded, Elasticsearch write threw a timeout – no rollback, no retry. Products were permanently missing from search.
Fix
Implemented the outbox pattern: write product to PostgreSQL within a transaction, also insert an event row in an outbox table. A background worker reads outbox events and pushes them to Elasticsearch with retries. CDC via Debezium is an alternative.
Key lesson
  • Dual writes are fragile – a partial failure leaves data inconsistent.
  • Always use an outbox or CDC for multi-database updates.
  • Search should be eventually consistent – accept the delay, but guarantee delivery.
Production debug guideCommon symptoms and immediate diagnostic steps when synchronization fails3 entries
Symptom · 01
Product shows in PostgreSQL but not in Elasticsearch
Fix
Check the outbox table for unprocessed events. Query: SELECT * FROM outbox WHERE processed = false;. Also examine the CDC consumer lag if using Debezium.
Symptom · 02
Session data not found in Redis
Fix
Verify TTL expiry and Redis memory eviction policy (check maxmemory and eviction strategy). Use redis-cli to check memory usage and key count.
Symptom · 03
Graph recommendations stale or missing
Fix
Verify the CDC pipeline (Debezium + Kafka) is running and consumer lag is low. Check Kafka consumer offsets.
★ Polyglot Sync Quick DebugFive-minute diagnose for data synchronization issues in polyglot systems
New record missing in secondary database
Immediate action
Check application logs for write errors in the last 5 minutes.
Commands
SELECT * FROM outbox WHERE processed = false;
kubectl logs -l app=outbox-worker --tail=50
Fix now
Manually trigger a sync job: invoke /sync endpoint or run outbox worker once.
Stale data in search after update+
Immediate action
Check the last successful sync timestamp on Elasticsearch document.
Commands
curl -XGET 'localhost:9200/products/_search?q=_id:123'
kubectl logs -l app=cdc-connector --tail=20
Fix now
Force index refresh: POST /products/_refresh
Redis session not found but PostgreSQL session row exists+
Immediate action
Check Redis maxmemory and eviction policy.
Commands
redis-cli INFO memory | grep maxmemory
redis-cli CONFIG GET maxmemory-policy
Fix now
Increase maxmemory or adjust eviction policy; flush expired keys with redis-cli.
Database TypeBest ForExampleNot Good For
Relational (PostgreSQL)Transactional data, complex queriesOrders, users, paymentsFull-text search, graph traversal
Document (MongoDB)Flexible schema, nested dataProduct catalogue, CMSComplex multi-document transactions
Key-Value (Redis)Caching, sessions, queuesSession store, rate limiterComplex queries, large datasets
Search (Elasticsearch)Full-text search, analyticsProduct search, log analyticsPrimary storage, ACID transactions
Graph (Neo4j)Relationships, recommendationsSocial graph, fraud detectionWrite-heavy, simple key-value lookups
Time-Series (InfluxDB)Timestamped metricsMonitoring, IoTRelational data, flexible schema

Key takeaways

1
Polyglot persistence
use the right database for each access pattern, not one database for everything.
2
The hardest problem
keeping data consistent across multiple systems.
3
CDC (Change Data Capture) + Kafka is the standard pattern for syncing across systems.
4
Outbox pattern ensures at-least-once delivery without dual-write inconsistency.
5
Operational complexity increases with each database technology
justify each addition.
6
Start with one database, measure the pain, then add another only if necessary.

Common mistakes to avoid

3 patterns
×

Dual-writing to multiple databases without a transactional outbox

Symptom
Some data appears in PostgreSQL but not in Elasticsearch. Users report missing search results. Operational errors show intermittent timeouts on Elasticsearch writes.
Fix
Implement the outbox pattern: within the same database transaction, write the entity and an event record to an outbox table. A background worker reads unprocessed events and writes to secondary databases with retry logic.
×

Assuming all databases have the same consistency model

Symptom
A user reads from a read replica of PostgreSQL and sees stale data minutes after a write. Team assumes eventual consistency is 'instant' and makes business decisions based on stale data.
Fix
Understand each database's consistency guarantees. For PostgreSQL read replicas, there is replication lag. For Elasticsearch, index refreshes are near-real-time (1s default). Design your application to tolerate this staleness or use strong consistency reads where needed.
×

Adding a new database for every new feature without operational readiness

Symptom
Team struggles to manage backups, monitoring, and DB-specific failures. Each new database adds a new learning curve and pager duty rotation.
Fix
Establish a database onboarding process: assess operational impact, ensure monitoring and backup automation, document runbooks, and assign an owner before adding a new database to production.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is polyglot persistence and what are its benefits and drawbacks?
Q02SENIOR
How do you maintain consistency when data exists in both PostgreSQL and ...
Q03SENIOR
When would you choose Redis over a relational database for session stora...
Q04SENIOR
What operational challenges arise when managing a polyglot persistence s...
Q01 of 04SENIOR

What is polyglot persistence and what are its benefits and drawbacks?

ANSWER
Polyglot persistence is using multiple database technologies within a single application, each chosen for its specific strengths. Benefits: optimal performance per access pattern, reduced workarounds, better scalability. Drawbacks: operational complexity, data consistency challenges, higher cost for monitoring and expertise.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
When is polyglot persistence worth the complexity?
02
What is the biggest mistake teams make when adopting polyglot persistence?
03
How do you handle backups across different databases?
04
What is the difference between the outbox pattern and CDC?
N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Drawn from code that ran under real load.

Follow
Verified
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
🔥

That's Database Design. Mark it forged?

4 min read · try the examples if you haven't

Previous
CQRS with Databases
15 / 16 · Database Design
Next
Single Table Inheritance: When to Use It and When to Avoid It