Rails 6 には、zeitwerk と classic の 2 つの自動読み込みモードが搭載されています。このプルリクエスト DEV: Upgrading Discourse to Rails 6 by KrisKotlarek · Pull Request #8083 · discourse/discourse · GitHub では、移行期として Rails をバージョン 6.0.0 にアップグレードし、クラシックな自動読み込みを使用しました。Zeitwerk への切り替えを試みるのは興味深いでしょう。
Zeitwerk は、Ruby 向けの効率的でスレッドセーフなコードローダーです。プロジェクトが命名規則に従っていれば、Zeitwerk は適切なファイルを検出し、require や require_dependency を一切必要とせずに、必要に応じてまたは事前にファイルをロードできます。また、この記事 https://weblog.rubyonrails.org/2019/2/22/zeitwerk-integration-in-rails-6-beta-2/ によると、アプリケーションのパフォーマンスがわずかに向上する可能性もあります。
動作させるためには、いくつかのステップを実行する必要があります。
Rails の命名規則に従うために、いくつかのクラス名を変更します。例えば、ファイル canonical_url.rb は CanonicalURL ではなく CanonicalUrl クラスを定義する必要があります。同様に、ファイル ondiff.rb は ONPDiff ではなく Onpdiff クラスを定義する必要があります。別のアプローチとして、プロジェクトにカスタムインフレクターをフックすることもできますが、規則に従う方がよいと考えます - GitHub - fxn/zeitwerk: Efficient and thread-safe code loader for Ruby · GitHub
前項と同様に、慣例により validations ディレクトリに存在するカスタムバリデーションは Validations モジュールでラップする必要があります。さらに、一部のバリデーションは EachValidator から継承しており、名前空間なしでアクセス可能であるべきです。これらを別のディレクトリに移動し、自動読み込みパスに追加する予定です。
全ての require_dependency を削除し、プロジェクトが正常に動作することを確認します。
全ての require を削除し、Discourse が正常に動作することを確認します。
全てのプラグインが必要な依存関係にアクセスできることを確認します。現時点ではその方法がわかりません。まずはプラグインなしで Discourse が動作するようにすることを目指します。
もちろん、まだ解決すべき不明点がたくさんあります。進捗状況は随時更新していきますが、もし興味があれば、こちら Commits · KrisKotlarek/discourse · GitHub で試作を始めています。
Zeitwerk の実装に欠点がある場合や、見落としている点がある場合は、お知らせください。
I made some progress around Zeitwerk, however, I changed my approach. My original plan was to change every place where Discourse is not following the Zeitwerk filename convention. After a few fixes, I realised that this is just the tip of the iceberg and noticed that if I am going to follow that path, then pull request will be difficult to read and confidently merge to master. For example, all job classes under regular directory should have Regular namespace, same with Onceoff and Scheduled.
I decided to step back a little bit and think about a more evolutionary approach than revolutionary.
I decided that it would be better to introduce custom Inflector, which will cover all files which are not following Zeitwerk convention. The biggest benefit will be that we would be able to deploy that small change and once we are happy with Zeitwerk and we don’t have any performance downgrades, we can start fixing convention file by file in reasonable small pull requests.
I found some problems which could not be sorted by custom Inflector, so I made additional fixes to make it work.
Pull request is still in progress, however, at this stage, I can run Discourse with Zeitwerk and default plugins, run all specs and run benchmark without a problem.
I wanted first to be at that stable state when all specs are passing. Now I can confidently start removing all require_dependency one by one and also test official plugins. Once everything is ready, I will share with you benchmark results in this post.
For now, if you are interested in progress you may take a look on that draft PR - DEV: Upgrading Discourse to Zeitwerk by KrisKotlarek · Pull Request #8098 · discourse/discourse · GitHub
The most important file is that custom Zeitwerk Inflector - DEV: Upgrading Discourse to Zeitwerk by KrisKotlarek · Pull Request #8098 · discourse/discourse · GitHub
To make plugins work I needed to create a few more small pull requests. Once they are merged I think that specs on Discourse should pass.
I also checked performance on Rails 6.0.0 with Classic autoloader and Rails 6.0.0 with Zeitwerk.
Test
Classic
Zeitwerk
Percent
categories-50
32
26
81.25
categories-75
37
29
78.38
categories-90
47
35
74.47
categories-99
67
49
73.13
home-50
30
29
96.67
home-75
37
31
83.78
home-90
44
40
90.91
home-99
67
52
77.61
topic-50
35
35
100.00
topic-75
36
36
100.00
topic-90
48
36
75.00
topic-99
57
58
101.75
categories_admin-50
51
48
94.12
categories_admin-75
62
50
80.65
categories_admin-90
89
66
74.16
categories_admin-99
135
101
74.81
home_admin-50
48
47
97.92
home_admin-75
58
49
84.48
home_admin-90
67
64
95.52
home_admin-99
101
81
80.20
topic_admin-50
48
48
100.00
topic_admin-75
55
49
89.09
topic_admin-90
63
65
103.17
topic_admin-99
92
69
75.00
load_rails
2617
2165
82.73
rss_kb
282428
315684
111.78
pss_kb
270491
303504
112.20
Results are not always consistent so I would take them with a grain of salt.
sam
(Sam Saffron)
2019 年 9 月 15 日午前 4:54
4
The amount of inconsistency on the median here is a bit odd, I wonder why results are fluctuating so much
Surprised loader would have any impact
I tried it once again, here are results
Test
Classic
Zeitwerk
Percent
categories-50
25
25
100.00
categories-75
26
26
100.00
categories-90
37
33
89.19
categories-99
57
48
84.21
home-50
26
26
100.00
home-75
27
28
103.70
home-90
38
35
92.11
home-99
60
50
83.33
topic-50
27
26
96.30
topic-75
35
27
77.14
topic-90
41
33
80.49
topic-99
54
50
92.59
categories_admin-50
48
50
104.17
categories_admin-75
60
61
101.67
categories_admin-90
76
71
93.42
categories_admin-99
122
122
100.00
home_admin-50
47
46
97.87
home_admin-75
58
55
94.83
home_admin-90
66
63
95.45
home_admin-99
99
121
122.22
topic_admin-50
50
49
98.00
topic_admin-75
62
50
80.65
topic_admin-90
72
65
90.28
topic_admin-99
103
74
71.84
load_rails
2675
2216
82.84
rss_kb
279924
315240
112.62
pss_kb
267659
303026
113.21
We can try another way to benchmark. What would you say about more iterations, something which would run for an hour? In addition, instead of taking the best result, compare the average from each experiment. That may give more consistent numbers. What do you think?
In my pull requests for plugins, you will notice that a lot of fixes are around searching in the global namespace.
I changed code like
module ::Jobs
class TranslatorMigrateToAzurePortal < Jobs::Onceoff
to
module ::Jobs
class TranslatorMigrateToAzurePortal < ::Jobs::Onceoff
One thing bothered me about that solution, why was it working before Zeitwerk. That question when something works but shouldn’t is always tricky
I think I found a potential answer in the description of the classic autoloader (https://guides.rubyonrails.org/autoloading_and_reloading_constants_classic_mode.html#resolution-algorithms ) - “If not found, then the algorithm walks up the ancestor chain of the cref”
Zeitwerk is more strict. Once I tried to load code before fix it was complaining that Jobs::Jobs::Onceoff can not be found.
Sam suggested under pull request FIX: Use top-level namespace for base classes · discourse/discourse-prometheus-alert-receiver@ef9c238 · GitHub , that instead of using < ::Jobs::Onceoff we can just use < Onceoff and he is right. I checked that without namespace it works as well. I am thinking that giving :: is explicitly saying that we are inheriting from Discourse Core class, however, we can go either way.
sam
(Sam Saffron)
2019 年 9 月 17 日午前 6:15
7
I think that when the code is this close together, this reads great:
module ::Jobs
class TranslatorMigrateToAzurePortal < Onceoff
If the code starts floating down being explicit makes more sense … eg:
module ::Jobs
[ 50 lines omitted]
class TranslatorMigrateToAzurePortal < ::Jobs::Onceoff
That said, I am on a fence here, so I am fine either way. ::Jobs::Onceoff is short enough and mega explicit so we can just go with that for now.
sam
(Sam Saffron)
2019 年 10 月 2 日午前 12:59
8
I would love to start getting this merged. @kris.kotlarek is this now in a merge-able state? Timing is quite good cause we just had a beta
master ← KrisKotlarek:zeitwerk-inflector
merged 04:01AM - 02 Oct 19 UTC
Update from Classic to Zeitwerk autoloader.
**This pull request is a little … bit risky, so please don't merge in a super busy period.**
Could you review if that looks fine?
Before merging that, I think that it would be good to merge fixes for plugins:
https://github.com/discourse/discourse-translator/pull/25
https://github.com/discourse/discourse-canned-replies/pull/51
https://github.com/discourse/discourse-cakeday/pull/29
https://github.com/discourse/discourse-voting/pull/45
https://github.com/discourse/discourse-perspective-api/pull/4
https://github.com/discourse/discourse-calendar/pull/4
https://github.com/discourse/discourse-prometheus/pull/4
What can go wrong?
- If I missed any `::` in front `Jobs::Onceoff`, `Jobs::Base` or `Jobs::Scheduled` it may crash
- I changed Demon::Base to Demon::DemonBase, so any third party plugin relaying on that class needs to be updated
- If I miss any typos like here: https://github.com/discourse/discourse/pull/8098/commits/1050ab0440dbad1f1f0d77e8ea8558ab9b552040
Details about approach are documented here: https://meta.discourse.org/t/upgrading-discourse-to-zeitwerk/128337
Let me rebase recent master into that and ensure that it still works, I will do that today
@sam I think we are good to go, I rebased with the latest master and made a small adjustment to Webauthn.
I checked 3 things:
run the server on local and click a little bit to ensure it works as expected
run specs
downloaded all official plugins and ensured that specs for plugins are passing (we need to merge adjustments for plugins first)
sam
(Sam Saffron)
2019 年 10 月 2 日午前 4:00
12
Cool I am merging this. Let’s see what shakes out!
Can we merge fixes for plugins as well? Otherwise, if you try to run Discourse with plugins it will fail
master ← KrisKotlarek:zeitwerk
merged 04:31AM - 02 Oct 19 UTC
Zeitwerk is failing when we are searching for Jobs::Onceoff and Jobs::Base witho… ut going back to the top-level namespace (this is correlated to the fact that we got Onceoff base class and module with the same name).
I noticed that in plugins we are using sometimes :: and sometimes not and that gives me comfort that this change will not break anything.
Travis error: https://travis-ci.org/discourse/discourse/jobs/585072364
https://github.com/discourse/discourse-canned-replies/pull/51
https://github.com/discourse/discourse-cakeday/pull/29
https://github.com/discourse/discourse-voting/pull/45
https://github.com/discourse/discourse-perspective-api/pull/4
https://github.com/discourse/discourse-calendar/pull/4
https://github.com/discourse/discourse-prometheus/pull/4
sam
(Sam Saffron)
2019 年 10 月 2 日午前 4:29
14
Please go ahead and merge away!
sam
(Sam Saffron)
2019 年 10 月 2 日午前 5:19
16
This is now merged! If any plugin authors are struggling here, let us know in a new dedicated topic!
Great work Kris!!!