Last time I left you with a promise: earned isn't paid. Somewhere between a driver's payment and an owner's bank account there has to be someone who can see every rupee in flight, and stop the ones that look wrong.
That someone is the admin. Today, the panel they work in, and the one number on it I couldn't get under a second.
The screen that has to be right
ParkEase's admin home is a single page: gross bookings, the ParkEase fee, what's owed to owners, GST payable, how many spaces are waiting for approval, and one word in large type: BALANCED. Or not.
Every money figure on that page is a read of the double-entry ledger. Not a cached total, not a column someone updates on the side. If the ledger and the dashboard ever disagree, the dashboard is the one that's wrong, and an operator about to approve a payout needs to know that before they click.
So the dashboard is honest by construction. It's also expensive: each load scans the ledger for the date range, checks every transaction balances, and counts the queues. On a ledger of 250,000 entries that's fine for one person.
I wanted to know what happens when it isn't one person.
The test that broke it
The admin stress suite fires 200 dashboard requests at the same moment against a 250k-entry ledger, through a connection pool of 5. That's deliberately unfair: it's what a double-clicking operator, a refreshing tab and a monitoring probe look like when they all land together, only worse.
The first run: p95 of 9.8 seconds.
The obvious fix is a cache. Compute the dashboard once, keep it for thirty seconds, hand everyone the same answer.
And that's exactly what I couldn't do. The balances screen, one click away, reads the same ledger live. With a cache, an admin could read one "owed to owners" figure on the dashboard and a different one on the balances page, at the same moment. For a screen whose whole job is trust the number, a stale number is worse than a slow one.
Same answer, computed once
Look at those 200 requests again. They aren't 200 different questions. They're the same question (this date range, right now) asked 200 times while the first answer is still being computed.
So instead of keeping answers, the dashboard shares questions. The first request for a range starts the computation and parks its promise in a map. Every identical request that arrives while it's running gets that same promise. The moment it settles, the entry is deleted:
private readonly inFlight = new Map<string, Promise<DashboardView>>();
constructor(@Inject(DB) private readonly db: Database) {}
read(from: string, to: string): Promise<DashboardView> {
const key = `${from}|${to}`;
const pending = this.inFlight.get(key);
if (pending !== undefined) return pending;
const fresh = this.compute(from, to).finally(() => this.inFlight.delete(key));
this.inFlight.set(key, fresh);
return fresh;
}
Nothing outlives the request. A dashboard loaded one second after the last one finished computes fresh, so it can never be staler than the balances screen read at the same instant. There's no TTL to tune and no invalidation to forget.
The money cards also stopped being their own query: they come from the same account totals the balances screen uses, mapped by a pure function, so the two screens physically can't disagree. And the queue counts became one statement instead of six, so a dashboard load holds three pooled connections instead of eight.
Same 200 requests, same 250k ledger, same pool of 5: p95 213 ms, zero errors. The test now holds it there:
it('200 concurrent dashboard reads over a 250k-entry ledger: p95 under 1 s, zero 5xx', async () => {
expect(percentile(sorted, 95)).toBeLessThan(1000);
What else is in the panel
Beyond the dashboard, the panel has approvals for new spaces and partners, a users screen with role grants, bookings with refunds, finance with the ledger and payouts, a live surge heat map over Bangalore, review moderation, and an audit log of every action an admin takes. Each one reads from the same ledger and the same API the app uses. There is no admin-only back door into the data.
What I'd tell myself before starting
When something is slow, ask whether the requests are different. A cache answers "how do I avoid recomputing?" Coalescing answers "why am I computing the same thing 200 times at once?" Only the second one kept the number honest.
Next: an admin panel is for the people running ParkEase. Tomorrow is for everyone else: the website a driver lands on before they've ever heard of us, and the one rule that stops it from promising a price the app won't charge.
ParkEase is a peer-to-peer parking marketplace for India, built solo.
Code: https://github.com/Deonkar/parkease ยท Music: ende.app (CC BY 4.0)

Top comments (0)