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

週刊Railsウォッチ(20210208前編)Rails次期リリースがバージョン7に決定、thoughtbotのアプリケーションセキュリティガイドほか

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

今回は以下のコミットリストのChangelog変更から見繕いました。

🔗 Action Cableクライアントの"thundering herd"を防止


つっつきボイス:「thundering herdって何だろう?」

herd n. 家畜[動物]の群れ; 《軽蔑》 (同じような行動などをする)群集.

「ググったら出てきた↓」「へ〜、こういう状態をthundering herdって言うのか!」「マルチスレッドなプログラムで何も考えずにシグナルを使ったときの現象に似てますね: シグナルマスクせずにシグナルを送ると配下の全スレッドがシグナルに応答してしまうので、どれか一つが応答すればよいだけの場合には無駄が大きいんですが、そのようなメッセージバスを共有するモデルに共通する問題に近いかもしれませんね」

参考: WebサーバでのThundering Herdは過去の話? | monolithic kernel

Thundering Herdというのは、ひとつのソケットに対してselectやepollのような通信可能になるのを待つシステムコールを利用して複数のプロセス (またはスレッド) が待機していると、通信可能になったときに本来ならひとつのプロセスだけが起きればいいところを、待機していたすべてのプロセスが起こされてしまうという問題です。この場合、実際に通信できるのはたまたま最も早く通信を開始したプロセスのみで、他のプロセスが起きたことは無駄になってしまいます。
blog.mono0x.netより

参考: Thundering herd problem - Wikipedia

「このプルリクはAction Cableクライアントが対象なので、そこで発生する可能性のあるthuntering herdとやらを修正するためのものなんでしょうね」「あ〜なるほど」「Action Cableで膨大な数のセッションを管理しているときにこれが起きるとかなりのパフォーマンス低下につながる可能性があるので、こういう問題が解決されるのはよいと思います👍」

「プルリクのコードを覗いてみるとJavaScriptのコードの追加が多いですね」「Changelogを見ると再接続と書かれているので↓、やはりクライアント側の修正なんでしょうね」

Action Cableクライアントに、サーバー接続が喪失した後のクライアント再接続における"thindering herd"を防止する以下のセーフガードが含まれるようになった。

  • クライアントは、サーバーへの直近のpingの後から最初の再接続試行までの間、staleスレッショルドのランダムな期間でウエイトをかける。
  • 以後の再接続試行では、(対数的バックオフではなく)指数的バックオフを用いるようになった。再接続試行までの遅延時間の増加を当初ゆるやかにするために、デフォルトの指数の基数部は2より小さい値になっている。
  • 再接続の試行と試行の間の遅延時間には、それぞれランダムなジッターが適用される
    Changelogより大意

参考: 仮数部、指数部、基数:意味と計算方法 - 具体例で学ぶ数学

「なるほど、以前も話したランダムなジッター(jitter)を遅延時間に加えているのか(ウォッチ20191216): これはJavaScriptの変更ですが、サーバー側にも影響するものですね」「おぉ?」「たくさんのクライアントが再接続を同時にかけてくるときは、ランダムなジッターを加えてアクセスが同時に集中しないようにするという手法です」「あ〜、思い出してきました」

参考: ジッター - Wikipedia

「ジッターを加えることでクライアントの再接続が成功する可能性も高まりますし、再接続するまでの時間も短縮できますね」「なるほど!」「ジッターがないと、多数のクライアントが同じ時刻に一斉に再接続をかけてきてしまいます」「これは勉強になりました」

「この問題自体はWebSocketの話のようですが、クライアントサーバー形式の通信では普遍的に起きうる現象ですね」「なるほど」「なお、このプルリクにはbackoff timerのアルゴリズム修正も入っているようですね: プルリクのコメントでかなり細かく具体的なケースのやり取りがされているので、興味ある人はコメントも読んでみると面白いと思います」


特に必要はないと思いますが、thundering herdという現象をうまく表せる既存の熟語が見当たらないので、英語ママとしました。

どうやら元ネタはZane Gray作の西部劇小説「Thundering herd」のようで、以下の映像にはBuffalo Stampedeというサブタイトルが付いていました。要するに時代劇ですね。

参考: ゼイン・グレイ - Wikipedia

🔗 Rails 6.2がRails 7になった


つっつきボイス:「@rafaelfrancaさんのコミットです」「え、Rails 6.1の次はもうRails 7になるんですか?」「どんな目的なんでしょうね?」「昨年末にリリースしたHotwireを入れるのかな?🤔」

# Gemfile.lock#L30
PATH
  remote: .
  specs:
-   actioncable (6.2.0.alpha)
-     actionpack (= 6.2.0.alpha)
-     activesupport (= 6.2.0.alpha)
+   actioncable (7.0.0.alpha)
+     actionpack (= 7.0.0.alpha)
+     activesupport (= 7.0.0.alpha)
...

速報: Basecampがリリースした「Hotwire」の概要

「6.0から6.1のようにマイナーバージョンが進むときって、基本的には大きく変わらないものだという通念がありますけど、Railsの場合マイナーバージョン番号が更新されるときにも大きな変更が時たま入ってきたりしますよね」「そう思います😆」「考えてみれば、Rails 6.1は6.0と比べてだいぶ変わりましたよね」「自分でも6.1に上げたときに変更点が割と多い印象でしたね、個人的には大きな支障はありませんでしたが」

「変更が大きかったといえば、やはりRails 3.0からRails 3.1のときでしょう」「そうそう!あれはキツかった」「アセットパイプラインがない世界から3.1でいきなりアセットパイプラインのある世界への移動は大きかった」「breaking changeとしてはでかい」

参考: アセットパイプライン - Railsガイド

「もしかするとrequired Ruby versionを変えるから7にするのかな?」「コミットのリリースノートを見ると↓、Rails 7ではRuby 2.7以上が必須、3.0以上が好ましいとなってる」「ホントだ」

  • Ruby 2.7.0+ required, Ruby 3.0+ prefered
    コミットのguides/source/7_0_release_notes.mdより

「そういえば、ちょうど今年1月の銀座Railsで、Ruby 3.0とRails 6.1のバージョニングについて@yahondaさんが話してたのを思い出しました↓」

参考: 【オンライン開催】銀座Rails#29@リンクアンドモチベーション - connpass

「スライドにも、required Ruby versionが上がるのは少なくともRails 5以降はRailsのメジャーバージョンアップのみ、とありますね」「へ〜!」「これを元に推測するなら、メジャーバージョンアップを決めた背景にrequired Ruby versionを変更したかったというのがあるのかもしれませんね」

「ところで、このコミットのコミットメッセージに"big plans"と書かれているんですが↓、具体的な情報は今のところまったく見当たりませんでした」「big planですか」「big planが何なのかは手がかりなしです」

We have big plans for the next version of Rails and that require big versions.
同コミットより

🔗 disallow_raw_sql!で末尾ホワイトスペースを削除するようになった


つっつきボイス:「なるほど、pluckの末尾に\nなどが入っているとエラーになるので、削除して動くようにしたんですね↓」「末尾のホワイトスペースならメソッド側で除去しても大丈夫そう」

# 変更前
User.pluck("first_name, last_name") #=> SELECT first_name, last_name FROM "users"
User.pluck("first_name, last_name\n") #=> ActiveRecord::UnknownAttributeReference
# 変更後
User.pluck("first_name, last_name") #=> SELECT first_name, last_name FROM "users"
User.pluck("first_name, last_name\n") #=> SELECT first_name, last_name\n FROM "users"

参考: ホワイトスペースとは - IT用語辞典 e-Words

「Railsのpluckに改行入り文字列を渡したことってなかったかも」「自分はpluckで複数カラムを指定するときにカンマ区切り文字列ではなく複数のシンボル引数を渡すようにしているので、このケースでハマることはありませんでしたね」

Rails: pluckでメモリを大幅に節約する(翻訳)

🔗 新機能: primary_abstract_class


つっつきボイス:「タイトルにexposeと書かれているのでprivateなメソッドをpublicにしたのかと思ったら、既存のself.abstract_class = trueprimary_abstract_classで明示的にpublicメソッドで書けるようにしたようですね」

参考: [Rails] self.abstract_class = true の意味と挙動 « Codaholic
参考: Active Record で複数のデータベース利用 - Railsガイド

# Railsガイドより
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :primary_replica }
end

self.abstract_class = trueって使ったことなかったかも...」「この書き方は、Railsのマルチプルデータベースなどで使うことがあります: データベースごとにベースクラスを作るけど、そのクラスは直接newさせたくないとき、つまりActive Recordのクラスとして直接使って欲しくないとき」「あ、今理解しました😅」

「コードの中でも最終的にself.abstract_class = trueを呼んでいる↓」

      def primary_abstract_class
        if Base.application_record_class && Base.application_record_class != self
          raise ArgumentError, "The `primary_abstract_class` is already set to #{Base.application_record_class}. There can only be one `primary_abstract_class` in an application."
        end

        self.abstract_class = true
        Base.application_record_class = self
      end

「考えてみれば、従来のself.abstract_class = trueのように従来のクラス変数を直接上書きする方式は、Railsガイドに書かれているとはいえやや直接的すぎるという見方もできるので、こうやってメソッドで設定する方がよさそうな気はします」「従来どおりにself.abstract_class = trueを書くこともできそうに見えるけど、メソッドができたなら今後はこれで設定する方がいいのかもしれませんね」


アプリケーションでprimary_abstract_classを設定する方法を提供する。
マルチプルデータベースのRailsアプリケーションで、名前がApplicationRecordでないプライマリ抽象クラスを使っている場合に、特定のクラスをprimary_abstract_classに設定できるようになった。

class PrimaryApplicationRecord
  self.primary_abstract_class
end

アプリケーションが起動すると、自動的にプライマリまたはデータベースコンフィグファイルの最初のデータベースに接続する。マルチプルデータベースでその後connects_toを実行するには、デフォルトのコネクションが ApplicationRecordのコネクションと同じであることを認識する必要がある。しかしアプリケーションによっては ApplicationRecordの名前が異っていることもある。この変更によって、Active Recordが同じデータベースに対してコネクションを複数オープンしてしまうことが防止される。
同Changelogより大意

🔗 Active Storageで定義済みのvariantを使えるようになった

# 同PRより
class User < ActiveRecord::Base
  has_one_attached :avatar, variants: {
    thumb: { resize: "100x100" },
    medium: { resize: "300x300", monochrome: true }
  }
end

class Gallery < ActiveRecord::Base
  has_many_attached :photos, variants: {
    thumb: { resize: "100x100" },
    medium: { resize: "300x300", monochrome: true }
  }
end

<%= image_tag user.avatar.variant(:thumb) %>

つっつきボイス:「Active Storageでようやくvariantを定義して、上のvariant(:thumb)のように名前で呼べるようになったんですね」「variantとは?」「上のコードのthumbmediumのような、Active Storageで扱う画像のサイズの種類のことですね: 100x100などがvariantの設定」「あ、それのことでしたか」

「なお、これはpaperclipやshrineといったgemには普通にある機能です」「そうそう」「Active Storage自体にはvariantの機能が入っていますが(ウォッチ20200114)、この改修が追加されたということは定義済みのvariantを使う機能がこれまでなかったということなんでしょうね」「こうやって他のgemの機能も取り入れてActive Storageがよくなっていくということですね」

thoughtbot/paperclip - GitHub

shrinerb/shrine - GitHub

🔗Rails

🔗 銀座Railsスライド『Active Recordから考える次世代のRuby on Railsの方向性』


つっつきボイス:「上は以前Railsdmで評判を呼んだ『Ruby on Railsの正体と向き合い方』↓の@_yasaichiさんが銀座Rails#29で発表したスライドです」「あれは2年ぐらい前でしたっけ?」「そういえばウォッチでも取り上げたときもとても評判がよかったですね(ウォッチ20190401)」

「前回のスライドは、Ruby on Railsの歴史的経緯を詳しく説明してから、それを踏まえてRailsが今後どう進んでいくのかを考察する内容になっていましたが、今回はフロントエンドも含めて同様の考察を行っています」「おぉ〜!」「Railsのフロントエンド周りの将来像は、Railsをやってる人たちなら誰でも気になるところですね」「これは後で読まなきゃ」「Railsのまとめ方なども含めていろいろ参考になります: RailsをやっていればRailsについて思うことはいろいろあるわけですが、今回のスライドもRailsに入れ込みすぎず突き放しすぎない視点でRailsの現状と将来像を捉えようとしているのがとても参考になります🙏」

「なおこの間の銀座Rails29はその後の懇親会がディープな話で盛り上がりましたね」

🔗 1個のファイルでRailsアプリを書く(Ruby Weeklyより)


つっつきボイス:「1個のファイルでRailsアプリを書く...とは?」「1個のファイルでRails書けるかどうかチャレンジするってことかしら?」「routes.rbにlambdaを書いておしまいにするってこと?それだともうRailsではないですよね」「たしかに」

「ああ、Railsのconfig.ruファイルに全部書くということか↓」「あ、理解しました!」「これスクショなのか...」


同記事より

「通常ならapplication.rbやらroutes.rbやらのファイルに分かれているのを、ここではRails::Applicationを継承したクラスを作ってそこに全部書くということらしい」「へ〜!」「スゴいことしますね」「routes.appendは本来routes.rbで行われますけど、そういうのもここに書かれている」

「やろうと思えばRailsでこんなこともできるという記事」「さすがに本番でやろうとは思いませんでした😆」「本当にRailsがシングルファイルアプリになるなら別の意義がありそうですが、この記事のは実用目的ではなさそうかな」

参考: Rails の初期化プロセス - Railsガイド -- config.ruについても説明されています

🔗 thoughtbotの「アプリケーションセキュリティガイド」


つっつきボイス:「thoughtbotの記事を追っててこのセキュリティガイドを見つけました」「日付は昨年7月なのか」「それほど長くなさそう」

「へ〜、Personally-Identifying Information (PII)という言葉があるのか: どこまで一般的なものかどうかはわかりませんが、このような個人を特定可能な情報の扱いに注意することは大事ですね」「たしかに」

参考: 個人を特定できる情報 (PII = Personally Identifiable Information) とは何か? - Twilio

「この記事で説明されていることは、セキュリティを考えるうえでどれも当たり前の話ではありますが、こうやってリスト形式で簡潔にまとめられていることで、改めて読んでみると自分の理解が不足している部分を見つけられたりするのがいいと思います👍」「そうですね」「何となくわかっているつもりのことでも、自分がこの記事のように説明できない部分を見つけたら、改めて調べて勉強しないといけない気持ちになれますね」「自分も頑張らなきゃ」

🔗 Google Cloud Functions


つっつきボイス:「この間のウォッチでも報じたGoogle Cloud Functionsのことですね(ウォッチ20210126)」「そうそう、ツイートにもあるようにRubyで使うbundlerのbundle installをGCP側でやってくれるんですよ」「それ優秀じゃないですか!」

参考: Bundler: The best way to manage a Ruby application's gems

「簡単なRubyコードを動かす分にはGoogle Cloud Functionsの方がAWS Lambdaよりも便利だと思います👍」「お〜」「AWS Lambdaだと、Lambdaレイヤの状態を考慮しないといけないなど、デプロイに多少ノウハウが必要なんですよ」「なるほど」「コンテナでデプロイするなら別ですけど、場合によってはコンテナだとオーバーキル気味になることもあります」

「RubyをサポートするようになったGoogle Cloud Functionsのいいところは、Rubyのソースコードをそのままアップロードして公開できる点だなと思います」

参考: Cloud Functions  |  Google Cloud

参考: AWS Lambda(イベント発生時にコードを実行)| AWS

🔗 その他Rails

つっつきボイス:「4月にオンライン開催予定のRailsConf 2021の登壇者募集のお知らせです」「CFP(Call for proposal: 登壇者募集)の締め切りが今月15日、ってもうすぐなのか」「発表したい人はどうぞ」

「お、募集ページにCFPのstatsが載ってますね↓」「これは?」「何日に何人応募があったかというグラフ」「こういうグラフはたいてい締切日になるとガガっと上昇します😆」「そういうものですよね、昔から」


同サイトより

参考までに、昨年のRailsConf2020のキーノートスピーチ動画を貼っておきます。


前編は以上です。

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

週刊Railsウォッチ(20210202後編)Ruby 3 irbのmeasureコマンド、テストを関数型言語のマインドセットで考えるほか

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

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

Rails公式ニュース

Ruby Weekly


CONTACT

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