SwadhyAI Notes

All System Design notes

System Design Data

Data Partitioning

An Uber rider taps request and the matching service must find nearby drivers without scanning the entire planet. Instagram must open one user's timeline without reading every post on earth. Both problems are the same shape: so each query touches a narrow slice.

Warehouses and OLTP engines automate splits differently, but the trade-offs: range vs hash vs composite, hot shards, and index fan-out: recur everywhere at scale.

Key decision path

1

Name the hottest query

What field almost always appears?

2

Check cardinality

Enough distinct values to spread load?

3

Celebrity tenants need salts or queues.

4

Pick range, hash, or composite

Match scan needs to placement.

What actually happens

You choose a . Rows with the same key land together. Reads that include that key stay local; reads that omit it scatter across shards or need a secondary index.

keeps ordered neighbors together. spreads keys evenly but loses global order. A often wins for timelines: hash or route by entity, then sort by time inside the shard.

Growth is not only more disks. You plan partition size, per-shard QPS, rebalance workflows, and what happens when one tenant becomes a .

Try it: see partition skew appear

Pick what you hash on, then replay a traffic mix with an optional celebrity spike. Shard bars show placement load, not storage bytes.

Requests routed
120
Hottest shard QPS share
14%
Skew ratio (max/min)
1.2
Idlest shard count
3

Shard 0

15 requests in this sample

Shard 1

15 requests in this sample

Shard 2

15 requests in this sample

Shard 3

14 requests in this sample

Shard 4

14 requests in this sample

Shard 5

14 requests in this sample

Shard 6

16 requests in this sample

Shard 7

17 requests in this sample

Partition keys should follow the hottest query filter. When one value dominates traffic, expect salts, dedicated queues, or async fan-out rather than pretending hash magic fixes skew.

Hash by user spreads a mixed tenant mix evenly. Partition by tenant alone concentrates a viral tenant on one shard until you salt or queue hot keys.

Start balanced. Use hash-by-user without the burst. Skew ratio stays near one because keys are numerous and evenly distributed.

Switch to tenant key. Enable the celebrity burst. One bar grows while siblings idle, mimicking a hot partition on a shared database or sharded cache.

Add salting. The same burst spreads across four salted buckets. You traded scatter-gather on reads for even write load, a common compromise for mega-tenants.

How to choose a partition key

1. Does the hottest query include this key? If not, expect scatter-gather or add an index. Start from access patterns, not from a random column.

2. Is cardinality high and traffic even? Low-cardinality keys create hotspots. Large tenants may still need salting even when the key looks "correct."

3. Do you need ordered scans? Prefer composite keys for per-entity ranges. Prefer hash when even write spread matters more than cross-key order.

Simple rule of thumb

Choose high-cardinality keys that appear in common reads and writes. Keep related mutations on one partition when you can. Prefer a or co-location over chatty cross-partition transactions unless money or correctness forces stronger coordination.

Partition strategies

Partitioning divides one logical dataset into smaller ownership or management units. It may happen inside one database machine, where partition pruning reduces scan work, or across machines, where it becomes sharding. Range partitioning keeps adjacent values together for scans; hash partitioning spreads distinct keys more evenly; a directory maps keys or tenants to explicitly chosen owners. The best choice depends on how queries locate their data.

Pick the partition key to match your hottest queries. Anything else pays scatter-gather cost or needs an alternate index.

Contiguous key intervals live together: friendly for scans ordered along that dimension (time slices, lexicographic entity IDs). Wide-column stores and some globally distributed SQL engines migrate contiguous directories as units.

Risk: append-only time keys concentrate writes on the leading partition unless salted or rotated.

partition = hash(key) mod N spreads keys pseudo-evenly. Many managed NoSQL systems derive partitions from hashes. Ordering across keys is lost unless you add a separate sorted component per partition.

Trade-off: range queries without the partition key fan out.

Hash (or route) by high-cardinality entity id, then sort inside the shard:PRIMARY KEY ((user_id), timestamp)style: balances spread while preserving efficient time-range reads per user.

Often the pragmatic default for per-tenant timelines.

Directory-based routing

A routing layer maps keys to shards explicitly: maximum flexibility for rebalancing without changing the hash function. Must itself be replicated for High Availability (HA).

Risk: metadata service becomes critical infrastructure; use versioned maps and cache invalidation carefully.

Beginner decision flow: does this key deserve to partition data?

Start from access patterns, not from a random column. A useful partition key appears in common reads/writes, has enough distinct values, and avoids one "celebrity" value dominating traffic.

Pick partition key

Does the hottest query include this key?

No

Yes

High cardinality and even traffic?

No

Yes

Need range scans in order?

Yes

No

Hot partitions and mitigation

Salting adds a secondary bucket to a hot logical key, distributing writes across several partitions. For a tenant producing 5,000 writes per second with a 2,000-per-partition safe capacity, three perfectly balanced buckets are a mathematical minimum, not comfortable reserve. Reads may now query all buckets and merge results. Select the salt count from measured skew and failure headroom, and preserve a way to determine the bucket for point lookups.

Skew comes from viral keys: a celebrity profile, a flash-sale SKU, a leaderboard artifact, not only from a naive partition key. Combine adaptive splitting with application tricks like write sharding and read aggregation.

When one logical shard absorbs disproportionate traffic, autoscaled tables may borrow capacity, isolate hot items, or split underlying partitions. Behavior differs by storage engine; instrument partition metrics early.

Hot partition math

If total writes are 100K/s and one tenant owns 5%, that tenant produces 5K writes/s. With a safe limit of 2K writes/s per partition, salt_count = ceil(5000 / 2000) = 3. Use at least three salted partitions, then remember reads may fan out to three unless you pre-aggregate.

Write sharding

Spread hot logical keys across suffix buckets (e.g. profile:id:{0..99}); aggregate on read. Great for writes, costlier for reads.

Adaptive capacity

Managed tables may borrow throughput or isolate partitions; behavior depends on the storage engine.

Split / merge

Automatic splits when size or queries per second (QPS) crosses thresholds; watch split storms.

Counter shards

Fan-out writes to likes:post:{id}:shard_n; total = sum of shards.

Celebrity profile

Read storm on one user id; cache, replicate reads, or materialize public profile snapshots.

Flash-sale SKU

Write storm on one item; queue reservations or shard counters with reconciliation.

Large tenant

One customer owns 5% of traffic; route tenant to multiple virtual shards.

Current timestamp

Append-only writes hit the newest range; add hash prefix or rotate buckets.

Partition sizing and growth math

Partition design is capacity planning: estimate bytes, writes, reads, and the cost to move data when a shard grows too large or too hot.

Too large or too hot?

No

Size

Traffic

Target partition size

Pick a practical target such as 10-50 GB for operational shards or 128-512 MB for analytical files. Smaller units rebalance faster but increase metadata.

Million-user timeline

1M users x 20 writes/day x 2 KB ≈ 40 GB/day. With 30-day hot retention, plan around 1.2 TB hot plus indexes and replicas.

Hot user or tenant

If one tenant produces 5% of traffic, cluster averages lie. Salt that tenant into 16-64 virtual partitions and merge results on read.

Rebalance window

Moving 500 GB at 100 MB/s takes about 1.4 hours before verification. Throttle below customer traffic headroom, not link maximum.

Sizing formula

partitions = ceil(total_hot_bytes / target_partition_bytes), then check per-partition QPS and top-key skew separately.

Capacity worksheet

logical_partitions = ceil(total_hot_bytes / target_partition_bytes)

qps_per_partition = total_qps / active_partitions

top_key_qps = total_qps x top_key_fraction

salt_count = ceil(top_key_qps / safe_qps_per_salt)

read_fanout = salt_count unless cache or pre-aggregation reduces it

1M-user hot retention example

1M users x 20 writes/day x 2 KB = 40 GB/day. For 30-day hot retention, that is 1.2 TB raw. With 3 replicas and 40% index overhead, plan about 1.2 TB x 3 x 1.4 = 5.04 TB of hot physical footprint.

Rebalancing workflow

Migration is a change in ownership while traffic continues. Copy the data, capture concurrent changes, verify the destination, and switch routing at a controlled point. The source must stop acting as an independent writer after handoff. Keep redirects or routing refreshes for stale clients, and track lag, copy bandwidth, checksums, and user latency. A failed migration should leave a clear authoritative owner, not two partially updated copies.

Moving a shard is not only copying bytes. You must keep writes correct, rebuild indexes, validate results, and flip the routing map safely.

1. Prepare destination

Create the destination shard and reserve write, read, and storage headroom.

2. Copy snapshot

Copy the selected range or logical shard while source continues serving traffic.

3. Catch up changes

Use dual-write or change-data-capture to apply writes that arrived during the copy.

4. Validate

Compare counts, checksums, index rows, and sampled reads.

5. Flip map

Publish a new versioned routing map and make clients refresh or respect redirects.

6. Drain source

Keep rollback data briefly, then remove old copies after the safety window.

Secondary index cost

Local indexes move with the partition. Global indexes duplicate writes and may lag, so repartitioning can require rebuilding dependent index shards and validating them separately.

Choosing partition keys under pressure

State the access pattern first, then the key. Interviewers are checking whether your choice reduces fan-out without creating a new hotspot.

User timeline

(user_id), timestamp

Most reads ask for one user over a time range; composite sort key keeps pagination local.

Orders by merchant

(merchant_id), created_at

Merchant dashboard scans stay narrow, but large merchants may need salting by day or order bucket.

Global log analytics

date/hour + randomized file prefix

Time pruning helps scans; randomized prefixes prevent all writers from hitting one object-store path.

Flash-sale inventory

sku alone is dangerous

One SKU can go viral; use reservation queues, counter shards, or single-writer coordination.

Say this when challenged

"I would verify cardinality and top-key traffic with production-like metrics, monitor per-partition p99/throughput, and keep a rebalancing path through virtual shards or a routing table."

Secondary indexes: local vs global

A local index organizes data already inside one partition, so a query still needs the partition key to route efficiently. A global index can answer a different access pattern across the dataset, but maintaining it creates additional writes and possibly a separate consistency boundary. If an order is partitioned by customer but looked up by seller, decide whether the seller index can lag or must update atomically with the order.

Local indexes stay collocated with the base partition: cheap writes, awkward fan-out if you omit the partition key on reads. Global indexes reorder data under new partition keys: faster alternate lookups, async propagation costs.

Indexed rows live beside primary rows on the same partition. Writes stay transactional with the base record. Omitting the table partition key in a query may scan many shards.

Local-index pattern: same partition key, alternate sort key; constraints often apply at creation time.

Materialized under independent partition keys: queries can target one shard when keyed correctly. Propagation is often asynchronous; budget duplicated throughput.

Global-index pattern: alternate partition key, added later in many designs after access patterns stabilize.

Access-pattern sketches

Orders by order_id

Same partition key ⇒ cheap reads inside one shard because alternate sort keys stay collocated.

“Shipped today” without scanning everything

Add a global index: partition status, sort order_date: targets one alternate shard fan-out.

No supporting index

Full table / shard scans: tail latency tracks your slowest partition.

Cross-partition work

Prefer designs where related mutations touch one partition. Distributed transactions exist where money demands them. They coordinate replicas across shards at latency cost.

Single-partition writes serialize cleanly. Cross-partition paths typically involve two-phase commit, consensus clocks, or compensating sagas.

Two-phase commit (illustrative)

  1. Prepare: coordinator asks each shard whether it can commit.
  2. Commit / abort: unanimous YES ⇒ commit everywhere; any NO ⇒ roll back.
  3. Risk: coordinator loss mid-flight can block participants until recovery. That is why apps favor sagas across services.

Saga pattern (microservices default)

Ordered local commits with explicit undo steps:

Orders→Payments→Inventory

If inventory fails → compensate payment and mark the order cancelled (each step idempotent).

Prefer co-location

  • Pack related aggregates under one partition key when queries naturally align.
  • Single-table and entity-centric models optimize for this.
  • Every cross-partition join or transaction is a bill you pay in latency and ops.

Globally ordered timestamps (concept)

Some systems bound clock uncertainty with hardware references so transactions pick globally meaningful commit timestamps: expensive infrastructure, powerful when cross-region serializability is non-negotiable.

Time-based and geo partitioning

Time partitions make retention cheap because an expired interval can be removed as a unit, and time filters can skip irrelevant partitions. The newest interval can become a write hotspot, so combine time with a tenant or hash bucket when needed. Geographic partitioning improves locality but requires an ownership rule for users who travel or records shared across regions. A query must include enough placement information to avoid scanning every region.

Time partitions prune scans and allow dropping whole intervals. Geo partitions pin rows near users or inside compliance boundaries.

Time windows

Logs and metrics benefit from RANGE partitions by month or day: queries over recent windows skip historical files entirely.

sql
CREATE TABLE events (
  id BIGINT,
  event_time TIMESTAMPTZ,
  data TEXT
) PARTITION BY RANGE (event_time);

CREATE TABLE events_2026_03 PARTITION OF events
  FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

SELECT * FROM events WHERE event_time > '2026-03-10';  -- prunes old partitions

DROP TABLE events_2025_01;  -- bulk retire

Geo placement

Pin EU tenants to EU replicas for latency and residency rules; some databases expose LIST PARTITION constraints tied to region columns.

sql
-- Illustrative geo constraint sketch:
ALTER TABLE users PARTITION BY LIST (region) (
  PARTITION eu VALUES IN ('eu-west', 'eu-central'),
  PARTITION us VALUES IN ('us-east', 'us-west')
);
-- Tie partitions to regional replica sets via zone configs

Fixed shards vs dynamic splits

Over-provision logical shards up front for painless moves, or let the store split heavy partitions automatically: each approach trades operational simplicity against prediction risk.

Fixed logical shards

Thousands of virtual shards mapped onto fewer nodes: add hardware by moving shard bundles without reshuffling every row (patterns in search and key-value systems).

Dynamic splitting

Regions split when byte size or throughput crosses thresholds; merges reclaim idle space. Operators monitor split storms during traffic spikes.

Directory moves

Some globally distributed SQL engines relocate contiguous key directories between Paxos groups for fine-grained load balancing.

Patterns at scale

Large social graphs often hash user ids into thousands of logical shards backed by pools of databases; ride platforms partition by city-day so localized queries stay narrow.

User-centric OLTP

shard = hash(user_id) % N; routing service maps logical shard → physical database.

Adaptive tables

Viral partitions borrow capacity or split: monitor per-partition metrics, not cluster averages only.

Geo-time analytics

Keys like (city_id, date) keep “rides in NYC today” on a narrow slice of data.

Degraded modes and rebalance drills

Partitioning spreads load until a hot key, split storm, or bad map update concentrates it again. Predefine how the product behaves while you rebalance.

Hot partition

Rate-limit that tenant/SKU, salt or queue writes, serve cached reads, and avoid global retries that amplify the hotspot.

Directory / map unavailable

Cached routing maps keep reads going briefly; fail closed on writes that need authoritative placement until the control plane recovers.

Split / move in progress

Dual-write or change-catchup must finish before cutover. Prefer read-repair and redirects over serving half-migrated ranges.

Cross-partition fan-out storm

Shed analytics and wide scans first; keep single-partition user paths. Cap fan-out concurrency per request.

Test and drill notes

  • Inject a 5% traffic tenant and verify salt_count and per-partition QPS ceilings hold.
  • Rebalance drill: move one shard under load with throttle; validate checksums before flipping the map.
  • Rule of thumb: state partition key, expected QPS/bytes per partition, and the hot-key mitigation before naming a database product.

Takeaway

Choose high-cardinality partition keys, model composite keys for per-entity timelines, plan mitigations for viral skew, add global secondary indexes (GSIs) or warehouse projections only where access patterns justify cost, geo-pin sensitive tenants, and monitor per-partition metrics, not just cluster averages.

Primary sources

Practice and further reading

Use these exercises to connect the lesson to a design decision. State your assumptions, trace what happens to one request, and explain the rule that must remain true even during a failure. That rule is the design's correctness invariant.

Focus for this lesson: Choose horizontal, vertical, range, hash, or directory partitioning with explicit skew thresholds, global-order/index cost, and split/merge recovery.

Explain the decision

When is range partitioning preferable to hashing?

Read the reasoning

When range scans and time pruning dominate and hotspots can be controlled; hashing balances writes but destroys natural range locality.

Apply it to a production scenario

Today’s time partition receives all writes while older partitions are idle. How do you spread load?

Read one approach

Add a hash suffix within the active time range, buffer/batch writes, split the hot range, or partition by a higher-cardinality ownership key.

Work through the numbers

A 9 TB table grows 300 GB/month; partitions should stay below 750 GB for maintenance. Estimate initial partitions.

Read the worked calculation

At least 12 today; plan additional partitions or automated splits for growth and keep per-partition replicas/headroom in the model.

Reason through a failure

What must directory-based partitioning survive?

Read the recovery reasoning

Directory outage or stale versions must not create two write owners; cache maps with versions and fence writes during ownership transfer.