DEV Community

Cover image for Operator Overload: How a Treasure Hunt Engine's Poor Architecture Design Left Us Reeling
Lillian Dube
Lillian Dube

Posted on

Operator Overload: How a Treasure Hunt Engine's Poor Architecture Design Left Us Reeling

The Problem We Were Actually Solving

When I joined the team at Veltrix as a production operator, we were struggling to scale our flagship application, a real-time treasure hunt engine. Users could create and join hunts, solve clues, and collect rewards. It was a complex system with a rapidly growing user base. Our main goal was to ensure the application remained stable and responsive under heavy load. But we consistently hit a wall at a certain scale. It was like we were stuck in an endless loop of "Cannot Connect to Database" errors and "Resource Pool Exhaustion" messages.

Our documentation hinted at a service-oriented architecture, but it was unclear how these services interacted with each other and with the database. Our system administrator, Jamie, would often receive frantic calls from developers, "The API is down again! The queue is backed up!" But when he dug deeper, he found that the API was simply overloaded, and the queue was clogged due to slow database queries.

We were convinced that our system was on the verge of collapse, but the documentation didn't point to a clear bottleneck. The usual suspects – code quality, resource management, or infrastructure provisioning – were all relatively good. It seemed like we had a perfect storm of problems, and we needed a good dose of engineering magic to fix it.

What We Tried First (And Why It Failed)

Initially, we thought the problem lay with the database, so we scaled up the resources dedicated to it. We upgraded the storage, added more RAM, and increased the number of instances. But this only delayed the inevitable. As soon as we hit the same scale, the database would become slow once again. We were convinced we needed a more powerful database, but we couldn't pinpoint the exact issue.

Then, we tried to address the API's performance. We optimized the API's code, reduced the number of database queries, and optimized the caching mechanism. However, the API still became overwhelmed when the user base grew beyond a certain threshold. We realized that the problem wasn't with the API or the database individually, but with how they interacted with each other.

We even resorted to hiring an external consultant to review our system architecture, thinking that a fresh pair of eyes might identify the root cause. But even the consultant couldn't pinpoint the exact problem, leaving us more perplexed than ever.

The Architecture Decision

After months of struggle, I decided to take a step back and look at the big picture. I realized that our system was attempting to manage a large number of concurrent connections while performing complex operations on the data. This led to a high volume of database transactions, which in turn caused the database to become slow and unresponsive.

I discovered that the API was using a simple RESTful approach to handle requests, with each request resulting in multiple database queries. This was compounded by the fact that the database was using a read-heavy workload, with many more reads than writes.

I decided to break down the system into smaller, more focused services. I created a request queue to buffer incoming requests from the API and ensure that they were processed at a rate that the database could handle. I also implemented a publish-subscribe messaging system to handle the high volume of transactions efficiently.

Our system administrator, Jamie, and I worked together to implement a caching mechanism that stored frequently accessed data in memory, thus reducing the load on the database.

What The Numbers Said After

After implementing the new architecture, we saw a significant drop in the number of "Cannot Connect to Database" errors. The API was no longer overwhelmed by the sheer volume of requests, and the queue was cleared in a matter of seconds.

We also noticed a 30% reduction in the number of database transactions. The read-heavy workload was now more manageable, and the database was faster and more responsive.

What I Would Do Differently

Looking back, I realize that we should have addressed the system's architecture design earlier in the development process. We should have implemented a more scalable and modular design from day one, rather than trying to fix the existing system as it grew.

In hindsight, I would have chosen a more robust caching mechanism and implemented it from the start. I would have also invested more time in designing a better communication protocol between the services to ensure that they were working together efficiently.

Most importantly, I would have recognized the system's limitations earlier and adjusted the design to better match the expected growth and usage patterns. By doing so, we would have avoided the production overload that left us scrambling to fix the issues we now know were preventable.


The tool I recommend when engineers ask me how to remove the payment platform as a single point of failure: https://payhip.com/ref/dev1


Top comments (0)