Skip to content
ExponaBLOG
MONGODB, NOSQL & DATA MODELING · DAY 001 / 100

When MongoDB is the right choice: lessons from production

Document databases shine for some workloads and hurt in others.

Expona Admin7 min read

When MongoDB is the right choice: lessons from production

We've all heard the hype around MongoDB, and we've all seen projects where it became a painful mistake. The truth is simpler than the marketing: MongoDB excels at specific workload patterns and fails predictably at others. After years shipping document databases in production across e-commerce, SaaS, and ride-hailing systems, we can tell you exactly when to reach for it and when to step away.

The case for document databases

Document databases like MongoDB solve real problems that relational systems make harder. They're not better or worse in the abstract—they're better at some things and worse at others. The key is recognizing which problems you actually have before you commit to a schema-on-read architecture.

When MongoDB is the right choice in production, it typically involves workloads where flexibility, embedding, and horizontal scaling matter more than transactional consistency across many entities. Let's walk through the concrete scenarios where this plays out.

Flexible schemas for evolving products

In early-stage products and rapidly iterating features, schema changes are constant. Adding a new field to a user profile, capturing additional telemetry, or experimenting with a variant of user data shouldn't require a migration that locks your table for hours.

Relational databases force you to choose: perform a migration (risky, slow, requires downtime on large tables) or use nullable columns everywhere (which pollutes your schema and your application logic). MongoDB lets you add fields to documents incrementally. Old documents simply don't have the new field; new documents do. Your application code handles both gracefully.

We built a customer analytics platform for an e-commerce company where product managers constantly requested new event fields. With PostgreSQL, each request meant a migration. With MongoDB, we could ship new fields within hours, backfill gradually, and roll back without drama.

javascript

// Old events (from months ago)
{ userId: "123", event: "purchase", total: 99.99, timestamp: "2024-01-15" }

// New events (with variant tracking)
{ userId: "123", event: "purchase", total: 99.99, timestamp: "2025-01-15", variant: "checkout_v2", conversionTime: 47 }

// Application handles both:
const conversionTime = event.conversionTime || null;

This flexibility has real business value. Product iterations ship faster. A/B tests can embed their own metadata directly in events. Feature flags can add arbitrary configuration fields without schema coordination.

The trap: flexibility doesn't mean chaos. You still need validation in your application layer. MongoDB doesn't enforce it for you. Teams that skip validation end up with garbage data and confusing bugs.

Nested data read together, stored together

When you have hierarchical data that's almost always accessed as a unit, MongoDB's embedded documents model shines. Instead of normalizing across tables and paying the cost of joins on every read, you store the whole structure in one document.

Consider a ride-hailing platform. A trip record includes the rider, driver, vehicle, route, pricing breakdown, and events (pickup, start, end). In a relational system, that's at least seven tables. A single trip view query involves a seven-way join. Every read pays that cost.

In MongoDB, you embed all of it:

javascript

{
  _id: ObjectId("..."),
  tripId: "trip_abc123",
  status: "completed",
  rider: {
    id: "rider_001",
    name: "Alice",
    rating: 4.8,
    phone: "+1234567890"
  },
  driver: {
    id: "driver_042",
    name: "Bob",
    rating: 4.9,
    vehicle: {
      make: "Toyota",
      model: "Prius",
      plate: "ABC123"
    }
  },
  route: {
    pickup: { lat: 40.7128, lng: -74.0060 },
    dropoff: { lat: 40.7580, lng: -73.9855 },
    distanceKm: 5.2
  },
  pricing: {
    baseFare: 2.50,
    distanceFare: 6.76,
    surgeFactor: 1.0,
    total: 9.26
  },
  events: [
    { type: "requested", timestamp: "2025-01-15T10:00:00Z" },
    { type: "accepted", timestamp: "2025-01-15T10:02:00Z" },
    { type: "arrived", timestamp: "2025-01-15T10:08:00Z" },
    { type: "completed", timestamp: "2025-01-15T10:18:00Z" }
  ]
}

One read, one network round-trip, the entire trip. No join logic. No N+1 queries. The cost of embedding is that updating a driver's rating now requires updating every trip document where that driver appears—but that's a deliberate trade-off we make because reads vastly outnumber rating updates.

This pattern works beautifully for:

  • Content with rich metadata (articles with author, comments, tags, revisions)
  • Orders with line items, shipping, payment, and status history
  • User profiles with preferences, devices, addresses, and settings
  • Messages with participants, attachments, and read receipts

The embedded model also simplifies application code. Instead of hydrating objects across multiple queries, you get the complete structure in one call. Less code means fewer bugs.

Horizontal scaling with sharding

PostgreSQL scales vertically well and reads can scale horizontally with replicas. But writes don't scale horizontally without tricks. MongoDB's sharding primitive distributes both reads and writes across multiple servers based on a shard key.

We built a real-time trading system where order volume grew from hundreds to tens of thousands per second. With PostgreSQL on a single primary, we hit write throughput limits. Replication helped reads but didn't solve the core problem: writes had to hit one machine.

MongoDB let us shard by trading account. Account A's orders go to Shard 1, Account B's go to Shard 2. Now writes scale linearly—each shard handles its own portion of throughput. We scaled from one machine handling 2,000 writes/second to four machines handling 8,000 writes/second with no application changes.

The mechanics matter. Your shard key determines everything. A bad shard key (like status, which has only a few values) creates hotspots where one shard gets all the traffic. A good shard key (account ID, user ID, or something with high cardinality) distributes evenly.

Sharding also gives you geographic distribution. You can place different shards in different regions, keep data closer to users, and meet data residency requirements without replicating your entire dataset everywhere.

Not ideal for heavy cross-entity joins

MongoDB's weakness is the inverse of its strength. When you need to query across many entities with complex relationships, MongoDB forces you into application-level joins, which are slow and complicated.

Imagine a SaaS platform where you need to report: "Give me all invoices for customers in the automotive vertical who haven't paid in over 30 days, grouped by sales rep." That query involves customers (filter by vertical and sales rep), invoices (filter by status and date), and payment records. In SQL, it's a clear join:

javascript

SELECT rep.name, COUNT(*) as unpaid_count, SUM(inv.amount) as total_owed
FROM invoices inv
JOIN customers cust ON inv.customer_id = cust.id
JOIN sales_reps rep ON cust.sales_rep_id = rep.id
WHERE cust.vertical = 'automotive'
  AND inv.status = 'unpaid'
  AND DATE(inv.created_at) < DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY rep.id, rep.name;

In MongoDB, you'd fetch all invoices matching the date and status, then for each invoice, fetch the customer, check the vertical, fetch the sales rep, and aggregate in memory. That's hundreds or thousands of round-trips and a ton of application logic. It's slow and error-prone.

MongoDB added the $lookup operator for joins in aggregation pipelines, which helps—but it's still fundamentally not what the database was designed for. You'll feel the friction.

Where MongoDB hurts worst:

  • Analytics queries across many dimensions
  • Reporting that requires filtering on multiple related entities
  • Complex WHERE clauses involving JOINs
  • Historical auditing across connected records
  • Anything that feels like a "cube" or "data warehouse" query

The pattern: if your query naturally expresses as a join, use a relational database. If it's "give me this one document and everything in it," MongoDB is cleaner.

Common mistakes

Over-normalizing in MongoDB. Teams treat MongoDB like SQL and split data across collections unnecessarily. This defeats the entire purpose. If you're doing joins constantly, you picked the wrong database.

Ignoring shard key impacts. Developers choose a shard key late in the project or based on a guess. Then the database behaves unpredictably under load. Shard key design is as critical as primary key design in relational systems.

Skipping validation. Because the schema is flexible, teams don't validate in code. Six months later, you have documents with wildly different shapes and features that silently fail. Schema-on-write is a feature, not a limitation.

Treating MongoDB as a cache. Some teams use MongoDB because they think it's fast. It's not faster than PostgreSQL for most workloads—it's different. The speed comes from access patterns (embedding, sharding), not magic.

No transactions until recently. Older projects built complicated multi-step workflows without transactional guarantees. MongoDB now supports multi-document ACID transactions, but old habits die hard. Use them when you need consistency across documents.

Key takeaways

  • MongoDB excels when you have hierarchical data accessed as a unit, need flexible schemas for iteration, or require horizontal write scaling.
  • Embedded documents reduce query complexity and network round-trips compared to normalized relational schemas.
  • Sharding distributes writes across machines, solving throughput limits you'd hit with a single-primary relational database.
  • Avoid MongoDB for workloads heavy in cross-entity joins, complex reporting, or queries that naturally express as multi-table SELECT statements.
  • Shard key design is critical; a poor choice creates hotspots and uneven load distribution.
  • Flexibility requires discipline; validate your data in code even though the schema won't.
  • For most SaaS applications, a mix works best: PostgreSQL for relational data, MongoDB for hierarchical or semi-structured content.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles