SpringMT / zstd-ruby

Ruby binding for zstd(Zstandard - Fast real-time compression algorithm)
https://github.com/facebook/zstd
BSD 3-Clause "New" or "Revised" License
69 stars 16 forks source link

Releases 1.5.6.2 and 1.5.6.3 have memory leaks when interacting with `mongoid` #80

Closed adam-hampton-sp closed 7 months ago

adam-hampton-sp commented 7 months ago

We have a Ruby on Rails app that uses MongoDB with the mongoid (7.5.4) and mongo (2.19.3) gem versions providing I/O to the database. The zstd-ruby is used extensively to provide over-the-network compression of payloads between RoR applications and MongoDB database clusters. Basically every DB I/O call involves compression and decompression of payloads.

When we upgraded zstd-ruby from 1.5.6.1 to releases 1.5.6.2 and 1.5.6.3 last week we started having issues with our RoR application consuming RAM to the point of OOM'ing. A trace of our recently changed gems and testing has shown that reverting our zstd-ruby gem to 1.5.6.1 resolves the memory leak issue.

I am, unfortunately, not particularly familiar with the zstd-ruby code base and its unit tests or specs. I do not know if it has any memory leak iterative test cases. I'm at limited capability of providing a concrete and stand-alone reproduction at the moment. At this point all I can say is:

The condition reproduces under Ruby 3.2.1 and 3.1.4 both MacOS M1 architecture and on Alpine Linux x86-64 architecture, both very recent releases, patches of operating systems. That inclines me to think this is not operating system related.

My first attempt to reproduce the issue fails with what look like un-related issues. Test driver

#!/bin/ruby
# ############################################################################
# A test driver to reproduce OOM in Ruby using `zstd-ruby`
require 'zstd-ruby'
require 'securerandom'

source_data = ""
512.times { source_data += SecureRandom.uuid }

puts "source_data.size:#{source_data.size}"

# Test compressing and de-compressing our source data 100,000 times.  The cycles
# are intended to exercise the libary and reproduce a memory leak.
100_000.times do |i|

  compressed_data = Zstd.compress(source_data)
  expanded_data = Zstd.decompress(compressed_data)
  puts " - #{i}: c:#{compressed_data.size} e:#{expanded_data.size}" if i % 1000 == 0

end

This produces problems a few thousand cycles into the iteration:

% ruby testlab.rb
source_data.size:18432
 - 0: c:9460 e:18432
 - 1000: c:9460 e:18432
 - 2000: c:9460 e:18432
testlab.rb:18:in `decompress': not compressed by zstd: Unspecified error code (RuntimeError)
    from testlab.rb:18:in `block in <main>'
    from testlab.rb:15:in `times'
    from testlab.rb:15:in `<main>'
SpringMT commented 7 months ago

I apologize for the oversight. There was a missed release of the context(ZSTD_DCtx) after decompression in Zstd.decompress. I have addressed this issue in the following pull request. https://github.com/SpringMT/zstd-ruby/pull/81

My local testing script appears to show that the memory leak has been resolved.

SpringMT commented 7 months ago

I am also aware of an issue with the compression. We are currently addressing it. In the meantime, please use version 1.5.6.1.

SpringMT commented 7 months ago

We have released a version with the fixes. https://github.com/SpringMT/zstd-ruby/releases/tag/v1.5.6.4 https://rubygems.org/gems/zstd-ruby/versions/1.5.6.4

I would appreciate it if you could check it again.

adam-hampton-sp commented 7 months ago

Wow, what amazing turn around! Thank you. I will pull v1.5.6.4 today and test it locally. Give me some hours to get back to you.

adam-hampton-sp commented 7 months ago

I have confirmed the test driver works correctly against v1.5.6.4 on my local M1 workstation. I will verify our RoR app in conjunction with the MongoDB gems later today.

adam.hampton in ~/zstd-ruby-oom  % bundle install
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Fetching zstd-ruby 1.5.6.4 (was 1.5.6.1)
Installing zstd-ruby 1.5.6.4 (was 1.5.6.1) with native extensions
Bundle complete! 3 Gemfile dependencies, 9 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

adam.hampton in ~/zstd-ruby-oom  % ruby ./testlab.rb 
source_data.size:18432
 - 0: c:9515 e:18432
 - 1000: c:9515 e:18432
...
 - 98000: c:9515 e:18432
 - 99000: c:9515 e:18432
adam-hampton-sp commented 7 months ago

I have confirmed that v1.5.6.4 works correcly with MongoDB's Gems and drivers. Thank you again for the fix! Cheers, --Adam

SpringMT commented 7 months ago

Thank you!