Speed · Security

The faster version that read before it asked

A screen was slow because it asked the database three questions in a row. The quick fix asks them all at once. It also fetched a venue’s data before checking whether you were allowed to see it.


One back-office screen was taking longer than our own budget allows. The reason was not a slow query. Every query was quick; they were simply waiting for each other.

Three steps, each one starting only after the last finished. Check who is asking and what they may see. Then fetch the venue. Then fetch everything the screen shows. For somebody in the UK talking to a database in North America, each wait is most of a tenth of a second before any work happens at all.

The obvious fix is to stop waiting. The third step does not need the second step's answer — it only needs the venue's identifier, which the first step already worked out. So fire them together and the screen gets a third of its time back.

The obvious fix was refused, correctly

We have a guard in our test suite that watches this particular screen for one thing: nothing about a venue may be fetched before the permission check has passed. The faster version failed it by name.

The temptation at that moment is to conclude the guard is out of date. The permission check had run — in the shared layer every request goes through, before this screen's own code starts. Fetching in parallel is safe because the caller was already vetted.

We changed the code instead, for a reason that is not theoretical. Earlier this month we found a route in our own system that leaned on exactly that assumption: the shared check runs, so this code does not need its own. It was true almost everywhere, and in one place the shared check looked in the wrong spot and the request walked straight past it. A single-venue account could have booked a courier against another venue's order.

"Somebody else already checked" is a fine thing to believe until the day it is wrong, and the day it is wrong nothing tells you.

What shipped

The screen now runs the permission check itself, first, using the answer the shared layer already worked out — same values, same function, no extra database trip. Only then does it fire the venue row and the screen's data together. Nothing is read before this code has checked, and the fetch still starts a step early.

Two waits instead of three, and the guard still holds.

The point for anyone running software in a venue

Performance work and permission work pull in opposite directions. Making a screen faster nearly always means doing things earlier, and doing things earlier means doing them before something has been confirmed.

That is not an argument against speed. It is an argument for having something that fails loudly when the two collide — and for treating that failure as information rather than an obstacle. Ours cost twenty minutes and kept a check that took a real incident to learn.


Try it on tonight’s service.

Nothing to install, no card. Not better by the weekend? Close the tab.