The month-end bottleneck that wasn’t a database problem
Removed a 30-minute-to-two-hour month-end stall — and stopped a permanent premium-SKU database bill before it started.
Sales teams uploaded license inventory spreadsheets, all of them in the same few days at month end. Every row was one software license; all anyone wanted was a count per product.
The app was cookie-cutter: web front end, .NET middle tier, SQL back end, with the business logic exhaustively encoded as stored procedures. Uploads went through the middle tier, held whole in memory, then pushed to blob storage — so the progress bar finished and users still waited. Then every row of every workbook was bulk-inserted into one shared table and summarized in SQL.
I identified SQL as the bottleneck. The PM’s fix was to scale the database SKU up for month end and back down after — which in practice means copying the database, with the replication lag and cutover risk that brings.
Plan B was to leave it on the expensive SKU permanently, on the reasoning that it was Microsoft’s cloud and Microsoft’s project, so the cost wasn’t real. I had to explain that every project carries its own cloud budget. Neither plan touched the actual problem.
Upload straight to blob storage and take the middle tier out of the data path entirely. A new blob raises an event onto the service bus. A pool of background workers picks up the event and streams the workbook.
Because only the summary mattered, the workers tally each license as the row goes by and write a single summary record at the end. The raw rows never enter SQL, and the shared table disappears — the data was per-upload all along.
Why it worked: the load moved onto something horizontally scalable. When month end gets busy you add workers and take them away after — and SQL, now receiving one small insert per file, stopped being the constraint at all.