# When Our Database Got Too Fast

My favorite production bug ever was the day our database got too fast.

I was CTO at MonitorBase, a borrower retention platform for mortgage lenders. We helped about 10,000 clients figure out which of their past borrowers were back in the market, and that meant predictive algorithms chewing through enormous amounts of data.

## The problem

The algorithms ran once a month, because they took 24 to 48 hours to finish. Under the hood it was a PHP process grinding through 10 to 15 million records with hundreds of columns and 8 joins. Meanwhile, table locks in MySQL capped the whole platform at roughly 60 requests a minute, and a full database restore took 8 to 12 hours.

The mortgage market does not wait a month. Rates move in real time, and a borrower who is shopping today is gone tomorrow. Our data could not keep up, and I was the one losing sleep over it.

One rule shaped every decision: this is regulated financial data. Credit data itself was never cached, ever. It was always pulled directly from the database. Everything around it was a different story. We ran a big Redis caching system for our logged-in clients so their dashboards loaded instantly, and caching covered the website and sessions too. Credit data never touched the cache. Any new platform had to live inside that rule.

## The search

I did not want to guess, so we trialed the field against our real workload: Snowflake, Algolia, Elasticsearch on AWS, Meilisearch, and plain MySQL.

The answer showed up sideways. I came across Jack Ellis, cofounder of Fathom Analytics, writing about running SingleStore inside a Laravel stack. His pain points were exactly ours. SingleStore gave us transactions and analytics in one system, a columnstore that ate our joins for breakfast, and it worked inside the Laravel framework we were already built on. We made the jump.

## The migration

We moved to SingleStoreDB Cloud and loaded 100 million records through Pipelines. I remember the moment because there almost was not one: we hit go, and the data was already there. That was the migration.

The real work was moving the predictive workload out of PHP loops and into set-based SQL, where it had belonged all along. Then we put Tableau on top so the team could see it all in real time.

## The results

- Predictive algorithms: 24 to 48 hours down to 15 minutes, roughly 200 times faster. We could run them whenever we wanted instead of once a month.
- Throughput: 60 requests a minute under MySQL table locks, up to 1,000 requests a minute.
- The big 8-join process that used to take one to two days: under 10 minutes.
- Restores stopped being an all-day event.

SingleStore later wrote the whole thing up as a case study: "MonitorBase Makes Every Contact Count." It is still on their site, quote and all.

## The bug

Success created my favorite bug. The new system was so fast that our Laravel queue workers started grabbing the same job in the same millisecond and doing the work twice. We had built the queue assuming the database was the slow part. Now the database was done before the workers had finished arguing about whose job it was.

We fixed the job locking quickly. It remains the best kind of problem to have: the platform outran the code around it, and we got to fix forward instead of apologizing.

I even published a small Laravel package to make SingleStore full-text MATCH queries play nicely with the framework. Once you have made a tool your own, you start giving pieces back.

## The lesson

Evaluate on your real workload, not on demos. Every vendor looks fast on their own slides. The right question is never "what is the best database," it is "what breaks first in my system, and which tool fixes that without breaking my rules." For us that was joins, locks, and a compliance rule we were never going to bend.

And sometimes the reward for fixing the bottleneck is a better bottleneck.

*Originally from my time as CTO at MonitorBase. The SingleStore case study is here: https://cdn.sanity.io/files/h3bp8c98/production/719dedc8a9b2f6add512189b76cbc18ee6b3236c.pdf*
