A twenty-year .NET monolith decomposed so document work, workflows, and tenant traffic could fail independently.

Vivek Chandramohan
Published 12 September 2026

A B2B document platform had carried the business for twenty years as a single ASP.NET monolith: intake, workflows, and document processing in one process, one SQL Server, one IIS pool. It was solid. Then it had to house forty clients. Friday-night PDF batches deadlocked the database and took every tenant’s portal down with them. We, at Futluz, split the monolith into services so document work, workflow, and interactive traffic could fail independently—and the platform could hold 99.9% uptime with more than one neighbour on the box.
Problem: ASP.NET Framework 4.8 monolith, 20 years in production, one SQL database, document conversion and age-old workflows in the same w3wp process. Deadlocks under batch load. Multi-tenant, shared fate.
Approach: Strangler extraction, not a rewrite. Document processing and workflow left the web process first, onto queues and worker services. Tenant-aware ASP.NET Core APIs next. The monolith shrank until it was a BFF, then it was retired.
Scale & results: Forty enterprise clients, ~2.1 million documents a month. Fourteen months. Portal remaining up while batches ran. Measured availability from 99.1% to 99.94% over two consecutive quarters. A noisy tenant no longer took the estate down.
Why it worked: The monolith had earned twenty years. Microservices were the answer to isolation and load shape, not a fashion. We extracted the two things that killed the process—documents and long workflows—before we touched the rest.
The application was not the problem. Putting forty clients and a PDF farm in the same process was the problem. Once documents left the web server, the outages stopped looking like destiny.
Ro***, ********
CTO, PlatformThe product started in 2005 as a custom ASP.NET application for one professional-services firm: receive documents, run a workflow, file the result. Twenty years later it was a hosted platform for forty enterprise clients—legal, insurance, and corporate operations teams—processing about 2.1 million documents a month. The stack had grown in place: ASP.NET Framework 4.8, a 1.4-million-line solution, one IIS application pool, one SQL Server with 4.8 TB of tenant data in shared tables, and a Windows Service that had been folded back into the web process so operations would only have one thing to restart.
That last decision is the one that aged worst. PDF conversion, OCR hand-off, and large-file hashing ran in-process. A client with a Friday batch of 40,000 files would pin CPU, hold SQL locks across the workflow tables, and deadlock check-in of documents that other tenants were trying to open in the portal. The UI thread pool starved. Health checks failed. Load balancers pulled the node. Every client saw the outage, including the ones who had uploaded nothing.
Workflows were the other load-bearing accident. Status machines for intake, review, exception, and archive had accreted as stored procedures and static classes named for clients who had left in 2012. Changing a step for one tenant meant a full regression of the monolith. Releases were monthly because a deadlock “fix” in invoicing could stall document check-in. Measured availability sat around 99.1%—fine for an internal tool, not for a platform that had to house forty paying clients and still look like 99.9%.
The application had been solid for twenty years because it had been, for most of that time, one client’s system. Multi-tenancy without isolation turned a reliable monolith into a shared crash domain. That is the whole case.
Bigger SQL, more IIS nodes, and a bigger VM are the first three proposals in every steering pack. We costed them so they could be rejected with numbers.
Peak team of nineteen: six .NET engineers who already knew the monolith, three who did not—on purpose—four QA, a data engineer, an SRE, and programme management sitting with the client’s operations lead. The people who had kept the monolith alive were not interviewed once and replaced. They owned the first two extractions, because they knew which stored procedure actually deadlocked on Friday.
QA stopped treating “the site is up” as the test. Suites ran a noisy-tenant document flood against a quiet tenant’s portal session and failed if either starved. Chaos days killed a worker pool in staging and expected the UI to stay up. Two of those days found missing timeouts. Both were fixed before any tenant flipped.
Programme management held the line microservices programmes usually lose: no new product features in the extracted domains until the old process no longer processed documents. A workflow redesign for a flagship client waited until that client was off the monolith path. Isolation first, then speed.
Operations no longer restart IIS on Friday night “just in case.” Support no longer tells Client 12 that Client 7’s batch is why their portal is slow. Engineers change a workflow without loading the entire solution. The twenty-year product is still the product. It is no longer one process pretending to be a platform.
Risk: Off a shared crash domain and deadlock-driven outages, onto services that can fail in pieces and still serve forty clients.
Cost: Web-tier oversizing for document CPU came out. Incident hours spent on “the site is down for everyone” dropped with the blast radius.
Speed: Tenant workflow changes ship on a service release train. Document capacity scales without a portal deploy.
A monolith-to-microservices move is not a verdict on the twenty-year application. It is a judgement that one process cannot convert documents, run old workflows, and house forty clients without sharing their failures. We extract the failure domains first; 99.9% is what you measure after isolation is real, not what you put on the architecture slide.