Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ(20210406前編)GitHubが修正したRailsセッションハンドリングの競合、erb/haml/slimの速度比較ほか

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Rails: 先週の改修(Rails公式ニュースより)

今回は以下のコミットリストから見繕いました。Changelog更新はほとんどありませんでした。


つっつきボイス:「先週mimemagicの件があったせいか今週の変更は少なめでした」

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

🔗 マイグレーション生成の--pretendオプションのエラーを修正

# railties/lib/rails/generators/actions/create_migration.rb#L22
        def invoke!
+         return super if pretend?
+
          invoked_file = super
          File.exist?(@destination) ? invoked_file : relative_existing_migration
        end

つっつきボイス:「マイグレーションに--pretendオプションがあるとは知らなかった」「dry run的なものかな?」

後でbin/rails g migration --helpして確認しました。

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist

また、手元のRails 5.2.5ではマイグレーションの--pretendはエラーにならず、Rails 6.1.3.1だとこのプルリクにかかれているのと同じエラーになりました。

🔗 ActiveSupport::NumericWithFormat#to_sを最適化


つっつきボイス:「to_snilチェックを移動して早期脱出させたようですね」

# activesupport/lib/active_support/core_ext/numeric/conversions.rb#L109
    def to_s(format = nil, options = nil)
+     return super() if format.nil?
+
      case format
-     when nil
-       super()
      when Integer, String
        super(format)
      when :phone
        ActiveSupport::NumberHelper.number_to_phone(self, options || {})
      when :currency
        ActiveSupport::NumberHelper.number_to_currency(self, options || {})
      when :percentage
        ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
      when :delimited
        ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
      when :rounded
        ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
      when :human
        ActiveSupport::NumberHelper.number_to_human(self, options || {})
      when :human_size
        ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
      when Symbol
        super()
      else
        super(format)
      end
    end

case format when nilは最終的にNilClass === nilを呼ぶので非常に効率がよい。これはObject#==のエイリアスなので、基本的には最終的にnil == nilとなる。
しかしformat.nil?は専用のopコードのおかげで著しく速い。
この場合Integer#to_sは引数なしで呼ばれることが非常に多いので、頻度が最も高いケースについて最適化する価値がある。
同PRより大意

🔗 issue: Rails 5.2.5とCSRFトークンフォーマット

mimemagic に依存しなくなった rails 5.2.5 に CSRF トークンのフォーマットが Urlsafe Base64 になる変更が(意図せず?)入っているようで 5.2.5 で生成されたセッションが 5.2.4.x 以下 or 6.0.x で読めなくなってしまっているようなので rails 5.2.5 と 6.1.x 以外の rails を組み合わせて使っている人は要注意です。
hackmd.io『mimemagicの最新動向』より

参考: mimemagicの最新動向 - HackMD


つっつきボイス:「mametterさんやruby-jp Slackの有志がまとめてくださった上のmimemagic動向記事に追記されていたのを見て知りました」「Rails 5.2.5でRailsがmimemagicに依存しなくなったときにCSRFトークンフォーマットが変わってしまったということですね」

「Railsサーバーコンテナを複数動かして段階deployする場合は、deployが完了するまで旧フォーマットで動くRailsサーバーと新フォーマットで動くRailsサーバーが混在してエラーレートが跳ね上がる可能性があるので、規模の大きなサービスほど問題になるでしょうね(#41783コメント)」「なるほど」「mimemagicの件でアップグレードする時にチェックしておく必要はあると思います」「Rails 5.2向けに#41797がオープンしてますね↓」

🔗Rails

🔗 rails_multisite: Discourseから切り出されたRailsマルチサイトgem(Ruby Weeklyより)

discourse/rails_multisite - GitHub


つっつきボイス:「DiscourseのRailsから切り出されたマルチサイトgemだそうです」

参考: Discourse - Civilized Discussion

「以下のようにホスト名ベースでデータベースを切り替えられるようですね↓」

# config/multisite.yml

db_one:
  adapter: ...
  database: some_database_1

db_two:
  adapater: ...
  database: some_database_2

参考: Active Record で複数のデータベース利用 - Railsガイド

「いわゆるマルチテナントのRailsアプリで、データベースもテナントごとに分けたいというときに使うのかな?案件の事情によってはこういう設計にしないといけない場合もあるのかもしれないけど、マイグレーションやデプロイが大変そうですし、このようにひとつのRailsアプリでデータベースをテナントごとに分けるよりもRailsアプリのインスタンスを分ける方が一般的かなと思いました」「それもそうですね」「自分たちが運用することを考えるとアプリを分ける方が楽だと思います」

参考: Apartment でマルチテナントサービスを作成する - Qiita

🔗 GitHubが発見・修正したRailsセッションハンドリングの競合(Ruby Weeklyより)


つっつきボイス:「GitHubの技術ブログです」「この図がポイントかな↓」


同記事より

「Railsのセッションcookieでレアなrace conditionが発生したということらしい」「race conditionの解決は大変そう」「記事冒頭を見ると、少し前にGitHubからユーザーが突然ログアウトさせられた件について書かれていますね: そのときの修正内容の解説なのか」「あ、そういえば1か月ほど前にそんなことがありましたね」

参考: GitHub security update: A bug related to handling of authenticated sessions - The GitHub Blog

「これは読んでみてもよさそう👍」「もう少ししたらGitHubブログの日本語版にも公開されるかもしれませんね↓」

参考: GitHubブログ - 製品アップデートや開発に関するアイディアやインスピレーションなど、エンジニアの皆さんに役立つ情報を発信します。

🔗 erbとhamlとslimの速度を比較してみた


つっつきボイス:「単純に出力するならerbが一番速いんじゃないかな?」「条件を変えたりして測定してますね」

haml/haml - GitHub

slim-template/slim - GitHub

「ループの中で毎回newしないで、以下のように最初にnewしたインスタンスを使い回せば速くなる↓、たしかに」

# 同記事より
erb_engine = ERB.new(erb_example, 0, '-', '__result')
slim_engine = Slim::Template.new { slim_example }
haml_engine = Haml::Engine.new(haml_example)

Benchmark.bmbm(10) do |bcmk|
  bcmk.report("erb_test") { (1..2000).each { erb_engine.result binding } }
  bcmk.report("slim_test") { (1..2000).each{ __result = slim_engine.render(context) } }
  bcmk.report("haml_test") { (1..2000).each { __result = haml_engine.render(binding) } }
end

「ただ、CMSなどでキャッシュを一括生成するような場合なら上のように書けるでしょうけど、通常のWebレンダリングではこういう書き方はできないと思います」「あ、それもそうか」

「記事末尾にある現実的な例だと予想通りerbが一番速い↓」「そんなに大きくは違わなそうですね」

# 同記事より
$ hey -n 1200 http://localhost:3000/notes_erb/index

Summary:
  Total:        52.2586 secs
  Slowest:      19.2837 secs
  Fastest:      0.0389 secs
  Average:      0.6960 secs
  Requests/sec: 22.9627

$ hey -n 1200 http://localhost:3000/notes_haml/index
Summary:
  Total:        61.7637 secs
  Slowest:      18.5290 secs
  Fastest:      0.0442 secs
  Average:      0.8557 secs
  Requests/sec: 19.4289

$ hey -n 1200 http://localhost:3000/notes_slim/index

Summary:
  Total:        63.1625 secs
  Slowest:      19.9744 secs
  Fastest:      0.0874 secs
  Average:      0.7959 secs
  Requests/sec: 18.9986

🔗 Railsのジェネレータを改造する(Ruby Weeklyより)


つっつきボイス:「Railsのジェネレータを自分で作ったことあったのを思い出した」「ジェネレータを作るのって大変そうですけど」「既存のジェネレータのコードを見ながらやれば作れますよ: erbが二重になっている箇所があってそこは少し面倒ですが」

「自分はジェネレータを手作りまでしようとはあまり思わないかな」「ジェネレータに手を加えたらRailsをアップデートしたときに困ったりしませんか?」「既存のジェネレータを上書きするならともかく、独自のジェネレータを作るなら影響は小さいと思います」「なるほど」「単純なジェネレータなら一度自分で作ってみると勉強になりますよ🎓」

🔗 RailsでキャッシュをクリアしたらSidekiqジョブが吹っ飛んた話(RubyFlowより)


つっつきボイス:「あ〜、redisのキャッシュをクリアしたらSidekiqジョブが飛んだのか、結構でかい事故」「これはキツそう」

mperham/sidekiq - GitHub

「redisはたしかデフォルトで16個のデータベースが使えて、キャッシュ用やジョブ用でデータベースを使い分けるものなんですが、それを同じデータベースに保存したらキャッシュをクリアした瞬間にジョブも消えるでしょうね」「う〜む」

参考: RedisのDB番号を増やす - tsunokawaのはてなダイアリー

redis/redis - GitHub

# 同記事より: Active Supportのコード
# Redisサーバーのすべてのキャッシュをクリアする。
# キャッシュが名前空間化されていれば共有サーバーでも安全。
#
# Failsafe: Raises errors.
def clear(options = nil)
  failsafe :clear do
    if namespace = merged_options(options)[:namespace]
      delete_matched "*", namespace: namespace
    else
      redis.with { |c| c.flushdb }
    end
  end
end

「元記事ではredisを名前空間化して修正したとあるけど↓、Redisのdatabase idを分ける方がいいんじゃないかな…」

# 同記事より
# config/application.rb
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"], namespace: "rails" }

🔗 その他Rails

つっつきボイス:「お〜、Rubyのバックトレースをフィルタできるんですね」「add_silencerはどこかで見たことあったかも」

# api.rubyonrails.orgより
bc = ActiveSupport::BacktraceCleaner.new
bc.add_filter   { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
bc.clean(exception.backtrace) # perform the cleanup

前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210330後編)Active Recordモデル属性暗号化が標準で入る可能性、Flipper Cloud、awesome_printほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。