Real-time reconciliation for a payments ledger moving ₹40 crore a day.
Kettle’s operations team reconciled settlement files by hand every morning. Three analysts, two hours, a spreadsheet each — and a 90-minute window between the bank file landing and the first customer asking where their money was.
The existing dashboard rendered every one of the day’s 180,000 rows into the DOM on load. Largest Contentful Paint sat at 5.8 seconds on the office laptops, and the tab reliably crashed at month end.
I moved the match itself into the database. A pair of ClickHouse materialised views does the join the browser used to do, and the app fetches a page of already-reconciled rows instead of the raw ledger.
The front end became a React Server Component tree with a single client island: the exception drawer, which is the only part anyone interacts with. Filters live in the URL, so a shared link reproduces exactly what the analyst was looking at — which turned out to matter more than any feature we planned.
Everything streams. The summary bar resolves first, the table streams in beneath it, and the audit trail loads only when a row is opened.
LCP fell from 5.8s to 3.4s — a 42% cut — and the month-end crash stopped happening, because the browser never holds more than fifty rows.
Reconciliation is now one analyst for twenty minutes. The other two moved onto dispute handling, which is work that actually needs a human.
5.8s → 3.4s
Largest Contentful Paint
6 person-hours → 20 min
Daily reconciliation effort
180,000 → 50
Rows held in the DOM