- はじめに
- 概要
- 環境
- Lambda-RDS Proxy間で発生するピン留めについて
- ピン留めを解決できなかった
- Lambda function実行後にコネクションを切断してみる
- Amazon CloudWatch Logsで検証
- まとめ
はじめに
テクノロジー戦略本部で在庫管理システムを担当している長兵衛です。
いきなりですが、最近間違ったことを発信してしまったらどうしようと尻込みしてあまりアウトプットができていませんでした。
失敗を恐れて何もしないより、どんどん失敗して学んだ方がいいと自分に言い聞かせて書きはじめています。
そのため半分自己満足なのですが、内容は有益だと思って書いているのでご容赦ください!
では本題に入ります。
概要
以前、新卒がサーバレスアーキテクチャに挑戦してみたという記事を書きました。
今回はこのRuby on Jets環境での「AWS Lambda-Amazon RDS Proxy間のコネクション接続/切断」、「Amazon RDS Proxy-Amazon Aurora PostgreSQL間のコネクション使いまわし」に着目していきたいと思います。
環境
- Ruby on Jets 2.3.18
- ruby 2.5.7
- ActiveRecord 6.0.3
- Amazon Aurora PostgreSQL 11.6
- Amazon RDS Proxy
Lambda-RDS Proxy間で発生するピン留めについて
ピン留めの原因
公式ドキュメントに書いていますが、SET ステートメントやプリペアドステートメント等の原因により起こってしまう現象で、RDS Proxy-DB間のコネクションをclientが専有してしまいます。
これによって、RDS Proxyの最大のメリットであるコネクションプーリング機能が使われにくくなってしまいます。
詳しくは図で説明したいと思います。
発生する問題
例としてLambda実行後にAというコネクションがピン留め状態で残っている場合、
コネクションBはAが使用したRDS Proxy-Aurora間のコネクションを使い回すことができません。
今回やりたいこととしては、Lambda実行後にRDS Proxyとのコネクションを切断することで、コネクションBが「Aが使っていたコネクション」を使い回せるようにしたいと思います。
ピン留めを解決できなかった
RDS Proxyログを見ると、Ruby on JetsとのコネクションのSET client_encording
が原因となってピン留めが起きていました。
はじめは公式ドキュメントを参考にSETステートメントをRDS Proxyの初期化クエリに移そうとしてみましたが、SETステートメントを指定しない方法が見つからなかったため、下記のような対策を取りました。
Lambda function実行後にコネクションを切断してみる
Ruby on JetsでもActiveRecordが使用されていて、Railsと同様にコネクションプーリング機能が搭載されています。しかし、コネクションプーリングはRDS Proxyが担うため、function実行後に切断したいというのが今回の目的となっています。
ActiveRecordの挙動を確認
Ruby on Railsと同様にRuby on Jetsでもinitializerでestablish_connection
が実行されます
これによってconnetion_poolが作成された状態になり、SQL発行時にActiveRecord::Base.connection
により、connection_poolに紐付いたconnectionを作成します。
以下の方法で確認してみました。
$ bundle exec jets c # この時点でestablish_connectionが実行されている # コネクションプールが作成されていることが確認できる irb(main):023:0> ActiveRecord::Base.connection_pool => #<ActiveRecord::ConnectionAdapters::ConnectionPool:0x00005558909eb720 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x00005558909eb5b8>, @query_cache_enabled=#<Concurrent... # しかし、connectionはまだ貼られていない irb(main):023:0> ActiveRecord::Base.connected? => false # connection_poolに紐付いたconnectionを確立 irb(main):027:0> ActiveRecord::Base.connection => #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter:0x0000555893f02f80 @transaction_manager=#<ActiveRecord::ConnectionAdapters::TransactionManager:0x0000555893f29b58 @stack=[], @connection=#<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter:0x0000555893f02f80 ...>, @has_unmaterialized_transacti... # connection確認 irb(main):032:0> ActiveRecord::Base.connection_pool.active_connection? => #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter:0x0000555893f02f80 @transaction_manager =#<ActiveRecord::ConnectionAdapters::TransactionManager:0x0000555893f29b58 @stack=[], @connection=#<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter:0x0000555893f02f80 ...>, ... irb(main):033:0> ActiveRecord::Base.connected? => true
ここで、connection_poolの状態を確認します。
# connectionが1つ使われているのがわかります irb(main):042:0> ActiveRecord::Base.connection_pool.stat => {:size=>1, :connections=>1, :busy=>1, :dead=>0, :idle=>0, :waiting=>0, :checkout_timeout=>5}
connectionを切断してみる
connectionを切断するためにremove_connection
を実行してみます
irb(main):034:0> ActiveRecord::Base.remove_connection => {:adapter=>"postgresql", :encoding=>"utf8", ... }
すると、connection_pool毎削除してしまうみたいです。
# establish_connectionしてくださいというエラーが出る irb(main):036:0> ActiveRecord::Base.connection_pool ... ActiveRecord::ConnectionNotEstablished (ActiveRecord::ConnectionNotEstablished) # 接続は消えた irb(main):037:0> ActiveRecord::Base.connected? => nil
ここで問題になるのが、connection_poolを残したままにしないと、
コンテナが再利用されて以下のコードをActiveRecordが実行した際にconnection_poolが無いと怒られてしまうことです。
irb(main):044:0> ActiveRecord::Base.connection ... ActiveRecord::ConnectionNotEstablished (No connection pool with 'primary' found.)
なんとかconnection_poolを残したままコネクションを切断する方法がないか模索してみました。
disconnectというメソッドが用意されていたので実行してみます。
# コネクションプールからコネクションを解放する irb(main):046:0> ActiveRecord::Base.connection_pool.disconnect => []
すると、connection_poolが残りました!connectionは切断されています。
irb(main):047:0> ActiveRecord::Base.connection_pool => #<ActiveRecord::ConnectionAdapters::ConnectionPool:0x000055588f7a0de0 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x000055588f7a0d18>, @query_cache_enabled=#<Concurrent::Map:0x000055588f7a0c78 entries=0 ... # connectionが切断されています irb(main):049:0> ActiveRecord::Base.connection_pool.stat => {:size=>1, :connections=>0, :busy=>0, :dead=>0, :idle=>0, :waiting=>0, :checkout_timeout=>5} irb(main):048:0> ActiveRecord::Base.connected? => false
after_actionで実装
特定のリクエストの最後にコネクションを切断したいので、今回はafter_actionで実装してみました!
class ApplicationController < Jets::Controller::Base after_action :disconnect ... private def disconnect ActiveRecord::Base.connection_pool.disconnect end end
Amazon CloudWatch Logsで検証
修正前
ログ
RDS Proxy-Aurora間コネクション数
リクエスト毎に確立されていっているのがわかります
修正後
ログ
コネクションを使い回すことができているのが確認できました!
RDS Proxy-Aurora間コネクション数
コネクションを使い回して接続の確立を抑えることができています。
まとめ
ピン留めが解決できない場合は、Lambda実行後にRDS Proxyへのコネクションを切断する方法が有効だと思います。
コンテナが再利用された場合のオーバーヘッドがかかってしまいますが、それ以外はデメリットがあまりないように感じます。
最後に、BuySell Technologiesではエンジニアを募集しております! 興味がある方はぜひご応募ください! herp.careers