The classic version of N plus one query shows up in ORMs: one query to fetch a list, then one more query per row to fetch each row’s related data.
This is a notorious problem that we have all seen in production :)
But the same pattern can exist in a loop that checks some shared state before doing each unit of work: a queue depth, a pool size, a counter, anything read once and then used to decide what to do next for every single item.
For example, imagine a worker pool processing jobs. Before taking each job, the dispatcher checks a shared store to see how many workers are currently busy. If it processes 1,000 jobs, that means 1,000 separate reads instead of checking the state once for the whole batch.
Now, if that state lives in something fast enough, like Redis, at roughly one millisecond per read, the cost can stay hidden in the overall system. A thousand jobs means a thousand extra milliseconds, which might not look significant next to everything else happening.
The problem becomes much more visible when the store underneath gets swapped for something slower, even by a few milliseconds.
A read that goes from 1ms to 5ms sounds trivial, but if the dispatcher pays that read once per job and runs it thousands of times, the same workload that used to spend 10 seconds on those reads now spends 50 seconds.
The store did not get catastrophically slower; the query just moved to a slower database, and it just uncovered this massive lapse.
The fix is simple: batch the state check across a fixed number of jobs instead of doing it once per job. Classic the batch-whenever-possible principle.
So, in a gist, a fast enough store can hide a bad access pattern. Something like Redis can make a per-item remote read look almost free, and that design flaw suddenly becomes very visible when you migrate to a slower database.