LearnastraTHE DESIGN COURSE

Understand the building blocks / CHAPTER 13

Database indexes

Choose indexes from query shapes and understand their storage and write costs.

Intermediate1h of deliberate study10 questions

Builds on System design building blocks, Back-of-the-envelope estimation.

By the end, you can
  • Explain how an index narrows data access.
  • Choose column order from filtering and sorting.
  • Evaluate selectivity and write overhead with query plans.

01Build another route to the same records

A support agent asks for the latest 50 open tickets belonging to customer organization 17. The database stores five million tickets across many organizations, also called tenants. Reading every ticket just to find those 50 would do unnecessary work.

An index gives the database another route to records:

  1. Save selected field values in an extra structure, together with a way to reach the corresponding record or its needed values.
  2. Search that smaller or better-organized structure for the requested group.
  3. Read the matching entries and fetch any remaining fields from the table.
  4. Maintain the extra structure when relevant records change.

An ordered structure called a B-tree is common. It organizes searchable key ranges into pages, so the database can locate a group and then walk entries in order rather than inspect every row.

Start with the actual screen, not the instruction “add an index.” This screen needs:

  • Tickets for one tenant.
  • Only open tickets within that tenant.
  • The most recently updated tickets first.
  • At most 50 results.

Word search, geographic search, and broad summaries can need different structures. One index is not a universal accelerator for every way of reading a table.

02Match filtering and ordering

Write the query before proposing a key:

SELECT id, updated_at, subject
FROM tickets
WHERE tenant_id = 17 AND status = 'open'
ORDER BY updated_at DESC
LIMIT 50;

Test a B-tree key on (tenant_id, status, updated_at). Its field order groups the data in a useful sequence:

  1. Locate entries for tenant 17.
  2. Within that tenant, locate the open-status group.
  3. Walk that group in the required time order, using the engine’s supported scan direction.
  4. Stop after enough visible matching records have been found, then fetch any fields not available from the index.

Putting time first, as in (updated_at, tenant_id, status), instead groups globally recent tickets. The database may examine many other tenants’ entries before finding this tenant’s 50 results. LIMIT 50 limits returned rows, not necessarily examined entries.

Consider two refinements only after the basic path is clear:

  • A partial index stores only records satisfying a stated filter, such as open tickets. The query must imply that filter, and the database must support this index type.
  • A covering index includes the fields needed for the screen. This can avoid separate table lookups, but some engines still inspect table information to check which row version the query may see. Including a large ticket body makes entries bigger and writes more expensive.

Do not turn key order into an absolute rule. Some engines can probe several leading-value groups, called a skip scan, or combine multiple indexes. Inspect the execution plan: the scans, lookups, and sorts the database actually chooses. Test it with representative data.

03Pay for the path and protect the right invariant

An index saves work only when its access path is cheaper than the alternatives. A filter that narrows five million rows to one ticket is highly selective. A filter matching most tickets is less selective; sequentially scanning the table may beat many scattered table lookups.

Estimate storage separately from read benefit:

  1. Assume an average of 32 bytes per index entry, purely for planning.
  2. Multiply 5000000 × 32 = 160000000 bytes, or 160 MB in decimal units.
  3. Add space for pages, tree structure, and other engine overhead. The 160 MB is not a final storage guarantee.
  4. Include maintenance work when inserts, deletes, or updates change indexed fields.

An index can also enforce a duplicate-key rule. Suppose an organization must not import the same external ticket identifier twice:

  1. Store a unique key on (tenant_id, external_ticket_id).
  2. Two concurrent imports may both try to insert the same pair.
  3. The database’s uniqueness enforcement permits at most one matching key and rejects or resolves the conflicting insertion according to the operation.

That rule does not allocate identifiers, authorize readers, or include an external notification in the database transaction.

Review the full cost:

  • Measure entries and rows visited, table lookups, and sorting for small and large tenants.
  • Measure write response time and index size after adding it.
  • Check the supported live-build procedure. Building an index reads and writes data while normal traffic continues.
  • Before removing an apparently unused index, check rare critical queries and any constraint it enforces.

Choose the index whose measured read benefit is worth its storage and write cost.

The ordered key narrows a ticket query

The ordered key narrows a ticket queryLocate tenant 17 and its open-status group. Walk the matching entries in update-time order. Fetch the remaining screen fields from ticket rows when needed. Measure actual rows visited, sorting, and index maintenance. Numbered arrows correspond to the connection key below. All connections are directional.Tenant 17, openLatest 50 by updatetimeB-tree keytenant → status →timeMatching orderedgroupStop after requiredresults when planpermitsTicket rowsFetch remainingscreen fieldsTicket updatesMaintain affectedindex entriesExecutionevidenceVisited rows, sorts,write latency12345The ordered key narrows a ticket queryLocate tenant 17 and its open-status group. Walk the matching entries in update-time order. Fetch the remaining screen fields from ticket rows when needed. Measure actual rows visited, sorting, and index maintenance. Numbered arrows correspond to the connection key below. All connections are directional.Tenant 17, openLatest 50 by updatetimeB-tree keytenant → status →timeMatching orderedgroupStop after requiredresults when planpermitsTicket rowsFetch remainingscreen fieldsTicket updatesMaintain affectedindex entriesExecutionevidenceVisited rows, sorts,write latency12345The ordered key narrows a ticket queryLocate tenant 17 and its open-status group. Walk the matching entries in update-time order. Fetch the remaining screen fields from ticket rows when needed. Measure actual rows visited, sorting, and index maintenance. Numbered arrows correspond to the connection key below. All connections are directional.Tenant 17, openLatest 50 by updatetimeB-tree keytenant → status →timeMatching orderedgroupStop after requiredresults when planpermitsTicket rowsFetch remainingscreen fieldsTicket updatesMaintain affectedindex entriesExecutionevidenceVisited rows, sorts,write latency12345

Follow the numbered arrows

  1. Tenant 17, open → B-tree keyfind tenant and status group
  2. B-tree key → Matching ordered groupwalk time order
  3. Matching ordered group → Ticket rowsfetch remaining display fields
  4. Ticket updates → B-tree keypay maintenance cost
  5. Tenant 17, open → Execution evidenceverify actual plan
The index groups one tenant’s open tickets in time order, so the database can locate that group before reading results. Ticket updates maintain the same index. Verify the saved read work and the added write work in the database’s actual plan and measurements.
Trace the diagram in words
  1. Locate tenant 17 and its open-status group.
  2. Walk the matching entries in update-time order.
  3. Fetch the remaining screen fields from ticket rows when needed.
  4. Measure actual rows visited, sorting, and index maintenance.

DRAW. CALCULATE. EXPLAIN.

Index a tenant’s ordered messages

20 minutes · guided practice

A table contains ten million messages. A client wants the next 50 messages in conversation c7 after sequence 800; c7 identifies the selected conversation.

  1. Propose an ordered index and describe the lookup steps.
  2. Name two costs and two useful measurements.
  3. Explain what a unique conversation-and-sequence key would and would not guarantee.
Your self-check
Compare the worked solution
  1. Try a B-tree on (conversation_id, sequence). Find the group for conversation c7, seek past sequence 800, and walk the next 50 entries in sequence order.
  2. Fetch display fields from table rows if the index does not contain them.
  3. Two costs are space for index entries and maintenance on relevant writes. Building the index also consumes resources.
  4. Measure entries and table rows visited, any sorting, write response time, and total index size. Test very large conversations as well as ordinary ones.
  5. A unique (conversation_id, sequence) rejects duplicate positions within a conversation. It does not choose or safely allocate the next sequence; that requires its own coordinated update.

The checkboxes record your own assessment. The worked solution gives you a way to check the reasoning.

RETRIEVE BEFORE YOU REVEAL

Keep the important ideas.

Say the answer aloud, then turn the card over.

CARD 01How do I choose an index?
  1. Write the filters, requested order, and result limit.
  2. Propose an index that groups and orders those records.
  3. Inspect the actual scans, table lookups, and sorting.
  4. Test large as well as ordinary tenants.

For open tickets, try tenant, then status, then update time.

CARD 02What is the cost?
  • Index entries occupy storage.
  • Relevant writes must maintain them.
  • Building them also consumes live resources.

Extra display fields may save table lookups, but make entries bigger and can increase write work.

CARD 03What does uniqueness provide?
  1. Put the required key pair under a database uniqueness rule.
  2. Let the database reject competing insertions of that same pair.

This prevents duplicate keys. It does not allocate the next ID, grant permission, or make an external notification part of the commit.

Practise this topic in the revision deck ↗

CHECK THE REASONING

Can you explain the choice?

Answer every question, then finish for an immediate score and an explanation for each answer. Aim for 80%, then revisit any gaps.

1 / 10The screen filters tenant, status and sorts time. Which candidate aligns naturally?
2 / 10A time-first index examines many other tenants before finding 50 matching rows. Why?
3 / 10Assume 5 million index entries average 32 bytes before overhead. Approximate entry bytes in decimal units?
4 / 10An index covers the screen except a large ticket body. Should the body automatically be included?
5 / 10Why might the optimizer choose a table scan despite an index?
6 / 10A tenant imports the same external ticket ID twice. Which index can enforce that key rule?
7 / 10What is a partial index?
8 / 10A service adds six indexes and write latency increases. What is a plausible mechanism?
9 / 10Can a non-leading composite-index column ever be useful?
10 / 10What best validates the ticket index?
0 of 10 answered

CLOSE THE LOOP

Explain it without the page.

Summarise the mechanism, one trade-off, and one failure mode. Then mark this chapter complete.

Go to the source