In the world of web development, frameworks, and libraries continually emerge to enhance user experiences and streamline the development process. This blog post explores the intriguing encounter with a memory leak in a work project, which was attributed to the combination of Emotion, Chakra-UI, server-side rendering (SSR), and dynamic props. We will delve into the significance of these technologies, the extent of the memory leak's impact, the detective work involved in identifying the root problem, the solution implemented, and the remarkable results achieved.
What is Emotion, Chakra-UI, and Dynamic Props?
To grasp the essence of the memory leak issue, it is crucial to understand the technologies at play. Emotion is a popular library for styling React components, providing a seamless way to handle CSS-in-JS. Chakra-UI, on the other hand, is a powerful React component library that offers a rich set of UI components and styling utilities. Dynamic props refer to the ability to pass props dynamically to styled components, enabling flexible and interactive styles.
The Magnitude of the Memory Leak
We had been dealing with this leak for quite a while, and it never crossed our minds that a styling tool could be the root cause. It's interesting to note that we had two projects with the same code but different languages, and only one of them leaked. The only noticeable difference between the two was the amount of requests hitting the server.
On Server 1, where the leak occurred, we saw an average of 10,000 requests every 5 minutes. Meanwhile, Server 2, which didn't leak, handled only around 1,000 requests in the same timeframe.
Realizing this disparity has made us reconsider the impact of the styling tool on the issue. It's surprising how something seemingly unrelated could be the culprit behind the leak.
Here's what the Server 1 memory usage graph looked like:
Unraveling the Root Problem:
Determined to identify the root cause, I decided to adopt the strategy outlined in this post. Taking inspiration from it, I proceeded to stress test a production build instance by running it locally using Artillery, simulating a small number of users per second.
To my surprise, I observed a peculiar behavior during the stress test. With every page refresh, the value of 'Internal Node' consistently increased. However, at this stage, I couldn't definitively conclude whether emotion was the underlying issue responsible for this behavior.
Excited by the progress made, I proceeded to isolate the problem further. To do so, I recreated a specific component using vanilla-extract. I then rendered this component multiple times using different strategies - once with vanilla-extract and again with emotion.
The results were telling. The pattern repeated itself consistently. Irrespective of the number of renderings (whether it was 10, 50, 100, or even 1000), when using emotion, a substantial amount of memory was allocated to InternalNode.
These findings strongly suggest that emotion was indeed the root cause of the memory consumption issue we've been facing.
With the source of the leak now identified, my focus shifted toward understanding the underlying reason behind it. To delve deeper into the issue, I conducted a series of tests using the emotion. I explored three different styling strategies: utilizing the style prop (from React), the css prop, and dynamic props.
And boom! A breakthrough was made as I analyzed the results of the tests. Among the three styling strategies employed, one of them stood out by consistently exhibiting the pattern we had been observing. That culprit was none other than dynamic props!
It became evident that the usage of dynamic props with emotion was responsible for triggering the memory leak.
But what about chakra?
Chakra UI builds upon the emotion as its foundation, it would be necessary to address this aspect as well to fully resolve the memory leak issue.
Now comes the challenging (and costly) part: considering the entire project's reliance on the emotion and chakra libraries, removing them would be an extensive and time-consuming undertaking.
Therefore, we devised a three-step solution to address the memory leak issue:
- Step 1 (short-term solution): Implement a cache layer, such as Azure Frontdoor. By introducing this caching mechanism, we could mitigate the impact of the memory leak while buying ourselves some time for further resolution.
- Step 2 (short-term solution): Transition all styles currently implemented through dynamic props to inline styles. Although this step will help reduce the memory leak, it may introduce code smells and increase code complexity due to the manual placement of styles within the markup.
- Step 3 (long-term solution): Commit to removing emotion and chakra from every project and design system. Instead, we will adopt a new zero-runtime solution that eliminates the possibility of memory leaks arising from styling issues. This comprehensive change will require careful planning and execution but will ultimately ensure a more reliable and robust system.
By following these steps, we can address the memory leak problem, implement short-term mitigation measures, and pave the way for a long-term solution that eliminates the potential for future memory leaks by adopting a new styling approach.
In our specific project, we decided to deviate from the recommended approach and proceeded directly to step 3, bypassing step 2. However, it's important to note that step 2 remains a viable option for addressing the memory leak.
By implementing step 2 and transitioning the styles from dynamic props to inline styles, we can potentially reduce the memory leak while maintaining the use of the emotion and chakra libraries. However, it's worth considering that this approach may introduce code smells and increase code complexity due to the manual placement of styles within the markup.
Ultimately, the decision to skip or include step 2 depends on the specific circumstances, project constraints, and trade-offs involved. It's crucial to assess the potential impact on code quality, development time, and maintenance overhead when determining the most suitable solution for addressing the memory leak in your project.
Now, let's dive into the results! As the fix is still a work in progress, we are yet to obtain the actual server results. However, the results from the stress test conducted thus far are undeniably impressive.
To stress test the server and evaluate the impact of our fix, I once again utilized Artillery, employing three distinct scenarios:
- Warming up: 60 seconds with 30 users per second.
- Ramping up: 240 seconds with 50 users per second.
- Sustained load: 500 seconds with 100 users per second.
Each user followed the same flow: accessing the homepage, waiting for 1 second, accessing a secondary page, waiting for 2 seconds, and then leaving.
Here are the summarized results:
- Production Average Data:
Memory Usage: 1.3GB
Requests: 500 requests per minute (during the period of the test)
- Stressed Server Average Data (Before Fix):
Memory Usage: 420MB
Requests: 2 requests per minute (yes, only two!)
- Stressed Server Average Data (Scenario 1):
Memory Usage: 250MB
Requests: 1.5k requests per minute
- Stressed Server Average Data (Scenario 2):
Memory Usage: 258MB
Requests: 3k requests per minute
- Stressed Server Average Data (Scenario 3):
Memory Usage: 450MB
Requests: 5k requests per minute
Comparing the stressed server's average data before the fix to the production environment, we achieved the following improvements:
- Scenario 1: 80% reduction compared to production.
- Scenario 2: 80% reduction compared to production.
- Scenario 3: 65% reduction compared to production.
Requests per Minute:
- Scenario 1: 200% increase compared to production.
- Scenario 2: 500% increase compared to production.
- Scenario 3: 900% increase compared to production.
These impressive improvements demonstrate the effectiveness of our fix in mitigating the memory leak issue and enhancing the server's performance under stress.
In the realm of web development, we embarked on a thrilling memory leak investigation, uncovering a hidden culprit lurking within Emotion, Chakra-UI, SSR, and dynamic props. Armed with determination and a three-step solution, we set out to crack the case and restore harmony to our codebase. With our detective hats firmly in place, we left no stone unturned, leading to remarkable results that left us in awe. As the memory leak faded into oblivion, our server danced with joy, boasting reduced memory usage and soaring performance.
In this thrilling tale, we learned that even the most unexpected suspects can be behind mysterious bugs. Armed with perseverance and the right tools, we can uncover their secrets and bring stability back to our projects. So, fellow developers, remember to stay curious, keep investigating, and code happily ever after, knowing that no bug can escape your determined gaze.