The list page takes nine seconds.
On your machine it took
a quarter of a second,
with twelve rows in the table.
There are four hundred rows
in production,
and there is nothing wrong
with any line you wrote.
Look at the loop.
You walk the orders.
For each one you read
the customer's name,
which is a field
on an object you already have.
It looks like a field.
It reads like a field.
It is a query,
issued one order at a time,
four hundred times,
each one leaving the process,
crossing a network,
waiting,
and coming back
holding a single row.
That is the entire bug.
Not slow code.
Slow code you cannot see,
because the language gave
a call to another machine
the same punctuation
as reading a value
out of memory.
Now watch what that costs.
It is invisible in review.
The diff is four clean lines
and they read beautifully.
It is invisible in testing,
because your fixtures are small,
and small is exactly
where this hides.
It is invisible in your metrics,
because every one of those queries
is genuinely fast.
Four hundred fast things
make a slow page,
and the database will report
that it is perfectly healthy
the whole time you are suffering.
So you go and look
in the wrong place.
You add an index.
You blame the database.
You give the machine more memory,
and the loop keeps its rhythm,
and nothing improves.
Make the boundary visible instead.
Before you write a function,
decide which lines in it
leave the process.
Anything that leaves
should look like it leaves:
a name that says fetch,
a parameter list,
a place to put a timeout.
Then pull what the loop needs
before the loop,
in one call,
and let the loop do arithmetic.
And count the queries in a test.
Not the milliseconds.
Milliseconds move with the weather.
A count is a fact,
and it breaks loudly on the morning
somebody puts a lookup
back inside the loop.
Run it once against real volume
before you call it done.
The bug was never the loop.
It was that you could not see
where your program stopped
and somebody else's started.
– Serguey Asael Shinder
Top comments (0)