DiscourseをRails 6にアップグレード

チームの皆様、こんにちは

Rails 6.0.0 は 25 日前にリリースされましたので、Discourse のアップデートを行う時期だと思います :slight_smile: 動作させるためにいくつかの手順が必要でした。

  1. 壊れた specs の修正
  • lib/mini_sql_multisite_connection.rb に空のメソッド trigger_transactional_callbacks? を追加
  • UrlHelper がデフォルトで lib/UrlHelper ではなく ActionView::Helpers::UrlHelper を読み込んでいました。これを解決するために先頭に :: を追加しましたが、そのクラス名を変更することについてどうお考えでしょうか?
  • Rails 5.2.3 の MigrationContext は 1 つの引数を受け入れていましたが、6.0.0 では追加の schema が必要になりました
  1. 非推奨メソッドの修正
  • update_attributes!update! に更新
  • Content_type は charset を含むため、代わりに media type を使用する必要があります - rails/guides/source/upgrading_ruby_on_rails.md at main · rails/rails · GitHub
  • すでに初期化された定数(TRADITIONAL_ESCAPED_CHAR、RFC_5987_ESCAPED_CHAR)に関する警告を修正。削除する前に、ActionPack 内の値が同じであることを確認しました
  1. Zeitwerk を導入する前に、まずクラシックな autoloader を使用する
  2. Rails 6.0.0 でのマイグレーションを修正 - 最新の Rails は古いマイグレーションからの誤り(すでに定義済みのカラム ‘integer’ を定義すること)を許可しません

Discourse が期待通りに動作するようスモークテストを行いました。さらに、回帰がないことを確認するためにパフォーマンステストを実行しました(デフォルトの 500 反復を使用しました)。

テスト Rails 5.2.3 Rails 6.0.0 パーセント
categories-50 27 24 88.89%
categories-75 31 26 83.87%
categories-90 36 37 102.78%
categories-99 52 50 96.15%
home-50 27 26 96.30%
home-75 30 28 93.33%
home-90 39 38 97.44%
home-99 53 55 103.77%
topic-50 35 27 77.14%
topic-75 36 29 80.56%
topic-90 37 39 105.41%
topic-99 56 50 89.29%
categories_admin-50 47 47 100.00%
categories_admin-75 54 59 109.26%
categories_admin-90 64 66 103.13%
categories_admin-99 132 116 87.88%
home_admin-50 47 46 97.87%
home_admin-75 51 56 109.80%
home_admin-90 63 64 101.59%
home_admin-99 110 97 88.18%
topic_admin-50 50 49 98.00%
topic_admin-75 58 59 101.72%
topic_admin-90 65 67 103.08%
topic_admin-99 113 86 76.11%
load_rails 2593 2618 100.96%
rss_kb 318800 287332 90.13%
pss_kb 306913 275378 89.73%
平均 89.31%

上記のすべての変更を含むプルリクエストを作成します。何か調整が必要か、またはすべてが期待通りに動作することを確認するための追加テストが必要かどうか、お知らせください。

PR - DEV: Upgrading Discourse to Rails 6 by KrisKotlarek · Pull Request #8083 · discourse/discourse · GitHub

よろしくお願いいたします
Kris

これは素晴らしいです :confetti_ball:

これをパーセンテージ変化を含む Markdown の表にまとめていただけますか?ざっと見た限り、大きな変化はないようですが、それは素晴らしいことです。

プラグインに関しては、すべての公式プラグインをインストールする rake タスク があります。それを実行して、Rails 6 上でプラグインの仕様テストがパスすることを確認していただけますか?(rake plugin:spec でできるはずです)

元の投稿をテーブル表示に更新しました。プラグインの仕様を指摘していただきありがとうございます。Travis で 2 つの仕様が失敗しているのを見ましたので、確認して修正いたします。

ここに非常に興味深い 2 つの数値があります:

6.0 の RSS はほぼ 10% 向上しています。

トピック(中央値時間)— 私たちの最も一般的なルートですが、22% 高速化しています。

これは非常に顕著なパフォーマンス向上です。topic-50 で 22% の高速化を一貫して測定できますか?実際のページが正しくレンダリングされていることを確認できますか?

ベンチマークを3回実行しましたが、今回は結果がそれほど劇的ではありませんでした。私の手順は、正しいブランチ(master または rails6)で ruby script/bench.rb と入力し、Enter を押してキーボードに触れず、結果に影響を与えないようにすることです。
| | topic-50 | RSS |
| — | — | — | — |
| 5.2.3 | 50 | 322852 |
| 5.2.3 | 50 | 309684 |
| 5.2.3 | 50 | 346376 |
| 平均 | 50 | 326304 |
| 6.0.0 | 49 | 328844 |
| 6.0.0 | 49 | 321824 |
| 6.0.0 | 49 | 283584 |
| 平均 | 49 | 311417 |

また、トピックページが正しく表示されていることを確認するため、開発サーバーをパフォーマンスデータベースに接続しました。以下のスクリーンショットは問題ないと思います。

ある修正についてご意見を伺いたいです。
すべてのプラグインをダウンロードしましたが、1 つの新しい仕様(./plugins/discourse-data-explorer/spec/controllers/queries_controller_spec.rb:32)がマスターと比較して失敗しています。

  1) DataExplorer::QueryController when disabled denies every request
     Failure/Error: render 'default/empty'

     ActionView::Template::Error:
       wrong number of arguments (given 2, expected 1)

これはマスターで修正されています(rspec-rails https://github.com/rspec/rspec-rails/blob/4-0-dev/lib/rspec/rails/view_rendering.rb)。
def self.call(_template)def self.call(_template, _source = nil) に変更することで対応しています。

lib/freedom_patched/rspec-rails.rb に新しいファイルを追加して rspec-rails をモンキーパッチすることもできますが、これが最善のアプローチか確認したかったです。

これが Rails 6 のマージを妨げている最後の修正だと思います。

さらに、この仕様は壊れていますが、マスターでも同様に壊れていることに気づきました(./plugins/discourse-calendar/spec/jobs/update_holiday_usernames_spec.rb:14)。

 Failure/Error: expect(DiscourseCalendar.users_on_holiday).to eq([post.user.username])
       expected: ["bruce1"]
            got: []

これを修正することもできます。

最後に、プラグイン内で非推奨のメソッドがいくつかありますが、明日にでも簡単に修正できます。

rspec-rails についてのご意見をお聞かせください。

まあ、rspec-rails 4 がリリースされるまでモッキパッチを適用するしかなさそうですね。ここでよりクリーンな解決策は思いつきません。

あるいは……もしすべてが動作しているなら、とりあえずベータ版の gem を使うのはどうでしょうか?

了解しました。今夜ベータ版をインストールして様子を見てみます。スムーズに更新できるかもしれません。

いくつかの追加修正を行いました。

まず、master ブランチと rails6 ブランチの両方で1つの仕様テストが失敗していた原因を特定しました - FIX: Freezed time used in update_holiday_usernames_spec.rb should be UTC by KrisKotlarek · Pull Request #3 · discourse/discourse-calendar · GitHub

また、様々なプラグインで非推奨となったメソッドに対するプルリクエストを作成しました:

最新の masterrails6 ブランチにリベースしました。

最後に、rspec-rails をバージョン 4.0.0.beta2 に更新しましたが、ローカルマシンでは問題なく動作しています。Travis にはいくつかの問題が発生しましたが、他のプルリクエストでも同様の問題が見られるため、rspec-rails のアップグレードとは関連していないと考えています。

マージ完了しました :confetti_ball: :confetti_ball: :confetti_ball:

本日は引き続き注視していきます。この作業、本当にありがとうございます。

また、このような快適なアップグレードを実現してくださった Rails チームにも心から感謝します!!

このトピックについては、いくつかの見やすいグラフを交えて改めて報告します。

アップグレードは非常に平穏で、それは素晴らしいことです。パフォーマンスは均一で、以前と比べて驚くほど変わりません。

メモリとCPUの使用状況も非常に似ています。

唯一の懸念点(そして解明したい点)は、Webワーカーで定期的に数秒間「ランアウェイ」スレッドが発生しているように見えることです。

つまり、何らかのリクエストが大量のスレッドを生成し、すぐに消滅させているようです。

引き続き調査を進めます。スレッド数が多くなった際にバックトレースを取得し、原因を特定する必要があります。

他の点については非常に良好なため、アップグレードをロールバックすることはありません。

これは以下で修正されるべきです:

これは、Rails 6 の新しいコードの結果であり、スレッドバインド変数へのアクセスを保護し、プリペアドステートメントを使用できるかどうかを判断するものです。

Discourse ではプリペアドステートメントを一切使用していないため、このパッチは必要ありません。

詳細は以下をご覧ください:

そして……確認しました……私の修正で大量のスレッドスパイクが解消されました

また、参考までに……私がどのようにデバッグしたかも記しておきます:

  1. まず、次のような小さなクラスを作成しました
# frozen_string_literal: true

class Thread
  attr_accessor :origin
end

class ThreadDetective
  def self.test_thread
    Thread.new { sleep 1 }
  end
  def self.start(max_threads)
    @thread ||= Thread.new do
      self.new.monitor(max_threads)
    end

    @trace = TracePoint.new(:thread_begin) do |tp|
      Thread.current.origin = Thread.current.inspect
    end
    @trace.enable
  end

  def self.stop
    @thread&.kill
    @thread = nil
    @trace&.disable
    @trace.stop
  end

  def monitor(max_threads)
    STDERR.puts "Monitoring threads in #{Process.pid}"

    while true
      threads = Thread.list

      if threads.length > max_threads
        str = +("-" * 60)
        str << "#{threads.length} found in Process #{Process.pid}!\n"

        threads.each do |thread|
          str << "\n"
          if thread.origin
            str << thread.origin
          else
            str << thread.inspect
          end
          str << "\n"
        end
        str << ("-" * 60)

        STDERR.puts str
      end
      sleep 1
    end
  end

end
  1. 次に、unicorn の after_fork フックでこのクラスを読み込み、ThreadDetective.start(14) を実行しました

  2. このクラスは TracePoint を使用してスレッドが作成されるたびに厳密に監視し、スレッドに origin という名前の小さなフレームを追加して、どこから来たのかを追跡できるようにしました。多数のスレッドが観測されると、その情報を STDERR にダンプします。これは /var/www/discourse/logs/unicorn.stderr.log で確認できます。

100 個のスレッドがすべて単一の場所から発生していることがわかった時点で、根本原因を特定するのは非常に簡単でした。

Rails 6 の開発モードでは、dev.local をホスト名として使用できなくなったことに気づきました。そのため、そのホワイトリストを設定するための ENV 変数を追加しました:

このモンキーパッチを長期的に維持する必要はありません。Rails に修正がマージされたためです。

こんにちは、

Discourse に Rails 6 を導入しようとするご尽力に感謝いたします。お手数ですが、これが Discourse に実装されるのはいつ頃と見込まれていますでしょうか?それとも、すでに 2.4.0.beta に含まれていますでしょうか?また、ユーザーのインスタンスにインストールされているプラグインが動作しなくなる可能性があるかどうかについてお伺いしています。

よろしくお願いいたします、
アンドレアス。

デフォルトのリリースチャンネルを使用しているすべてのユーザー向けに、9 月からこの機能は公開されています。これは 2.4.0.beta5 で初めて紹介されました。

わかりました、どうもありがとうございます。2020 年が素晴らしい年になりますようお祈りしております。ここでの皆さんの活動に心から感謝しています。