SQL vs NoSQL Trade-offs in System design: When to Pick Which, With Examples

  • Last Updated: July 23, 2026
  • By: javahandson
  • Series
img

SQL vs NoSQL Trade-offs in System design: When to Pick Which, With Examples

Understand SQL vs NoSQL trade-offs in System design with clear rules for picking each. Covers ACID, CAP, sharding, and five real scenarios with Spring Boot code examples.

1. Introduction

Every backend project reaches the same fork in the road. You have your Spring Boot service ready. You know your entities. Then someone asks which database you plan to use. And the SQL vs NoSQL trade-offs debate starts all over again.

Most teams answer this badly. Some pick Postgres because that is what the last project used. Others pick MongoDB because it sounded modern in a conference talk. Neither reason holds up when traffic grows and the data model starts fighting back.

The honest truth is simpler than the internet suggests. Neither family is better. They optimise for different things. SQL databases protect correctness above all. NoSQL databases bend the rules to buy scale and freedom. Once you see what each one gives up, choosing gets much easier.

This article walks through that choice the way a senior engineer would. We start with what each family actually is. Then we compare them on the dimensions that matter in real systems: schema, scaling, joins, transactions, and consistency. After that, we work through concrete examples with Spring Boot code, so the ideas stick.

1.1 What This Article Covers

Here is the ground we will cover, in order:

  • What relational databases really promise, and what that costs you
  • The four main NoSQL families, and what each one is built for
  • Schema rigidity versus schema freedom, with real migration pain
  • Vertical and horizontal scaling, sharding, and why joins get hard
  • ACID, BASE, and the CAP theorem explained without the jargon
  • Five concrete scenarios with a clear pick and the reasoning behind it
  • Polyglot persistence, common mistakes, and interview questions

You do not need distributed systems background. If you have written a Spring Boot REST API and used JPA, you are ready.

sql vs nosql decision map

2. What SQL Databases Actually Give You

A SQL database stores data in tables. Each table has fixed columns. Each row follows that same shape. Postgres, MySQL, Oracle, and SQL Server all work this way.

That rigid shape is not a limitation. It is the whole product. Because the database knows the shape of your data, it can check your work. It can reject a bad insert. It can enforce that every order points at a real customer.

2.1 The Relational Model in One Minute

You split data into tables and connect them with keys. A customer lives in one table. Their orders live in another. The order table stores a customer id that points back.

This avoids duplication. If a customer changes their email, you update one row. Every order on its own sees the new value, because orders never stored a copy in the first place. That idea is called normal form design, and it is the heart of relational design.

CREATE TABLE customers (
    id      BIGSERIAL PRIMARY KEY,
    email   VARCHAR(255) NOT NULL UNIQUE,
    name    VARCHAR(120) NOT NULL
);
 
CREATE TABLE orders (
    id           BIGSERIAL PRIMARY KEY,
    customer_id  BIGINT NOT NULL REFERENCES customers(id),
    total_amount NUMERIC(12,2) NOT NULL,
    created_at   TIMESTAMP NOT NULL DEFAULT now()
);
 
-- The database itself refuses an order with a bad customer_id.
-- You get that check for free, on every insert, forever.

Look at that last line. The foreign key is a promise the database keeps for you. No application bug can break it. That promise is worth a lot when several services write to the same tables.

2.2 ACID: The Four Letters That Matter

SQL databases are famous for ACID transactions. The acronym sounds academic, but each letter solves a real problem you have probably hit.

  • Atomicity: a group of writes either all happen or none do. No half-finished money transfers.
  • Consistency: the data always satisfies your rules, like foreign keys and check constraints.
  • Isolation: two concurrent transactions do not trip over each other mid-flight.
  • Durability: once the database says committed, the data survives a power cut.

Atomicity is the one that saves careers. Imagine transferring money between accounts. You debit one row and credit another. If the server dies between those two writes, ACID promises the debit rolls back too. Without it, money simply vanishes.

@Service
public class TransferService {
 
    private final AccountRepository accounts;
 
    public TransferService(AccountRepository accounts) {
        this.accounts = accounts;
    }
 
    // Both updates commit together, or neither does.
    // A crash halfway through rolls the whole thing back.
    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        Account from = accounts.findById(fromId).orElseThrow();
        Account to   = accounts.findById(toId).orElseThrow();
 
        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException(fromId);
        }
 
        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
 
        accounts.save(from);
        accounts.save(to);
    }
}

One annotation buys you all of that. Spring opens a transaction, runs your method, and commits at the end. If an exception escapes, it rolls back instead. Try building the same safety by hand across two systems and you will appreciate what the database is doing.

2.3 Where SQL Starts to Hurt

So why does anyone use anything else? Because those promises have a price, and the bill arrives at scale.

First, the schema is rigid. Adding a column to a table with 500 million rows can lock it for minutes. Teams plan those migrations for weeks and run them at 3am.

Second, writes are hard to spread across machines. A single primary node usually handles all writes. You can add read replicas easily, but write capacity has a ceiling.

Third, joins get expensive as data grows. A three-table join across millions of rows is fine on one machine. Split those tables across ten machines and the same query turns into a network storm.

▶ Interview insight
Interviewers often ask why relational databases are hard to scale for writes. The answer is not “SQL is slow”. It is that ACID promises need node-to-node chatter, and node-to-node chatter across machines costs network round trips. Single-node writes keep isolation cheap. Spread them out and you pay for every lock check over the wire.

3. What NoSQL Actually Means

NoSQL is a terrible name. It groups together databases that have almost nothing in common, except that they are not relational. So treating NoSQL as one thing leads to bad decisions.

A better way to think about it is families. There are four main ones. Each solves a different problem, and each has a clear best fit.

3.1 Document Stores

Document stores keep data as JSON-like documents. MongoDB is the best known. Couchbase and Amazon DocumentDB belong here too.

A single document holds everything about one entity. An order document can contain the line items, the shipping address, and the payment status, all nested inside. No join needed to read it back.

{
  "_id": "ord_88213",
  "customerId": "cus_4471",
  "status": "SHIPPED",
  "items": [
    { "sku": "JH-1001", "name": "Java Handbook", "qty": 1, "price": 39.00 },
    { "sku": "JH-2044", "name": "Spring Guide",  "qty": 2, "price": 24.50 }
  ],
  "shipping": {
    "city": "Hyderabad",
    "pincode": "500081"
  },
  "createdAt": "2026-03-11T09:42:00Z"
}

Notice what happened there. In a relational design, that is three or four tables. Here it is one read. For a screen that always shows the whole order together, that is a genuine win.

3.2 Key-Value Stores

Key-value stores are the simplest family. You give a key, you get a value. Redis and DynamoDB in its simplest mode both fit here.

They are blazing fast because they do almost nothing. No query planner, no joins, no schema checks. Just a hash lookup. Redis often answers in well under a millisecond.

The trade-off is obvious. You cannot ask questions like “give me all sessions created today” unless you built an index yourself. Access is by key, and only by key.

3.3 Wide-Column Stores

Wide-column stores like Cassandra and HBase look like tables from a distance. Up close they are very different. Rows can have different columns, and data is partitioned by a key you choose up front.

These databases are built for enormous write volume. Cassandra can absorb hundreds of thousands of writes per second across a cluster. Time-series data, event logs, and IoT readings suit them perfectly.

The catch is that you design the table around the query. You decide the access pattern first, then model the data to match. Change your mind later and you often rewrite the whole table.

3.4 Graph Databases

Graph databases store nodes and the relationships between them. Neo4j is the common choice. Relationships are first-class citizens, not an afterthought.

Try asking a SQL database for friends-of-friends-of-friends. That is three self-joins, and it gets slower with every hop. A graph database walks the edges directly, so depth barely costs extra.

Fraud detection, recommendation engines, and social graphs all lean on this. If your core question is about connections rather than records, a graph store earns its place.

The Four Families at a Glance

Family Examples Best For Weak Spot
Document MongoDB, Couchbase Nested entities read as a unit Cross-document joins
Key-Value Redis, DynamoDB Caching, sessions, counters Queries by anything but the key
Wide-Column Cassandra, HBase Huge write volume, time-series Ad-hoc queries
Graph Neo4j, JanusGraph Deep relationship traversal Bulk analytics, sheer scale

Keep this table in mind when someone says “let us use NoSQL”. Your first question should be which family, and why. That single question filters out most bad choices.

4. Schema: Rigid Versus Flexible

Schema is the first place the two worlds visibly split. And it is where most of the emotional arguments happen.

4.1 Schema-on-Write

SQL databases check the shape on write. You define the shape first. Every insert must match it. Send a string where a number belongs and the write fails right away.

That strictness is a safety net. Bad data never enters the system, so you never write cleanup scripts for it later. Your reports can trust every row.

The cost shows up when needs change. Suppose product adds an optional loyalty tier to customers. In Postgres you write a migration, test it, and run it against production. On a big table that is real planning work.

4.2 Schema-on-Read

Document stores flip it around. You write whatever shape you like. Your code makes sense of it when reading. That is schema-on-read.

Adding that loyalty tier now takes zero migrations. New documents carry the field. Old documents simply do not have it. Your code handles the missing case and moves on.

This feels wonderful early on. It also quietly moves the problem into your codebase. Now every reader must handle every historical shape, forever.

// The hidden cost of schema-on-read.
// This document was written before loyaltyTier existed.
public String describeTier(Document doc) {
 
    String tier = doc.getString("loyaltyTier");
 
    // Old documents return null here. Every single reader
    // in every single service has to remember this.
    if (tier == null) {
        return "STANDARD";
    }
    return tier;
}

Multiply that by fifty fields and five years of changes. The schema did not disappear. It just moved from the database into scattered if-statements that nobody documented.

▶ Interview insight
A strong interview answer is that schema-on-read does not remove the schema, it relocates it. The database stops enforcing shape, so your application must. That is fine for one team and one service. It gets dangerous when many services read the same collection with different assumptions.

4.3 Which One Fits Your Data

Ask one question about your entities. Do they all look the same, or does each one differ?

  • Bank accounts, invoices, and payroll records look identical row to row. Relational fits naturally.
  • Product catalogues differ wildly. A laptop has RAM and screen size. A shirt has fabric and collar type. Documents fit better.
  • Audit logs and events come in many shapes and are rarely updated. Documents or wide-column both work.
  • User profiles sit in the middle. Start relational, and use a JSON column for the messy optional bits.

That last point deserves attention. Modern Postgres supports JSONB columns with indexing. So you can keep the strict core relational and let one column be flexible. Many teams reach for MongoDB when a JSONB column would have solved it.

5. Scaling: The Real Dividing Line

Schema arguments are loud, but scaling is what actually decides most architectures. This is where the trade-offs get sharp.

5.1 How SQL Scales

SQL databases scale up first. You buy a bigger machine with more cores, more RAM, and faster disks. This works remarkably well, and it works for longer than most people expect.

A well-tuned Postgres instance on decent hardware handles tens of thousands of transactions per second. Most applications never come close. So the scaling panic is often premature.

For reads, you add replicas. The primary handles writes and streams changes to several read-only copies. Your application sends reports and dashboards to the replicas, freeing the primary.

# Spring Boot pointing reads at a replica and writes at the primary.
spring:
  datasource:
    primary:
      url: jdbc:postgresql://db-primary:5432/shop
      username: app
    replica:
      url: jdbc:postgresql://db-replica:5432/shop
      username: app_readonly
 
# Replicas lag behind by milliseconds to seconds.
# So never read back a value you just wrote from a replica.

That comment matters. Replication is asynchronous by default. A user updates their profile, gets redirected, and the replica has not caught up yet. They see stale data and file a bug. This bites almost every team once.

5.2 Why Write Scaling Is Hard

Replicas solve reads, not writes. Every write still funnels through one primary. When that machine runs out, you must shard.

Sharding means splitting rows across machines by some key. Customers A to M on one server, N to Z on another. It works, but it breaks two things you relied on.

  • Joins across shards need your code to fetch from both and stitch results together.
  • Transactions across shards need two-phase commit, which is slow and fragile.
  • Rebalancing when you add a shard means moving huge volumes of live data.
  • A bad shard key creates hot spots, where one machine takes most of the traffic.

Notice how the promises you paid for start slipping away. Once you shard a SQL database manually, you have given up much of what made it attractive.

5.3 How NoSQL Scales

NoSQL databases were designed for horizontal scaling from day one. Sharding is not an add-on. It is the default behaviour.

In Cassandra, you pick a partition key and the cluster distributes rows on its own. Add a node, and the cluster rebalances itself. In MongoDB, you enable sharding on a collection and choose a shard key.

This works because those databases dropped the hard parts. No cross-shard joins, because there are no joins. No cross-shard transactions in the classic sense, because the model discourages them. Give up the promise and spread across nodes becomes straightforward.

Dimension Relational NoSQL
Primary scaling path Bigger machine, then replicas More machines from the start
Write scaling Single primary, sharding is manual Built-in sharding
Joins Native and optimised Usually done in app code
Adding capacity Planned, often disruptive Add a node, cluster rebalances
Ceiling High, but real Very high in practice

Read that table as a trade, not a scoreboard. You are exchanging query power for spread across nodes. Whether that trade is good depends entirely on your access patterns.

6. Consistency: ACID, BASE, and CAP

Now we reach the concept that gets got wrong the most in interviews. Let us keep it plain and practical.

6.1 The CAP Theorem Without the Mystique

CAP says a distributed system faces three properties: consistency, availability, and partition tolerance. When a network split happens, you must choose between consistency and availability. You cannot keep both.

Partition tolerance is not optional in the real world. Networks fail. Cables get cut. So the actual choice is the other two.

  • Choose consistency and the system refuses some requests during a partition. Correct answers or no answer.
  • Choose availability and the system keeps answering, but different nodes may disagree for a while.
  • Banking ledgers usually pick consistency. A wrong balance is worse than an error page.
  • Shopping carts and social feeds usually pick availability. A slightly stale like count harms nobody.

One important correction. CAP only applies while a partition is happening. During normal operation you can have strong consistency and high availability together. People forget this and treat CAP as a forever tax.

6.2 BASE: The Other Acronym

Many NoSQL systems follow BASE instead of ACID. It stands for Basically Available, Soft state, Eventually the same. The name is a chemistry joke, and the idea is genuinely useful.

Eventual consistency means writes spread over time. Read right away after a write and you might see the old value. Wait a moment and every node agrees.

That sounds scary until you look at real products. When you post a comment, some friends see it a second later than others. Nobody notices, and nobody is harmed. Eventual consistency is fine for a huge share of features.

// A pattern that survives eventual consistency.
// Instead of "set the count to 91", we say "add one".
// Repeating an increment is safer than repeating a set,
// because concurrent writers do not overwrite each other.
 
@Service
public class ViewCounterService {
 
    private final StringRedisTemplate redis;
 
    public ViewCounterService(StringRedisTemplate redis) {
        this.redis = redis;
    }
 
    public long recordView(String articleId) {
        // Atomic increment on the server side.
        return redis.opsForValue().increment("views:" + articleId);
    }
}

Design like this and eventual consistency stops being a problem. The trick is choosing operations that commute, so order does not change the result.

▶ Interview insight
A common trap is claiming MongoDB has no transactions. That has been false since version 4.0, which added multi-document ACID transactions. The honest answer is that they exist but carry a performance cost, and the document model is designed so you rarely need them. Knowing the nuance separates a rehearsed answer from real experience.

6.3 Consistency in Practice

Ask what a stale read actually costs your users. That single question resolves most consistency debates.

  • Account balance shown before a withdrawal: stale data causes real financial harm. Go strong.
  • Number of likes on a post: stale for two seconds harms nobody. Go eventual.
  • Inventory count on a checkout page: stale data means oversold stock. Go strong, or reserve stock explicitly.
  • Search results and recommendations: rebuilt on a schedule anyway. Eventual is perfectly fine.

Notice how few features truly need strong consistency. Teams often demand it everywhere out of habit, then pay for node-to-node chatter they never needed.

7. Concrete Examples: Picking a Database

Theory only goes so far. Let us walk through five real scenarios. For each one, we state the needs, make a pick, and explain the reasoning honestly.

7.1 An E-Commerce Order and Payment System

What we need: customers place orders, pay, and get refunds. Finance runs monthly reports. Stock must never go negative.

Pick: relational, without much doubt. Postgres or MySQL both work.

The reasoning is money. Payment, stock deduction, and order creation must succeed or fail together. That is textbook atomicity, and a relational transaction handles it in one annotation.

Reporting seals it. Finance will ask for revenue by region by month by product category. Those are joins and aggregates, which SQL was literally built for. Doing the same in a document store means either duplicating data everywhere or writing aggregation pipelines nobody enjoys looking after.

@Entity
@Table(name = "orders")
public class Order {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id", nullable = false)
    private Customer customer;
 
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> items = new ArrayList<>();
 
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
 
    private BigDecimal totalAmount;
    private Instant createdAt;
 
    // getters and setters omitted
}

One caveat worth stating. The product catalogue inside the same shop may not fit relational neatly, because product attributes vary by category. That is a fine place to use a JSONB column, or a separate document store. Same application, two different fits.

7.2 A User Session and Cache Layer

What we need: store login sessions and cached lookups. Reads happen on every request. Data expires after 30 minutes. Losing it is annoying, not fatal.

Pick: Redis, a key-value store.

Access is always by session id. There are no queries, no joins, and no reporting. A hash lookup is the entire access pattern, so the simplest tool wins.

Expiry seals the choice. Redis has time-to-live built in, so old sessions clean themselves up. Doing the same in Postgres means a scheduled delete job and a growing table in between runs.

// Externalising sessions so any instance can serve any request.
// This is what makes horizontal scaling actually work.
 
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
 
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory(
            new RedisStandaloneConfiguration("redis-host", 6379)
        );
    }
}

Notice the wider benefit. Once sessions live in Redis, your Spring Boot instances become stateless. You can add and remove instances behind a load balancer freely, because no user is tied to one machine.

7.3 An IoT Sensor Ingestion Pipeline

What we need: 50,000 devices each send a reading every 10 seconds. That is roughly 5,000 writes per second, all day. Queries always ask for one device over one time range. Data is never updated.

Pick: Cassandra, a wide-column store.

The write volume is the deciding factor. Cassandra was designed for exactly this shape: heavy append-only writes spread across many nodes. A single relational primary would struggle and need sharding within months.

The query pattern helps too. You always know the device id, and you want a time slice. Model the partition key as the device and the clustering key as the timestamp, and every query hits one partition.

CREATE TABLE sensor_readings (
    device_id   TEXT,
    reading_ts  TIMESTAMP,
    temperature DOUBLE,
    humidity    DOUBLE,
    PRIMARY KEY ((device_id), reading_ts)
) WITH CLUSTERING ORDER BY (reading_ts DESC);
 
-- Fast: hits exactly one partition on one node.
SELECT * FROM sensor_readings
WHERE device_id = 'dev-7741'
  AND reading_ts > '2026-07-01';
 
-- Slow and a bad idea: no partition key, scans the cluster.
-- SELECT * FROM sensor_readings WHERE temperature > 40;

That last commented query shows the trade clearly. Cassandra gives you huge write speed. In return, you must know your queries in advance. Ask an unplanned question and the answer is painful.

7.4 A Content Management System

What we need: articles, videos, podcasts, and courses. Each type has different fields. Editors add new content types every few months. Traffic is read-heavy.

Pick: MongoDB, a document store.

The shapes genuinely differ here. An article has a word count and a body. A podcast has a duration and an audio URL. A course has modules and lessons nested inside. Forcing all of that into one table means dozens of mostly-null columns.

Read patterns agree. A content page loads one item with everything attached. That is a single document fetch rather than five joins. And when editors invent a new content type, nobody runs a migration.

@Document(collection = "content")
public class ContentItem {
 
    @Id
    private String id;
 
    private String type;    // ARTICLE, PODCAST, COURSE
    private String title;
    private String slug;
 
    // Type-specific fields live here without schema changes.
    private Map<String, Object> attributes = new HashMap<>();
 
    private List<String> tags = new ArrayList<>();
    private Instant publishedAt;
 
    // getters and setters omitted
}

Be honest about the cost though. That attributes map is untyped, so your IDE cannot help you. Some teams keep typed subclasses instead and let Spring Data map them. Either way, the validation you skipped in the database has to live somewhere in the code.

7.5 A Fraud Detection Service

What we need: find accounts connected through shared devices, addresses, or payment cards. Suspicious rings are often four or five hops deep.

Pick: Neo4j, a graph database.

Depth is the whole problem. In SQL, each hop is another self-join. Four hops across millions of accounts produces a query that times out, no matter how you index it.

A graph database stores each relationship as a direct pointer. Walking five hops costs roughly five pointer follows per path, not five joins over huge tables. The query stays readable too.

// Find accounts within 4 hops of a flagged account
// through any shared device, card, or address.
MATCH path = (suspect:Account {id: 'acc_9912'})
             -[:SHARES_DEVICE|SHARES_CARD|SHARES_ADDRESS*1..4]-
             (linked:Account)
WHERE linked.id <> suspect.id
RETURN linked.id, length(path) AS hops
ORDER BY hops
LIMIT 50;

Try writing that in SQL and you will understand the appeal. The graph version reads almost like the requirement itself.

The Five Picks Summarised

Scenario Pick Deciding Factor
Orders and payments PostgreSQL Multi-step transactions and reporting joins
Sessions and cache Redis Key-only access with automatic expiry
IoT sensor ingestion Cassandra Very high write rate, known query shape
Content management MongoDB Varied shapes, whole-document reads
Fraud detection Neo4j Deep multi-hop relationship traversal

8. Polyglot Persistence: You Can Pick Both

Here is the part most articles skip. Real systems rarely choose one database. They use several, each for the job it does best. That approach is called polyglot persistence.

Picture a single e-commerce platform. It might run all of these at once:

  • PostgreSQL for orders, payments, and inventory, where correctness is a must
  • MongoDB for the product catalogue, where every category has different attributes
  • Redis for sessions, rate limits, and hot lookups
  • Elasticsearch for product search with typo tolerance and facets
  • Cassandra for clickstream events feeding the recommendation model

None of those choices contradicts the others. Each store handles the workload it suits. And the boring, careful SQL database still guards the money.

8.1 The Price of Polyglot

This freedom is not free, and pretending otherwise causes real damage. Every extra database adds work for your team.

  • More systems to monitor, back up, patch, and restore under pressure
  • More skill needed on the team, and more people on call
  • Data spread across stores, so keeping copies in sync becomes your problem
  • No single transaction can span two databases, so you need patterns like the outbox or Saga

So start small. One SQL database serves most products well past their first million users. Add a second store only when a specific workload clearly fights the first one.

▶ Interview insight
When asked how you would combine SQL and NoSQL safely, mention the transactional outbox pattern. You write the business change and an event row inside one relational transaction. A separate publisher reads that outbox table and pushes to Kafka or the other store. This keeps your write atomic without needing distributed transactions.

9. A Practical Decision Framework

Let us turn all of this into questions you can ask in a design meeting. Work through them in order and the answer usually appears.

9.1 Six Questions to Ask

  • Does a single user action change several records that must all succeed together? If yes, lean relational.
  • Do your entities share one stable shape, or does each one differ? Same shape leans relational, varied shape leans document.
  • What are your top five queries? If they all filter by one known key, key-value or wide-column fits.
  • How many writes per second do you honestly expect at peak? Under a few thousand, relational is comfortable.
  • What breaks if a user reads data two seconds stale? Money and stock break. Likes and feeds do not.
  • Will people ask unplanned analytical questions later? If yes, keep the data somewhere SQL can reach.

The fourth question deserves a second look. Teams routinely guess too high their write volume by two orders of magnitude. Do the back-of-the-envelope maths before choosing a database for scale you will never see.

9.2 Default to Relational, Then Justify Leaving

If you take one habit from this article, take this one. Start relational unless you have a concrete reason not to.

The reasoning is practical. Postgres gives you transactions, joins, constraints, JSON columns, full-text search, and mature tooling. It covers a huge range of needs in one system your team already understands.

Then let the pain justify the move. When a specific workload clearly fights the relational model, add the right store for that workload. That is a decision you can defend in a review, and later in an interview.

10. Common Mistakes and Pitfalls

These come up again and again in real projects. Watch for them in your own designs and in code reviews.

10.1 Choosing NoSQL for Scale You Do Not Have

This is the most common mistake by far. A team expects a thousand users and picks Cassandra because it scales. They then spend a year fighting query limitations they never needed to accept.

Run the numbers first. A single Postgres node handles far more than most products ever require. Solve the problem you have today.

10.2 Modelling Documents Like Tables

Teams often move to MongoDB and keep their relational habits. They create separate collections for orders, items, and addresses, then join them in app code.

That throws away the main benefit and keeps every drawback. If entities belong together, embed them. If you truly need joins everywhere, you probably wanted a SQL database.

10.3 Picking a Bad Shard or Partition Key

A key with low variety creates hot spots. Partition by country and India takes ninety percent of the traffic while other nodes idle.

Worse, changing the key later usually means rewriting the whole dataset. So spend real time on this choice up front.

10.4 Assuming Eventual Consistency Is Always Fine

Eventual consistency suits likes and feeds. It does not suit stock counts. Two customers can both buy the last item while nodes disagree.

Decide per feature, not per database. Some parts of one product genuinely need stronger promises than others.

10.5 Forgetting That Replicas Lag

Send a read to a replica right after a write and you may get the old value. Users see their own update missing and report it as a bug.

The usual fix is read-your-own-writes. Route a user to the primary for a short window after they write something.

10.6 Ignoring Operational Cost

Running five databases means five backup strategies and five upgrade paths. Small teams drown in that work.

Count the humans before counting the features. Every store you add needs someone who can fix it at 2am.

11. Key Terms Recap

Here is a short list of everything covered, so you can revise quickly before an interview.

Term Meaning in One Line
Normal form design Splitting data into tables so nothing is duplicated
ACID Atomic, consistent, isolated, durable transactions
BASE Basically available, soft state, eventually consistent
CAP theorem During a network split, pick consistency or availability
Schema-on-write The database enforces shape when data is inserted
Schema-on-read Your code interprets shape when data is read
Sharding Splitting rows across machines by a chosen key
Replica A read-only copy that trails the primary slightly
Partition key The field deciding which node stores a row
Polyglot persistence Using several databases, each for its best-fit workload
Outbox pattern Writing an event row in the same transaction as the business change

12. Interview Questions

Q: What is the main difference between SQL and NoSQL databases?

A: SQL databases store data in fixed tables and enforce the shape on write, which gives you joins, constraints, and ACID transactions. NoSQL databases relax those rules to spread data across many machines easily. The core trade is query power and correctness against effortless horizontal scaling.

Q: Is NoSQL faster than SQL?

A: Not by default. NoSQL is faster for the specific access pattern it was designed around, such as a key lookup in Redis or a partition scan in Cassandra. For a join-heavy report, a tuned PostgreSQL instance usually wins. Speed depends on matching the query shape to the store, not on the label.

Q: Does MongoDB support ACID transactions?

A: Yes. MongoDB has supported multi-document ACID transactions since version 4.0. They carry a performance cost, so the document model is designed so you rarely need them. Embedding related data in one document usually makes a transaction unnecessary in the first place.

Q: When should a Java developer choose PostgreSQL over MongoDB?

A: Choose PostgreSQL when several records must change together, when your entities share one stable shape, or when people will run unplanned analytical queries later. Orders, payments, and inventory all fit that description. PostgreSQL also offers JSONB columns, so you can keep a flexible field without leaving the relational model.

Q: What does the CAP theorem actually say?

A: During a network partition, a distributed system must choose between consistency and availability. It cannot keep both. Partition tolerance is not optional because networks really do fail. One point people miss is that CAP applies only while a partition is happening, not during normal operation.

Q: Can I use SQL and NoSQL together in one application?

A: Yes, and most large systems do. That approach is called polyglot persistence. A shop might keep orders in PostgreSQL, the catalogue in MongoDB, and sessions in Redis. Each extra store adds real operational work, so add one only when a specific workload clearly fights your main database.

Q: How do I keep writes atomic across two different databases?

A: Use the transactional outbox pattern. You write the business change and an event row inside one relational transaction. A separate publisher then reads that outbox table and pushes the event to Kafka or the other store. This avoids fragile distributed transactions while keeping your write atomic.

Q: What is a bad shard key, and why does it matter?

A: A bad shard key has low variety, so most traffic lands on one node while the others idle. Partitioning by country is a classic example. Changing the key later usually means rewriting the whole dataset, so this choice deserves real thought before you go live.

13. Conclusion

The SQL vs NoSQL trade-offs come down to one exchange. SQL databases give you correctness, joins, and flexible queries, in return for harder write scaling and rigid schemas. NoSQL databases give you effortless spread across nodes and shape freedom, in return for weaker promises and queries you must plan ahead.

So stop asking which is better. Ask what your data looks like, what your top queries are, how many writes you truly expect, and what a stale read would cost. Those four answers point at a database almost every time.

And remember that the choice is not forever or one-way. Start with the SQL database your team knows. Watch where it strains. Then add the special store that fixes that specific strain, and keep the rest boring.

Get that habit right and database selection stops being a religious argument. It becomes an engineering decision you can explain, defend, and revisit.

Further Reading

 

Leave a Comment