How I reduced PostgreSQL memory usage from our Sidekiq workers

~ 2 mins

Like all good stories do, it started with a bunch of errors in my inbox:

ActiveRecord::StatementInvalid sidekiq#pro
  PG::ConnectionBad: PQconsumeInput() SSL connection has been closed unexpectedly : SELECT …
PG::ConnectionBad analytics:attribution
  could not fork new process for connection: Cannot allocate memory could not fork new process for connection: Cannot allocate memory could not fork new process for connection: Cannot allocate memory
ActionView::Template::Error admin#stats
  PG::OutOfMemory: ERROR: out of memory DETAIL: Failed on request of size 48. : SELECT … 

“How strange!”, I thought, “I wonder what the memory usage looks like.”

Saw pattern

It was actually worse than this and hit 1GB, but I don't have an earlier image.

Well, saw patterns in graphs are rarely good. These errors cropped up after increasing the number of Sidekiq workers, and thus also the number of connections to the database.

After some intense googling, I found out that PostgreSQL stores PREPAREd statements per session. I know Rails uses prepared statements, and I know prepared statements need to be referenced by name, which means Rails keeps this name around somewhere. After looking through the source of Rails on GitHub, I found this:

# https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/connection_adapters/statement_pool.rb#L6
module ActiveRecord
  module ConnectionAdapters
    class StatementPool # :nodoc:
      include Enumerable

      DEFAULT_STATEMENT_LIMIT = 1000

So, turns out Rails by default stores the 1 000 most recently used prepared statements. Quick math! With 30 workers, we can expect 30 000 prepared statements kept around in memory. All in all it’s not that much, but we’re sporting a humble 1GB of memory, so it turns out that this is enough to hit the roof!

So, what to do? More googling! Turns out that Rails has support for disabling prepared statements, and it’s had it since at least Rails v4.0! So, I sent a pull request with a configuration change to disable prepared statements for our Sidekiq workers:

default: &default
  adapter: postgresql
  ⋮
+  prepared_statements: <%= ! Sidekiq.server? %>

Once merged and deployed, memory usage for PostgreSQL dropped considerably, and has since been stable at just under 200 MB!

Post-fix Stability

Blue line is postgres memory, green line is free memory.

This probably comes with a cost of increased load, the good old Memory vs CPU tradeoff. We could possibly have lowered the statement limit (available since at least Rails 4.2) but the load on our servers is not too high, so we’re happy with just turning them off!

rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium rubygems