Rails ではない Ruby + ActiveRecord なコードベースを AWS Lambda で動作させるような状況についての備忘録です。

バッチ用途だったり API Gateway と組み合わせて API サーバーとして実装するような状況についてです。

単一のデータベースに接続する情報はよく見かけるのですが、 API Gateway + Lambda(Ruby + ActiveRecord) + Aurora MySQL(writer/reader) というような構成での接続初期設定についてはまとまった情報がなかったためこちらにまとめました。

確認した環境

  • AWS Lambda (+ API Gateway 等)
  • Ruby 2.7.x
  • ActiveRecord 7.x

最小コード

最小コードは以下になります。 writer/reader だけではなく、データベースが別の場合の設定(connects_to 等の設定が別になる場合)は Rails で設定する場合 と一緒なので割愛します。

require 'active_record'

ENV['RAILS_ENV'] = 'test'
ActiveRecord::Base
config = ActiveSupport::ConfigurationFile.parse(Pathname.new('database.yml'))
db_config = ActiveRecord::DatabaseConfigurations.new(config)
ActiveRecord::Base.configurations = db_config
ActiveRecord::Base.establish_connection

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  connects_to database: { writing: :primary, reading: :primary_replica }
end

class User < ApplicationRecord
end
  
User.find(1)

database.yml は環境によりますが、最小だと以下のようになります。 _replicareplica=true が必須です。

DATABASE_URL_WRITERDATABASE_URL_READER は SSM 等から注入するイメージです。

default: &default
  # なにかあれば

test:
  primary:
    <<: *default
    url: <%= ENV['DATABASE_URL_WRITER'] || 'mysql2://root:@127.0.0.1/db_test' %>
  primary_replica:
    <<: *default
    url: <%= ENV['DATABASE_URL_READER'] || 'mysql2://root:@127.0.0.1/db_test?replica=true' %>

development:
  primary:
    <<: *default
    url: <%= ENV['DATABASE_URL_WRITER'] %>
  primary_replica:
    <<: *default
    url: <%= ENV['DATABASE_URL_READER'] %>

production:
  primary:
    <<: *default
    url: <%= ENV['DATABASE_URL_WRITER'] %>
  primary_replica:
    <<: *default
    url: <%= ENV['DATABASE_URL_READER'] %>

DATABASE_URL だけで複数データベースの設定をする方法はないと思っていますがもしあったら教えて下さい。

明示的に writer / reader を利用する

writer を利用する場合は以下のようにします。(デフォルトは writer になっているので使うケースは少なそうです)

ActiveRecord::Base.connected_to(role: :writing) { User.find(1) }

reader を利用する場合は以下のようにします。

ActiveRecord::Base.connected_to(role: :reading) { User.find(1) }

Process, Thread まわり

AWS Lambda は Cold Start / Warm Start とあり、それぞれ観測している範囲では

  • Cold Start: 新しい Process でハンドラーが起動される
  • Warm Start: 既存 Process / 既存 Thread でハンドラーが起動される

という挙動を示しています。よって、 INIT コード部分で establish_connection しておくことで、 Warm Start している間は

  • ActiveRecord の ConnectionHandler によるコネクションプールが使い回される
  • アプリケーションが明示的にマルチスレッドなコードを書かない限り、シングルスレッドでの動作を前提としてよい
  • シングルスレッドかつ逐次実行するコードであれば、デフォルト設定である pool=5 のままでよい(減らしてもよい)

と言えるかと思います。

参考文献

おわり