Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to address multiple Rails CI failures

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Yasuo Honda Yasuo Honda
October 29, 2021

How to address multiple Rails CI failures

GinzaRails#38
Rails CIが同時に複数モジュールで落ちたときの対応方法

Avatar for Yasuo Honda

Yasuo Honda

October 29, 2021
Tweet

More Decks by Yasuo Honda

Other Decks in Technology

Transcript

  1. 前提知識としてのRails CIの特色 BuildKiteを利用 https://buildkite.com/rails/rails/builds?branch=main Gemfile.lock はリポジトリにあるが、CI時に削除して、 bundle install している https://github.com/rails/buildkite- config/blob/ed61405b4b52f480a49e3556ccc50ce0dcbda31d/Dockerfile#L134

    && rm Gemfile.lock && bundle install -j 8 && cp Gemfile.lock tmp/Gemfile.lock.updated \ Dependabot などによるGemfile.lockの更新は行われていない 定期的にRafaelが Gemfile.lock のバージョンを一括で上げている AciveRecordで利用されるRDBMSは下記のDockerイメージを利用 PostgreSQL:  postgres:alpine MySQL: mysql:latest
  2. Railties https://buildkite.com/rails/rails/builds/81481#a7c8033a-6e08-401c-9abd- 131a339b8775/1136-1164 Controller周り Failure: ScaffoldControllerGeneratorTest#test_controller_tests_pass_by_default_inside_full_engine [test/generators/scaffold_controller_generator_test.rb:256]: Expected /2 runs,

    2 assertions, 0 failures, 0 errors/ to match "Run options: --seed 38355 # Running: .E Error: DashboardControllerTest#test_should_get_foo: ActionController::MissingExactTemplate: DashboardController#foo is missing a template for request formats: text/html /rails/actionpack/lib/action_controller/metal/implicit_render.rb:45:in `default_render' /rails/actionpack/lib/action_controller/metal/basic_implicit_render.rb:6:in `block in send_action' /rails/actionpack/lib/action_controller/metal/basic_implicit_render.rb:6:in `tap' /rails/actionpack/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action' /rails/actionpack/lib/abstract_controller/base.rb:214:in `process_action' /rails/actionpack/lib/action_controller/metal/rendering.rb:53:in `process_action' /rails/actionpack/lib/abstract_controller/callbacks.rb:234:in `block in process_action' /rails/activesupport/lib/active_support/callbacks.rb:118:in `block in run_callbacks' /rails/actiontext/lib/action_text/rendering.rb:20:in `with_renderer' ... snip ... /rails/actionpack/lib/action_dispatch/testing/integration.rb:279:in `process' /rails/actionpack/lib/action_dispatch/testing/integration.rb:16:in `get' /rails/actionpack/lib/action_dispatch/testing/integration.rb:370:in `get' /rails/railties/test/fixtures/tmp/bukkits/test/controllers/dashboard_controller_test.rb:5:in `block in <class:DashboardControllerTest>' rails test test/controllers/dashboard_controller_test.rb:4 Finished in 0.180352s, 11.0894 runs/s, 5.5447 assertions/s. 2 runs, 1 assertions, 0 failures, 1 errors, 0 skips
  3. CIが落ちる理由として考えたこと 1. このcommitそのものによるものか Pull request https://github.com/rails/rails/pull/43335 に対して実施されたCIは greenなので考えにくい committerによるmain branchへの直接pushでは、事前にCIが回らないためありえ

    る 2. テストの順序依存によるものか すべてのRubyバージョンで問題が起きており、テスト順序依存とは考えにくい テストの順序依存問題については、大体対応できているので可能性は低そう 3. そのほかの環境変化によるものか 今回はこちらに該当する
  4. 利用している Gemfile.lock の違い: 7 gems updated Last successful build aws-partitions

    (1.507.0) cssbundling-rails (0.2.3) debug (1.1.0) jsbundling-rails (0.1.8) selenium-webdriver (4.0.0.rc1) sequel (5.48.0) tailwindcss-rails (0.4.3) First failing build aws-partitions (1.509.0) cssbundling-rails (0.2.4) debug (1.2.2) jsbundling-rails (0.1.9) selenium-webdriver (4.0.0.rc2) sequel (5.49.0) tailwindcss-rails (0.5.0)
  5. 7つのgemの更新により事象が再現するか確認する bundle update <gems> --conservative で Gemfile.lock で違いのあったgemのみ 更新する $

    bundle update aws-partitions cssbundling-rails debug jsbundling-rails \ selenium-webdriver sequel tailwindcss-rails --conservative https://bundler.io/man/bundle-update.1.html Use bundle install conservative update behavior and do not allow indirect dependencies to be updated. 7つのgemをupdateすると、事象が再現した
  6. tailwindcss-rails のみの更新で事象が再現した 最小再現ケース cd railties bundle update --conservative tailwindcss-rails git

    diff bin/test test/generators/scaffold_controller_generator_test.rb:248 git diff rails (>= 6.0.0) sucker_punch (3.0.1) concurrent-ruby (~> 1.0) - tailwindcss-rails (0.4.3) + tailwindcss-rails (0.5.0) rails (>= 6.0.0) terser (1.1.4) execjs (>= 0.3.0, < 3)
  7. tailswindcss-railsのどのcommitで振る舞いが変わったのか tailwind-css-rails 0.4.3 と 0.5.0 の間のいずれかのcommitがきっかけになって いるはず tailwindcss-rails の main

    ブランチで再現を試みる -> 再現しない gem "tailwindcss-rails", github: "rails/tailwindcss-rails", branch: "main" 0.5.0 と main の間のcommitで対応されたと考えられる https://github.com/rails/tailwindcss-rails/pull/78 でmainブランチには修正されていた tailwindcss-rails 0.5.1 をリリースしてもらって対応終了 https://rubygems.org/gems/tailwindcss-rails/versions/0.5.1 Gemfile.lock の tailwindcss-rails (0.4.3) はそのまま(理由は前述したとおり)
  8. Active Record PostgreSQL (activerecord postgresql) https://buildkite.com/rails/rails/builds/81481#a9f5a3f2-46a2-44f2-a9b4- ca15516e04cb/1099-1110 Failした結果の抜粋 ActiveRecord::Schema.define(version: 0)

    do ... snip ... # Custom types defined in this database. # Note that some types may not work with other database engines. Be careful if changing database. create_enum "mood", ["happy", "ok", "sad"] end create_enum メソッド第2引数のarray要素の順序が違う assert output.include?('create_enum "mood", ["sad", "ok", "happy"]'), output
  9. PostgreSQLのDockerイメージでPostgreSQL 13を明示してみる PostgreSQL 13では、エラーが再現しないことを確認した https://github.com/rails/buildkite-config/pull/14 @@ -109,7 +109,7 @@ services:

    - "./mysql-initdb.d:/docker-entrypoint-initdb.d" postgres: - image: "${POSTGRES_IMAGE-postgres:alpine}" + image: "${POSTGRES_IMAGE-postgres:13}" environment: POSTGRES_HOST_AUTH_METHOD: "trust"
  10. 修正コード 修正は、jhawthorn によるもの string_agg 関数の中で enumsortorder で明示的に order by するように変更

    https://github.com/rails/rails/pull/43371/files query = <<~SQL SELECT type.typname AS name, - string_agg(enum.enumlabel, ',') AS value + string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value FROM pg_enum AS enum JOIN pg_type AS type ON (type.oid = enum.enumtypid) これでこのテストはgreenとなった 背景をもう少し見てみましょう
  11. SQL文での再現コード DROP TYPE IF EXISTS mood; DO $$ BEGIN IF

    NOT EXISTS ( SELECT 1 FROM pg_type t WHERE t.typname = 'mood' ) THEN CREATE TYPE "mood" AS ENUM ('sad', 'ok', 'happy'); END IF; END $$; SELECT type.typname AS name, string_agg(enum.enumlabel, ',') AS value FROM pg_enum AS enum JOIN pg_type AS type ON (type.oid = enum.enumtypid) GROUP BY type.typname;
  12. PostgreSQL 13での結果 : sad,ok,happy activerecord_unittest=# SELECT type.typname AS name, string_agg(enum.enumlabel,

    ',') AS value FROM pg_enum AS enum JOIN pg_type AS type ON (type.oid = enum.enumtypid) GROUP BY type.typname; name | value ------+-------------- mood | sad,ok,happy (1 row)
  13. PostgreSQL 14での結果 : happy,ok,sad activerecord_unittest=# SELECT type.typname AS name, string_agg(enum.enumlabel,

    ',') AS value FROM pg_enum AS enum JOIN pg_type AS type ON (type.oid = enum.enumtypid) GROUP BY type.typname; name | value ------+-------------- mood | happy,ok,sad (1 row)
  14. PostgreSQL 13.4での実行計画 activerecord_unittest=# explain activerecord_unittest-# SELECT type.typname AS name, string_agg(enum.enumlabel,

    ',') AS value FROM pg_enum AS enum JOIN pg_type AS type ON (type.oid = enum.enumtypid) GROUP BY type.typname; QUERY PLAN ----------------------------------------------------------------------------------------- GroupAggregate (cost=130.53..148.08 rows=780 width=96) Group Key: type.typname -> Sort (cost=130.53..132.48 rows=780 width=128) Sort Key: type.typname -> Hash Join (cost=73.20..93.06 rows=780 width=128) Hash Cond: (enum.enumtypid = type.oid) -> Seq Scan on pg_enum enum (cost=0.00..17.80 rows=780 width=68) -> Hash (cost=58.09..58.09 rows=1209 width=68) -> Seq Scan on pg_type type (cost=0.00..58.09 rows=1209 width=68) (9 rows)
  15. PostgreSQL 14.0での実行計画 : Sort 処理が消えている activerecord_unittest=# explain activerecord_unittest-# SELECT type.typname

    AS name, string_agg(enum.enumlabel, ',') AS value FROM pg_enum AS enum JOIN pg_type AS type ON (type.oid = enum.enumtypid) GROUP BY type.typname; QUERY PLAN -------------------------------------------------------------------------------- HashAggregate (cost=75.85..76.82 rows=78 width=96) Group Key: type.typname -> Hash Join (cost=2.75..75.26 rows=78 width=128) Hash Cond: (type.oid = enum.enumtypid) -> Seq Scan on pg_type type (cost=0.00..67.71 rows=1071 width=68) -> Hash (cost=1.78..1.78 rows=78 width=68) -> Seq Scan on pg_enum enum (cost=0.00..1.78 rows=78 width=68) (7 rows)
  16. わかっていること・わかっていないこと RDBMSでは、 order by のないクエリーの結果順序は「不定(non determistic)」である PostgreSQL 13でも「並び順はデフォルトでは指定されません」と書いてある https://www.postgresql.jp/document/13/html/functions-aggregate.html 集約関数(中略)string_agg、およびxmlagg、そして類似のユーザ定義の集約関数

    は、入力値の順序に依存した意味のある別の結果値を生成します。 この並び順はデ フォルトでは指定されませんが、4.2.7に記述されているように、集計呼び出し中 にORDER BY句を書くことで制御可能となります。 PostgreSQL 14 Beta 2(2021年6月24日)の時点で再現することがわかった https://www.postgresql.org/about/news/postgresql-14-beta-2-released-2249/ PostgreSQL 13から14の間のどのcommitによる振る舞いの変更なのかはわからなかった PostgreSQLのコードをgit bisectしてビルドすれば理論上可能のはず
  17. Active Job integration (acivejob integration) Integration testに必要なPostgreSQLデータベースが存在しない NOTICE: database "active_jobs_qc_int_test"

    does not exist, skipping Cannot run integration tests for queue_classic. To be able to run integration tests for queue_classic you need to install and start postgresql.
  18. 修正コード by jhawthorny(again) 修正はRailsのActive Job integration testが利用している queue_classic になされた https://github.com/QueueClassic/queue_classic/pull/334

    libpqに渡す接続文字列でttyオプションの値を空文字 '' から nil に変更している [ host, # host or percent-encoded socket path url.port || 5432, - nil, '', #opts, tty + nil, nil, #opts, tty url.path.gsub("/",""), # database name url.user, url.password Rails側ではこのpull requestの元になるブランチのqueue_classicを利用中 https://github.com/rails/rails/pull/43373
  19. この変更が必要なわけ "PostgreSQL 14 tty"で検索したところ、"篠田の虎の巻「PostgreSQL 14 新機能検証結 果」"に乗っていそうなことがわかった https://community.hpe.com/t5/HPE-Blog-Japan/篠田の虎の巻-PostgreSQL-14-GA-新 機能検証結果-公開/ba-p/7151317#.YXgEyZ-RX0p 2.5.13.

    LIBPQ 接続文字列 接続文字列の authtype および tty は削除されました。SSL 圧縮を行うクライアン ト接続 設定 sslcompression はバックエンドでは無視されます。 当時は、ダウンロードできなかったので、twitterで聞いたところ、なんと該当する commitまで教えていただきました https://twitter.com/nori_shinoda/status/1445278813311561728 (そのときはBeta版で、GA版に移行する前だったのでダウンロードできなかった)
  20. この変更が有用なわけ (cont.) 「接続文字列の authtype および tty は削除されました。」のcommit https://github.com/postgres/postgres/commit/14d9b37607ad30c3848ea0f2955a7 8436eff1268 2003年(PostgreSQL

    7.4)からdeprecatedになったったttyオプションが、約18年たって PostgreSQL 14で削除された https://github.com/postgres/postgres/commit/cb7fb3ca958ec8bd5a14e740c067f1d 096af3454 https://github.com/QueueClassic/queue_classic/pull/334 は動くことはわかったが、 この根拠があったほうがマージされやすいと考えたため、追記してコメント https://github.com/QueueClassic/queue_classic/issues/333#issuecomment- 934200173 無事pull requestがマージされる