Skip to content

Secondary indexes across shards: local or global

How to find a row by a column that is not the shard key, such as an email, with a local index or a global index, and what each one costs.

  • Lesson 73 of 169
  • 19 min read
  • Depth
  • Free to read · No account needed
On this page
  1. Why interviewers ask this
  2. The idea in plain words
  3. Pause and predict
  4. Worked example
  5. Trade-offs
  6. Common misconceptions
  7. How to say it in the interview
  8. Check your understanding
  9. Answers
  10. Summary card: Secondary indexes across shards
  11. Sources

01Why interviewers ask this

After you shard a database (lesson 69), the interviewer asks: "The data is split by user id. How do you find a user by email?" This is a very common follow-up, because the shard key cannot be every column.

A weak answer is "add an index", as if the table were on one server. A strong answer says which shard the query goes to, and what each choice costs in reads, writes and delay. Then the interviewer asks the next question: "What if the user changes the email?" or "How do you keep emails unique?"

At SDE-1 level you should see that a query without the shard key must ask every shard. At SDE-2 level you should compare a local index and a global index with numbers. At SDE-3 level you should explain how the index stays up to date, and how a unique rule works across shards.

02The idea in plain words

A school keeps 20 registers, split by roll number. A parent says: "Find the child whose phone number is this one." The clerk must open all 20 registers. Or the school keeps one extra book that lists phone numbers in order, with the roll number next to each. The book is quick to read, but every new child must now be written in two places.

Here the shard key is the user id. A query by email does not contain it, so no one knows which shard holds the row. An index on email is a secondary index: an index on a column other than the shard key (lesson 36 explains indexes).

Ask every shard (scatter-gather, lesson 69). The query goes to all N shards, and the answer waits for the slowest one. That is N queries for one lookup.

How likely is it that one of them is slow? Say each shard is slow for a share p of its queries. Assumption: shards are slow independently of each other. One shard is not slow with chance 1 - p. All N shards are not slow with chance (1 - p) to the power N. So at least one is slow with chance 1 - (1 - p) to the power N.

Local index. Each shard indexes only its own rows. A write is cheap: one shard, one transaction (all changes together, or none). But a query without the shard key still goes to every shard. Each shard answers fast, but there are still N calls.

Global index. The index is itself split, by the indexed column. It is sharded like any table: hash the email to pick the index shard. The entry "email -> user id" lives on that index shard. A lookup goes to one index shard, which returns the user id. Then it goes to the data shard for the row: two calls.

The cost is on writes. A new user is written on the data shard and on the index shard, so one write touches two shards. A change of email costs more: delete the old entry, add the new one, and change the row. That is three writes on up to three shards.

Keeping a global index up to date. There are two ways.

  • In the same step. Both shards change in one transaction. The index is always right, but a transaction across two shards is slow and hard (lesson 69).
  • A moment later. The data shard saves first, and a background step updates the index. All copies end up the same after some time (eventual consistency, lesson 66). In the gap, a new user cannot be found by email yet, and a changed email can still be found by its old value. So check the row after you fetch it.

A real example. DynamoDB, a database from Amazon, offers both kinds by name. Its global secondary index is split by its own key and is updated in the background, usually within a fraction of a second. Its local secondary index is different. You can use it only together with the table's own key, so it does not solve this problem (see Sources).

A unique rule across shards. "Email must be unique" cannot be checked inside one shard. One place must decide. Use a lookup table: a small table with the email as its primary key and the user id as its value.

  1. Insert into the lookup table first. If the email exists, the insert fails, and you reject the sign-up.
  2. Then insert the user.
  3. If that second insert fails, remove the lookup row. A clean-up job removes rows that are left over.

This takes two steps with a repair, not one transaction.

Copy extra columns into the index. If the index entry also holds the name, a login lookup needs no second call. The cost is a live copy (lesson 35): when the name changes, two places change.

Adding an index to live data. Build it in the background, fill it in small batches, and switch the queries only when it is complete (lesson 41).

sql
-- Run in SQLite 3.50. In a real system this table is sharded by email.
CREATE TABLE email_lookup (
  email   TEXT PRIMARY KEY,   -- the unique rule lives here
  user_id INTEGER NOT NULL,
  name    TEXT                -- a copied column
);
INSERT INTO email_lookup VALUES ('asha@example.in', 1001, 'Asha');
INSERT INTO email_lookup VALUES ('asha@example.in', 2002, 'Asha K');
-- Error: UNIQUE constraint failed: email_lookup.email
SELECT user_id, name FROM email_lookup WHERE email = 'asha@example.in';
-- 1001 | Asha

A local index is only CREATE INDEX users_email_idx ON users(email); on every shard.

Want to check your understanding? Try explaining this idea in your own words. You can continue reading first, or ask the AI tutor a question. Sign up free to ask the tutor

03Pause and predict

Pause. Guess first.

The shard key is the user id and there are 20 shards. How many shard queries does one lookup by email cost with a local index, and with a global index?

Write your guess before you read on. The answer is in the worked example.

04Worked example

Assumptions (made up to teach with): 20 shards; 100 million users; 2,000 lookups by email per second. Each shard is slow for 1% of queries (p = 0.01), and shards are slow independently. There are 50 new users per second, and an index entry is 50 bytes.

  • Local index: each lookup is 20 shard queries. 2,000 x 20 = 40,000 shard queries per second. The chance that at least one shard is slow: 1 - 0.99 to the power 20 = 1 - 0.818 = 18.2% of lookups. If shards slow down together, for example after a bad release, the real chance is lower than 18.2%, but the work is still 20 calls.
  • Global index: each lookup is 1 index query and 1 row fetch = 2 queries. 2,000 x 2 = 4,000 queries per second, ten times fewer. This answers the predict box: 20 against 2. The slow chance is about 2 x 1% = 2%.
  • The write side: 50 new users per second cost 50 writes with a local index, and 50 x 2 = 100 writes with a global index.
  • Size: 100,000,000 x 50 bytes = 5,000,000,000 bytes = 5 GB of index, split over index shards.
  • With the name copied into the index, the 2,000 row fetches per second are not needed for a login.
Top: with a local index the lookup goes to every one of the 20 shards, drawn as 20 small boxes, so it costs 20 queries.
A local index asks 20 shards. A global index asks 2.
Top: with a local index the lookup goes to every one of the 20 shards, drawn as 20 small boxes, so it costs 20 queries. Bottom: with a global index the lookup goes to one index shard, which finds the user id, and then to one data shard, which holds the row. That costs 2 queries.

05Trade-offs

  • A local index solves cheap writes (50 per second, one shard each). It costs 20 calls for every lookup by email. I would change it when the lookup by email is frequent.
  • A global index solves the lookup (2 calls). It costs a second write for each change, and an index that can be a moment old. I would change back when the data changes very often and lookups are rare.
  • A global index on a column with few values (such as status) puts all its entries on a few index shards. Use it for columns with many values, such as email.
SituationChoice
The query has the shard keyNo secondary index. Go to the shard.
Lookups by email are rare, writes are manyLocal index
Lookups are hot (2,000 per second)Global index
The index may be one second oldGlobal index, updated in the background
The value must be uniqueLookup table: insert it first, then the row, with repair
The index must always be exactOne transaction across two shards: slow and hard, so avoid the need

06Common misconceptions

"I added an index on email, so the lookup is fast." A local index makes each shard fast, but the query still goes to 20 shards.

"A global index is always better, because it needs fewer calls." It needs fewer calls to read, but every write touches two shards. A change of email touches three.

"A global index is always up to date." If it is updated in the background, a new user may not be found for a short time.

07How to say it in the interview

Say it like this

A local index makes writes cheap, but a lookup by email asks every shard. A global index makes it two calls, but every write touches two shards and the index can be a little old.

08Check your understanding

  1. What is the difference between a local and a global secondary index?
  2. There are 50 shards, 1,000 lookups per second, and each shard is slow for 2% of queries, independently. How many shard queries per second does a local index cause? What share of lookups meets at least one slow shard?
  3. Sign-up must reject a second account with the same email, and the users are sharded by user id. How do you do it?

09Answers

Show the answers
  1. A local index covers the rows of one shard, so a lookup asks every shard. A global index is split by the indexed column. A lookup goes to one index shard and then one data shard. But a write touches two shards.
  2. 1,000 x 50 = 50,000 shard queries per second. 1 - 0.98 to the power 50 = 1 - 0.364 = 63.6%.
  3. Keep a lookup table with the email as the primary key. Insert there first. If it fails, reject the sign-up. Then insert the user, and remove the lookup row if that fails.

10Summary card: Secondary indexes across shards

One-page summary

  • A query without the shard key has to ask all N shards (scatter-gather).
  • Local index: each shard indexes its own rows. Cheap writes, but N calls per lookup.
  • Global index: split by the indexed column. 2 calls per lookup, but each write touches 2 shards, and a background update leaves it a moment old.
  • Chance that at least one of N shards is slow = 1 - (1 - p) to the power N. This holds if shards are slow independently (20 shards, 1%: 18.2%).
  • Unique across shards: one lookup table with a primary key. Insert it first, then the row, and repair if the second step fails.
  • Copy columns into the index to skip the second call. Build new indexes in the background.
  • Trap: an index on every shard does not remove the N calls.

11Sources

Try what you just learned

Put this idea into practice with a short exercise or a relevant tool. Free to try. No sign-up needed.

More practice. This needs a free account.

Continue learning with the lessons that build on this one.

Practise this idea

Have a question about this lesson?

Ask the AI tutor to explain a difficult idea, walk through an example or check your understanding. It uses the lesson to help you work through the topic.

A free account includes 10 AI tutor questions a day. The lesson remains free to read without an account.

All lessons in Module 10: Copies and splits of data

Prefer another topic? Browse all 169 lessons.