将 Discourse 升级到 Rails 6

大家好,

Rails 6.0.0 已于 25 天前发布,我认为现在是更新 Discourse 的时候了 :slight_smile: 为此我完成了一些必要的步骤。

  1. 修复损坏的测试规范
  • lib/mini_sql_multisite_connection.rb 中添加空方法 trigger_transactional_callbacks?
  • UrlHelper 默认加载的是 ActionView::Helpers::UrlHelper 而非 lib/UrlHelper。我通过在前面添加 :: 解决了这个问题,不过,你们觉得是否应该更改该类的名称?
  • 在 Rails 5.2.3 中,MigrationContext 接受一个参数,而在 6.0.0 中则需要额外的 schema 参数。
  1. 修复已弃用的方法
  1. 在引入 Zeitwerk 之前,首先使用经典自动加载器。
  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 上失败,我会查看并修复它们。

这里有两个数字让我非常感兴趣:

6.0 版本的 RSS 性能提升了近 10%

主题(中位时间)——这是我们最常用的路由——速度提升了 22%

这确实是显著的性能提升。您能否在 topic-50 上持续测得 22% 的提速?能否确认实际页面渲染正常?

我运行了三次基准测试,这次的结果没那么显著。我的流程是在正确的分支(masterrails6)中键入 ruby script/bench.rb,然后按回车,之后不再触碰键盘,以免影响测试结果。

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

我还将开发服务器连接到性能数据库,以确保主题页面显示正确。下方的截图在我看来没有问题。

我想就一个修复征求您的意见。
我下载了所有插件,但有一个新的规范与 master 分支相比失败了(./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)

这在 master 分支的 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 合并的最后一个变更。

此外,我注意到这个规范已损坏,但 master 分支上也是如此。我可以尝试修复它(./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 有什么看法?

天哪,我想我们只能先通过 monkey patch 来应对,直到 rspec-rails 4 发布,这里想不出更干净的修复方案了。

或者……也许……如果一切正常的话,暂时使用 beta 版本的 gem?

明白了,我今晚试试安装测试版,看看情况如何。更新过程可能会很轻松顺利。

我进行了一些额外的修复。

首先,我找到了为什么某个测试在 masterrails6 分支中都失败的原因 - FIX: Freezed time used in update_holiday_usernames_spec.rb should be UTC by KrisKotlarek · Pull Request #3 · discourse/discourse-calendar · GitHub

我还为各种插件中的已弃用方法创建了拉取请求:

我将最新的 master 分支变基到了 rails6 分支。

最后,我将 rspec-rails 更新到了 4.0.0.beta2 版本,在我的本地机器上运行正常。Travis 遇到了一些问题,但我看到其他拉取请求中也存在同样的问题,所以我认为这与 rspec-rails 的升级无关。

现在已合并::tada: :tada: :tada:

今天会密切关注,非常感谢大家的辛勤工作。

也要特别感谢 Rails 团队,让这次升级如此愉快!!

稍后会在本主题中分享一些漂亮的图表。

升级过程相当平稳,这很好。性能表现均衡,且与之前相比几乎一致。

内存和 CPU 的使用情况也极为相似。

我唯一的担忧(也是我想要彻底查明的)是,Web Worker 似乎会定期出现持续几秒的“失控”线程。

因此,某些请求似乎会导致大量线程被创建,随后又迅速消失。

我将继续调查此问题,我们需要在线程数量较高时获取堆栈回溯信息,以便找出罪魁祸首。

鉴于其他方面表现都非常出色,我将不会回退此次升级。

这应根据以下内容进行修复:

这是 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 作为主机名,因此我添加了一个环境变量来配置该白名单:

我们不需要长期保留这个猴子补丁,因为我们刚刚在 Rails 中修复了这个问题。

你好,

感谢你为将 Rails 6 引入 Discourse 所做的努力!我想冒昧地问一下,这预计何时会在 Discourse 中发布?或者它是否已经包含在 2.4.0.beta 中?我只是想了解一下,这是否可能会破坏用户实例上已安装的任何插件。

此致,
Andreas。

对于所有使用默认发布渠道的用户,此功能自 9 月起已正式上线。它首次在 2.4.0.beta5 版本中亮相。

好的,非常感谢。祝2020年一切顺利,也感谢你们在此所做的一切。