sinatra + pgでsorry, too many clients alreadyが出た時の対処法
はじめに
Railsを利用するほどでもないような簡単なAPIサーバを立てたりするときに、Sinatraをよく利用するのですが、アクセスした時に以下のようなエラーが発生しました。
PG::ConnectionBad: FATAL: sorry, too many clients already FATAL: sorry, too many clients already api.rb:13:in `initialize' api.rb:13:in `new' api.rb:13:in `block in <main>'
原因
アプリにはDBとしてPostgreSQLを利用していますが、DBへの接続が上限に達しているようでした。
どうやら、アクセスの度にDBに接続を行い、その接続がずっと残っているままになっているようでした。
原因はすぐに分かったのですが、対処に結構時間がかかってしまいました。
rubyファイルは、以下のように記述していました。
require 'sinatra' require 'sinatra/reloader' require 'json' require 'pg' class MyApp < Sinatra::Base set :bind, '0.0.0.0' set :port, 4567 get '/api/hoge' do con = PGconn.connect('localhost', 5432, '', '', 'hoge_db', 'postgres', '') res = con.exec("SELECT COUNT(*) FROM foo;") res.to_a.to_json end end
問題は、特にこの、リクエスト処理の部分で発生していると思われます。
DBへの接続を行っている con
変数が、消えずに残っているため、DB接続数の上限に達しているようです。
get '/api/hoge' do con = PGconn.connect('localhost', 5432, '', '', 'hoge_db', 'postgres', '') res = con.exec("SELECT COUNT(*) FROM foo;") res.to_a.to_json end
対処法
対処法としては、以下のようにPostgreSQLへのアクセスを行う con
をクラス変数として共通化してやることで、コネクションがアクセスの度に増えることがなくなりました。
class MyApp < Sinatra::Base @@con ||= PGconn.connect('localhost', 5432, '', '', 'hoge_db', 'postgres', '') ... end
失敗した対処
その1 DBへのコネクションをcloseする
con
を生成しているメソッド内で、全て con.close
を行い、DB接続を切断することを試みました。
しかし、これでは改善されませんでした。
pry
で実行すると、正しく切断できていたので、 sinatra
だとダメだったようです。良くわかりませんでした。
その2 インスタンス変数にして初期化する
con
をインスタンス変数にし、以下のようなメソッドを定義し、アクセスの度に con
が生成されることを防ごうとしました。
しかし、これでも改善されませんでした。
調べてみたところ、Sinatraはアクセスの度にアプリケーションのインスタンスを生成し、リクエストを処理しているらしいです。
そのため、おそらく各インスタンスに @con
が存在していしまい、最初のコードと同じ結果になってしまっているのだと思います。
ということは、生成されたインスタンスが削除されずに存在し続けているということでしょうか....これもよく分からない。
参考 Sinatra のスコープについて - 君の瞳はまるでルビー - Ruby 関連まとめサイト
def con @con ||= PGconn.connect('localhost', 5432, '', '', 'hoge_db', 'postgres', '') end
背景と考察
自分用に軽量なアプリを作ったり、書き捨てでアプリ作るときは、Sinatraと、DBアクセスのためにgem pgを利用しているのですが、
今回のエラーには初めて遭遇しました。
PostgreSQLのコネクションの最大数は、デフォルトで100が設定されているようで、1人で開発や運用していると、なかなかこの上限には引っかからないと思います。
サーバの再起動とか、アプリの更新を挟むとリセットはされるので、尚更でしょう。
今回のAPIサーバは、稼働中のあるシステムにAPIを追加するために作成しました。
稼働中のものを触るため、本番に影響は与えないようテストなどを行って追加し、しばらくは何の問題も発生しなかったのですが、
しばらく運用した後、急にこのエラーが発生してシステムが停止していましました。
継続運用や大量アクセスで初めて発生するバグ、しかもサーバ負荷とかではなくコードに原因があるバグだったので、出会えてよかったのかなと思います。
もちろん、事前に意識して防ぐことが出来るのがベストですが。今回のおかげで、今後は同様な自体は防ぐことが出来ると思います。
おわりに
自分で開発するだけだと、アクセス数などの負荷への意識はなかなか身につかないと思いますね。
作ったものは、ぜひ様々な人に使ってもらい、より洗練させていきましょう。
失敗した対処に関して、指摘点などございましたら是非コメント下さい!!