memo.log

技術情報の雑なメモ

docker compose run 経由で bundle install した。 vendor ディレクトリをマウントもしている。でもいざ、 bundle exec するとライブラリが無くてエラーになる。

という挙動に遭遇し、なんでだと途方に暮れていたら、以下が原因だった・・。

公式のRubyのDockerイメージのbundlerの挙動 https://blog.freedom-man.com/ruby-docker-bundler

【Rails】N+1のメモ( where するとキャッシュが効かないからRuby上で計算すること)

前提

  • 「カード」と「カードセット」というモデルがあるとする
  • 「カードセット」に複数の「カード」が紐づいているとする
  • 「カード」には「remembered」属性がある
  • あるページで各カードセットのカードの総数や「remembered」の true の数をカウントする
  • したがって、単純に実装するとN+1が発生する

サンプルコード

# == Schema Information
#
# Table name: cards
#
#  id          :bigint           not null, primary key
#  remembered  :boolean          not null
#  card_set_id :bigint           not null
#  created_at  :datetime         not null
#  updated_at  :datetime         not null
#
class Card < ApplicationRecord
  belongs_to :card_set
end


# == Schema Information
#
# Table name: card_sets
#
#  id         :bigint           not null, primary key
#  name       :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class CardSet < ApplicationRecord
  has_many :cards
end

メモ

コントローラーで以下のように実装すると(1)(2)両方で、クエリが発行されてN+1状態になってしまう。

  def test
    @card_sets = CardSet.all
    @card_sets.each do |card_set|
      card_set.cards.size # (1)
      card_set.cards.where(remembered: true).size # (2)
    end
  end

そこで以下のようにカードセットを取得するときに eager_load すると上記の (1) でキャッシュが効き、クエリが発行されなくなる。

@card_sets = CardSet.all.eager_load(:cards)

しかし、(2)ではまだクエリが都度発行されてしまう。これは where メソッドが都度SQLを発行してしまうことによる。Ruby上では各カードの関連データをキャッシュしているので、 (2) を以下のようにRuby上で計算するように変更する。

card_set.cards.select(&:remembered).size

するとクエリの発行が無くなりN+1を回避できる。

さらにメモ

preload は単純にSQLが2回発行される

CardSet.where(user_id: user_id).preload(:cards)
SELECT "card_sets".* FROM "card_sets" WHERE "card_sets"."user_id" = $1 LIMIT $2 OFFSET $3  [["user_id", 1], ["LIMIT", 10], ["OFFSET", 0]]
SELECT "cards".* FROM "cards" WHERE "cards"."card_set_id" IN ($1, $2, $3)  [["card_set_id", 1], ["card_set_id", 2], ["card_set_id", 3]]

eager_load は LEFTER JOIN するテーブルがキャッシュされる

CardSet.where(user_id: user_id).eager_load(:cards)
SELECT DISTINCT "card_sets"."id" FROM "card_sets" LEFT OUTER JOIN "cards" ON "cards"."card_set_id" = "card_sets"."id" WHERE "card_sets"."user_id" = $1 LIMIT $2 OFFSET $3  [["user_id", 1], ["LIMIT", 10], ["OFFSET", 0]]
SELECT "card_sets"."id" AS t0_r0, "card_sets"."public" AS t0_r1, "card_sets"."forked" AS t0_r2, "card_sets"."name" AS t0_r3, "card_sets"."user_id" AS t0_r4, "card_sets"."created_at" AS t0_r5, "card_sets"."updated_at" AS t0_r6, "cards"."id" AS t1_r0, "cards"."answer" AS t1_r1, "cards"."question" AS t1_r2, "cards"."hint" AS t1_r3, "cards"."remembered" AS t1_r4, "cards"."card_set_id" AS t1_r5, "cards"."created_at" AS t1_r6, "cards"."updated_at" AS t1_r7 FROM "card_sets" LEFT OUTER JOIN "cards" ON "cards"."card_set_id" = "card_sets"."id" WHERE "card_sets"."user_id" = $1 AND "card_sets"."id" IN ($2, $3, $4)

関連メモ

zenn.dev

tech.stmn.co.jp

zenn.dev

moneyforward-dev.jp

「ActionDispatch::Cookies::CookieOverflow」

session にデータを格納しすぎていた。 RailsではデフォルトでSessionのデータはCookieに保存するらしい。(Rails 7で確認)

config/initializers/session_store.rb

に設定がある。

そこで以下を導入。

github.com

gem をインストール rails generate マイグレーション session_store を設定変更

で解決した。

Terraform Provider AWS でAuroraの結合テストが失敗するとき

make testacc PKG=rds TESTS='TestAccRDSClusterInstance_basic'

結合試験は実際のAWS環境にリソースがデプロイされる。 デプロイされるリソースはこのへんで定義されてる

github.com

func testAccClusterInstanceConfig_base(rName, engine string) string {
    return acctest.ConfigCompose(
        acctest.ConfigAvailableAZsNoOptIn(),
        testAccClusterInstanceConfig_orderableEngineBase(engine, false),
        fmt.Sprintf(`
resource "aws_rds_cluster" "test" {
  cluster_identifier = %[2]q
  availability_zones = [
    data.aws_availability_zones.available.names[0],
    data.aws_availability_zones.available.names[1],
    data.aws_availability_zones.available.names[2]
  ]
  engine              = data.aws_rds_engine_version.default.engine
  engine_version      = data.aws_rds_engine_version.default.version
  database_name       = "mydb"
  master_username     = "foo"
  master_password     = "mustbeeightcharacters"
  skip_final_snapshot = true
}
`, engine, rName))
}

main ブランチを持ってきているはずなのに。以下のエラーが出てハマった。。。

2024-02-20T18:18:39.014+0900 [ERROR] sdk.helper_resource: Unexpected error:
  error=
  | Error running apply: exit status 1
  | 
  | Error: creating RDS Cluster (tf-acc-test-xxxxxxxxx): DBSubnetGroupDoesNotCoverEnoughAZs: The DB subnet group doesn't meet Availability Zone (AZ) coverage requirement. Current AZ coverage: us-west-2c. Add subnets to cover at least 2 AZs.
  | \tstatus code: 400, request id: d4d1ccab-xxx-xxxx-xxxx-xxxxx
  | 
  |   with aws_rds_cluster.test,
  |   on terraform_plugin_test.tf line 31, in resource "aws_rds_cluster" "test":
  |   31: resource "aws_rds_cluster" "test" {

どうやら前提として、 aws_rds_cluseterdb_subnet_group は省略すると以下の動作になるらしい(ドキュメントに明記しておいてほしい・・。多分無いと思う)

  • 既存のサブネットグループを(適当に)選択する
  • サブネットグループが無かったらサブネットグループを作成するが、その時既存のサブネットを用いる
  • その際、サブネット(AZ)の数が足りなかった場合は、エラーになる(サブネットを自動で作ったりはしない)
    • Auroraで指定するサブネットグループは最低でも 2AZ またがないといけない

なので、既存のVPCにサブネット(AZ)を3つ作ったら解決した。 ちなみに、リージョンはデフォルトでオレゴン(us-west-2)が選ばれた。 これはテスト中に data ソースで Optin されているリージョンから先頭が自動で選ばれるみたい。これも分かりにくすぎるでしょ・・。