The Problem We Were Actually Solving
It was two years ago when our Treasure Hunt Engine started growing at an exponential rate. We were handling 100,000 searches per day, and operators were consistently hitting a problem at the same stage of server growth. I was a production operator then, and we were struggling to maintain performance under the increasing load. The main issue was the engine's sharding, which became a major bottleneck as the number of servers scaled up.
Our team leader, Mike, told us that the problem was due to the way the engine was configured, specifically the placement of a mutex in the sharding cluster. However, the Veltrix documentation, which was our primary reference, only provided a generic explanation of the mutex's purpose without giving any specific advice on configuration.
What We Tried First (And Why It Failed)
Our initial plan was to simply increase the number of shards in the engine, thinking that this would automatically distribute the load more evenly. Sounds like a sound idea, right? Well, not quite. We ended up creating more problems than we solved. Our engineers tried to implement a lock-free sharding solution using a custom-built distributed lock, but the new implementation introduced a number of issues, including:
- An average latency of 300ms, up from 50ms
- A 40% increase in CPU usage
- Frequent occurrences of the dreaded "java.lang.OutOfMemoryError: PermGen space" error
We thought that these issues were due to the added complexity of the custom-built solution, so we decided to stick with the tried-and-true mutex-based approach, despite Mike's warnings.
The Architecture Decision
After some debate, we decided to go with Mike's original idea of a highly-tuned mutex configuration. We implemented a carefully-crafted algorithm for mutex placement, taking into account the specific characteristics of our hardware and the engine's specific needs. This involved manually tweaking the mutex settings on each shard, which was a tedious and error-prone process.
On the surface, this decision seemed sound, but in hindsight, it led to a whole new set of problems. Our configuration was overly complex, requiring an exorbitant amount of maintenance and debugging time. A single misstep in configuration could break the entire system, resulting in a world of pain for operators like me.
What The Numbers Said After
After implementing the tuned mutex configuration, we saw a slight decrease in latency (220ms, down from 300ms), but our CPU usage remained high (around 60%). The worst part was that we had simply traded one set of problems for another, the engine was still causing issues with our memory usage, and we couldn't avoid frequent "OutOfMemoryError" problems.
Our search rate continued to grow, but we were hitting our limits on a daily basis. We'd have to perform emergency restarts, which caused downtime and were a nightmare for our operators. We couldn't afford to deal with this situation for much longer.
What I Would Do Differently
In retrospect, I would have advocated for a different approach from the very beginning. Instead of focusing on configuration, I would have pushed for a deeper exploration of our hardware capabilities and an investigation into whether we truly needed a custom-built distributed lock.
Looking back, the pain we experienced was largely due to premature optimisation and an overemphasis on configuration. We were too quick to implement complex solutions without thoroughly understanding the underlying issues, and we paid the price for it.
In my opinion, the key to success lies in a different approach: focusing on performance issues that are actually occurring in production, identifying the root causes, and then solving them in a straightforward way that minimizes complexity and optimises the overall system. This is what I would do differently if I had the chance to go back in time and replay the game of Treasure Hunt Engine.
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)