Definition · Secondary indexA secondary index maps a non-key attribute to the rows carrying it. In a partitioned store it can be local — each shard indexes only its own rows — or global, partitioned by the indexed attribute so that all entries for one value live together. The primary key's own index is neither; it is the partitioning function.
The two designs are the same data structure placed differently, and the placement decides which operation pays. What makes the comparison tractable is that both costs can be measured in the same unit — shard operations a second, meaning the total number of per-shard requests the fleet performs — so a read that fans out to sixteen shards counts as sixteen.
r = indexed reads a second w = writes a second N = shards
local r · N + w every read asks all N, every write is local
global r + 2w every read is routed, every write is doubled
r = 5 000 /s, w = 2 000 /s, N = 16
local 5 000 × 16 + 2 000 = 80 000 + 2 000 = 82 000 ops/s
global 5 000 + 2 × 2 000 = 5 000 + 4 000 = 9 000 ops/s
ratio 82 000/9 000 = 9.11× the global index wins here
Where they cross
r·N + w = r + 2w
r·N − r = 2w − w
r(N − 1) = w
r/w = 1/(N − 1)
N crossover r/w as a decimal cheaper at r/w = 2.5
2 1/1 1.0000 global
4 1/3 0.3333 global
8 1/7 0.1429 global
16 1/15 0.0667 global
32 1/31 0.0323 global
64 1/63 0.0159 global
Read the middle column down and the shape of the trade is obvious. At two shards the two designs meet at equal reads and writes, which is a genuinely balanced decision. By sixty-four shards they meet at one read per sixty-three writes, so any workload that reads the indexed attribute at all is already past the crossover. The local index is a design for small fleets and write-dominated workloads, and the arithmetic says so without any appeal to taste.
Example · A workload on the other side of the lineTake an audit log partitioned by record id, indexed on a status field, with w = 10 000 /s of writes and r = 200 /s of status lookups across N = 4 shards. The local index costs 200 × 4 + 10 000 = 10 800 ops/s and the global costs 200 + 20 000 = 20 200 — the local index is cheaper by nearly two to one. The ratio is r/w = 0.02, comfortably below the crossover of 1/3, and the same two formulas that chose the global index in the lab choose the local one here.
What the ratio does not capture
Three things, and all of them push the same way. A local index read is a scatter-gather, so it also carries the tail of “Scatter-Gather Cost” — N requests and a wait for the slowest — while a global index read is one routed request with one shard's latency. A global index write touches two shards, so unless it is done in a transaction it is briefly inconsistent with the row, and doing it in a transaction is the cross-shard commit of the next lesson. And the global index has its own hot-key problem: all entries for one attribute value live on one shard, so a heavily skewed attribute reproduces “Hot Keys and Salting” inside the index.
So r·N + w against r + 2w is the first cut and not the whole decision. It is worth doing first anyway, because it is arithmetic on two numbers you already have, and when it comes out at nine to one the qualitative arguments are unlikely to reverse it.