System Design
Intermediate 13 min read How to answer

Data modelling and choosing a database

In one line

List the entities and the exact queries the system runs, then pick the store and the keys that make those queries cheap at your scale.

What it is

Data modelling in an interview means three things: the main entities and their key fields, the access patterns (the queries and writes, with their frequency), and the store plus primary key, partition key and indexes that serve those patterns.

Access patterns come first. 'Get the last 50 messages of a chat, newest first' at 200,000 reads per second leads to a very different model than 'find all orders for a merchant between two dates with filters'. The first fits a wide-column store partitioned by chat_id; the second fits a relational database with a composite index.

Most real systems use more than one store: a relational database for the source of truth (orders, payments, users), a cache for hot reads, object storage for blobs, a search index for text search and maybe a wide-column or key-value store for very high write volume. The skill is putting each piece of data in the right place and keeping them in sync.

Why interviewers ask

The data model is where many designs succeed or fail. A wrong partition key creates hot partitions; a missing index turns a 5 ms query into a 5 second scan; a store chosen for buzzwords forces painful workarounds. Interviewers expect you to justify the store from access patterns and numbers, and to name the keys.

How it works

  1. 1List 3 to 6 core entities with key fields: User(id, phone, name), Order(id, user_id, restaurant_id, status, amount, created_at). Mark the IDs and foreign keys.
  2. 2Write the access patterns as queries with rough rates: 'get order by id: 5k/s', 'list a user's orders newest first: 2k/s', 'update order status: 1k/s'. These decide everything else.
  3. 3Default to a relational database (Postgres or MySQL) when you need transactions, constraints (unique seat per show), joins or flexible queries, and the write rate fits one primary or a few shards.
  4. 4Choose a wide-column or key-value store (Cassandra, ScyllaDB, DynamoDB) when writes are very high, queries are known and key-based, and you can model one table per query.
  5. 5Choose specialised stores for specialised queries: Elasticsearch or OpenSearch for full-text search, Redis for counters and leaderboards, a time-series or OLAP store for metrics and analytics, a vector index (pgvector or a vector database) for similarity search, object storage for files.
  6. 6Pick the partition key so load and data spread evenly and each query hits one partition: chat_id for messages, user_id for a user's orders. Avoid keys like date or country that concentrate traffic.
  7. 7Pick the sort or clustering key to match the read order: (chat_id, message_id DESC) gives newest messages first with no sorting at read time.
  8. 8Add secondary indexes only for real queries, and remember each one slows writes. In NoSQL, prefer a second denormalised table written alongside the first.
  9. 9Explain how derived copies stay in sync: change data capture or an outbox to update the search index and cache, and what staleness is acceptable.

Worked example · Modelling messages for a chat app

  1. Entities: User, Chat (1-on-1 or group), ChatMember, Message(chat_id, message_id, sender_id, body, sent_at).
  2. Access patterns: send message (120k/s average), load last 50 messages of a chat (300k/s), list a user's chats ordered by last activity (50k/s).
  3. Messages: Cassandra or ScyllaDB table with partition key chat_id and clustering key message_id descending. A time-sortable ID (Snowflake-style) gives order without a separate timestamp sort.
  4. Big groups: a chat with years of history becomes a huge partition. Use a composite partition key (chat_id, bucket) where bucket is a time window such as a month, and read the newest bucket first.
  5. User's chat list: a separate table user_chats partitioned by user_id, clustered by last_message_at, updated on each message. This is denormalisation: one write becomes several, but each read is a single partition.
  6. Users and chat membership: Postgres, because they change rarely, need uniqueness on phone number and are joined in admin tools.
  7. Media: object storage with the URL in the message body. Search across messages, if in scope: an Elasticsearch index fed from a Kafka stream.

Picture it

It is like organising a kitchen. You place things by how you cook, not alphabetically: salt near the stove, plates near the sink. A database is arranged the same way, around the queries you run most often.

Reference code

-- Cassandra (CQL): one table per access pattern
CREATE TABLE messages_by_chat (
  chat_id     bigint,
  bucket      int,        -- e.g. yyyymm, caps partition size
  message_id  bigint,     -- time-sortable (Snowflake-style)
  sender_id   bigint,
  body        text,
  PRIMARY KEY ((chat_id, bucket), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);

-- Latest 50 messages: a single-partition range read
SELECT * FROM messages_by_chat
 WHERE chat_id = ? AND bucket = ? LIMIT 50;

Key patterns

Query-first modelling

When: Using Cassandra, DynamoDB or other key-based stores

How: Design one table per query, with the partition key equal to the query's equality filter and the clustering key equal to its sort order. Accept duplicated data.

Polyglot persistence with one source of truth

When: Different queries need different stores

How: Keep one authoritative store, and feed caches, search indexes and analytics stores from it through CDC or an outbox. Never write the same fact independently to two stores from the API.

Time bucketing

When: Partitions grow forever (chat history, sensor data, logs)

How: Add a time bucket to the partition key, such as (device_id, day), to cap partition size and spread writes.

Real-world use

Discord messages

Discord moved message storage from MongoDB to Cassandra in 2017 and later to ScyllaDB, partitioning by channel and a time bucket so a busy channel's history is split across partitions.

Instagram on sharded Postgres

Instagram sharded Postgres into many logical shards and generated 64-bit IDs that embed a timestamp and the shard ID, so IDs sort by time and tell you where the row lives.

Amazon DynamoDB single-table design

AWS guidance for DynamoDB is to list access patterns first and design partition and sort keys, often in one table with overloaded keys, so each query is a single key lookup or range read.

Payments on relational databases

Payment systems such as those at Indian fintechs typically keep ledgers in relational databases like Postgres or MySQL, because double-entry ledgers need transactions, constraints and auditability more than extreme write throughput.

In a project

A quick-commerce app stores inventory per dark store in a single Postgres table. During evening peaks, the 'products available near me' query slows to 2 seconds and checkout sometimes oversells items.

  1. List the access patterns: browse available products for one store (very high reads), reserve stock at checkout (moderate writes, must not oversell), restock updates (low).
  2. Fix overselling first: reserve with a conditional update, UPDATE inventory SET qty = qty - 1 WHERE store_id = ? AND sku = ? AND qty >= 1, and check affected rows. This keeps correctness in Postgres.
  3. Add a composite primary key (store_id, sku) so browsing a store is an index range scan, not a table scan.
  4. Serve browsing from a Redis hash per store, updated from Postgres changes through change data capture; a few seconds of staleness is fine because checkout re-checks in Postgres.
  5. Partition the table by store_id ranges when it grows, since all queries include store_id.

Impact: Browse latency drops from 2 seconds to under 20 ms from Redis, overselling stops because the only authoritative decrement is a conditional update, and the database load at peak falls sharply.

Trade-offs

Use it when

  • Relational: transactions, uniqueness, joins, ad-hoc queries, money and inventory
  • Wide-column or key-value: very high write rates with known key-based queries
  • Document store: flexible per-item attributes such as product catalogues, read mostly by ID
  • Search engine: full-text, fuzzy and faceted search, as a derived copy
  • Redis: counters, sessions, leaderboards, rate limits and hot caches

Avoid it when

  • NoSQL just because the scale sounds big, when estimates fit one Postgres primary plus replicas
  • Relational with heavy cross-shard joins at very high write rates
  • Using a search index or cache as the source of truth
  • Adding an index for every column; each index slows writes and uses memory

Common mistakes

  • Choosing the database before listing access patterns.
  • Naming a store but not the partition key, sort key or indexes.
  • Partition keys that create hot spots, such as date, country or a celebrity user ID.
  • Unbounded partitions, for example all messages of a busy group in one partition forever.
  • Writing to the database and the search index separately from the API, so they drift apart on failure.
  • Storing images or videos in the database instead of object storage.
  • Saying 'NoSQL scales, SQL does not' instead of comparing on the actual needs.

Interview questions

Quick revision

  • Entities, then access patterns with rates, then store and keys.
  • Default: Postgres or MySQL until numbers or query shapes say otherwise.
  • Partition key = the equality filter of the hot query; spreads load evenly.
  • Sort or clustering key = the order you read in.
  • Cap partition size with time buckets.
  • In NoSQL, one table per query and denormalise.
  • One source of truth; caches and search indexes are derived through CDC or outbox.
  • Blobs go to object storage; store only the URL.
  • Every index speeds a read and slows every write.

Check yourself

1. What should you list before choosing a database?

2. Which partition key is most likely to create a hot partition for an orders table?

3. A seat booking system must never sell the same seat twice. Which store property matters most?

4. Why add a time bucket to a chat message partition key?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free