Has your Rails app ever thrown the 'Lost Connection to MySQL
server' exception? I love that error. How about 'MySQL server has gone
away?' That.s a good one too. If you're a fan of these exceptions then
stop reading now because I'm about to make them go away.
Rails is set up to handle database connections in such a way that the
likelihood of hitting these errors increases with the size of your
code base. ActiveRecord maintains one database connection per model. If
you use that database command often enough then it times out resulting
in the errors listed above. You can set the timeout interval in your
environment.rb with this:
ActiveRecord::Base.verification_timeout = 14400
If your app only has a handful of models and you hit actions that
trigger activity on each model.s connection frequently enough then
bumping up the interval in the above config might just be a good enough
solution for you. But when you keep adding models to your app to keep
up with feature requests then you.ll eventually have a user execute a
code path on a process whose database connection has timed out. We have
123 models at Zvents and counting. We got to the point where Increasing
the verification_timeout wasn't really helping - we still hit a handful
of connections timeouts on models that didn't receive a lot of usage
(backend stuff mostly).
The solution, quite simply, is to have the database adapter reconnect
to the database when it detects a lost connection. It's not an original
or groundbreaking idea, but I couldn.t any plugins so I threw together
the mysql_retry_lost_connection plugin. It looks like this:
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter
def execute(sql, name = nil) #:nodoc:
reconnect_lost_connections = true
begin
log(sql, name) { @connection.query(sql) }
rescue ActiveRecord::StatementInvalid => exception
RAILS_DEFAULT_LOGGER.info("ActiveRecord::StatementInvalid Error: #{exception.message}\n#{exception.backtrace.join("\n")}")
if reconnect_lost_connections and exception.message =~ /(Lost connection to MySQL server during query|MySQL server has gone away)/
reconnect_lost_connections = false
reconnect!
retry
elsif exception.message.split(":").first =~ /Packets out of order/
raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
else
raise
end
end
end
end
end
end
We've been running it in production for a couple weeks with no side
effects - and lost connections are just a happy memory now.
I'm not entirely happy with the code. Instead of clobbering the existing
execute method it should probably use alias_method to wrap the existing
method. That would make it more resilient to changes to the underlying
code in future Rails releases. But it works for me and I'm hitting
the sack. Free beer to the first person who rewrites it that manner or
improves it any other way.