Stephen Swann |
Memory leaks can be a nightmare to debug, especially in large-scale applications. They can cause the application to become slower over time and eventually crash. As a Ruby on Rails developer, I recently encountered a memory leak issue in one of my applications, and in this post, I will share my experience on how I found and fixed it.
When I first noticed the issue, I suspected that it might be caused by an old gem called Virtus, which had been deprecated for a couple of years. To confirm my suspicion, I used the Memory Profiler gem, which is a great tool for tracking memory usage in Ruby applications.
Originally we thought the memory leak might have originated in the HTTP client that communicates with an external API. So, we wrapped the code in a memory profiler block and ran a test multiple times. It appeared that Virtus was holding on to the most memory, but the amount was actually insignificant. Despite removing the gem, the memory usage continued to increase over time.
require 'memory_profiler'
report = MemoryProfiler.report do
def request(http_method: :get, ...)
...
end
end
report.pretty_print
At one point during our investigation, we also tried wrapping various parts of our code in AppSignal instruments to help identify the memory leak. However, this approach didn't prove completely fruitful because we were looking in the wrong places.
Stefan and I were actually pair programming on a completely different issue when he stumbled upon a possible solution to the memory leak problem. While we were brainstorming different ideas Stefan was searching through old GitHub issues when he stumbled upon one from March 2014 that sounded similar to our problem. After digging deeper, we found out that the issue had been fixed just 10 hours before he found the thread. It turned out to be the prepend_view_path
that was causing the leak.
The reason why it’s hard to find these is because it’s an application running as a CMS with each business having its own themes. That’s why we’ve used the prepend view path.
With Stefan's programmatic approach to problem solving and his experience with Rails, we were able to find a solution to the memory leak issue in our application. Stefan discovered an article that showed how to patch the prepend_view_path
method, which was causing the memory leak. After applying the patch, the issue was resolved.
RESOLVER_CACHE = Concurrent::Map.new
def prepend_view_path(path)
resolver = RESOLVER_CACHE.fetch_or_store(path) do
ActionView::FileSystemResolver.new(path)
end
# <https://github.com/rails/rails/issues/14301#issuecomment-771651933>
resolver.clear_cache unless ActionView::Resolver.caching?
super(resolver)
end
Finding and fixing a memory leak in a Ruby on Rails application can be a challenging task, but with the right tools and knowledge, it can be achieved. However, tools like the memory profiling gem are helpful, but are often limited to looking at specific pieces of code or endpoints, which can make it difficult to track down elusive memory leaks. If you’d hit a different endpoint than the troublemaker, it is almost impossible to find this problem. Ultimately, it was Stefan's thorough research that led to the discovery of the real culprit and a solution to the problem. It is important for developers to stay up-to-date with the latest tools and best practices to ensure that their applications are performant and scalable.