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

週刊Railsウォッチ: Ruby 3.2.0devにRust版YJITがマージ、Docker Compose V2ほか(20220511後編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 Rust版YJITがRuby 3.2.0devにマージ


つっつきボイス:「YJITがRustで?!」「Shopifyがまたすごいことをやった」「差分もすごいボリューム」「今年最大級の成果が4月に出るとは」

YJIT: CRuby向けの新しいJITコンパイラを構築する(翻訳)

「ちょうど@koicさんもRust YJITをビルドしてみた記事を公開しましたね↓」「早い!」「同記事によると、Ruby 3.2.0devでYJITをRustでコンパイルするには、Rustの処理系を用意したうえで--enable-yjit=devオプションを指定してdev/debugモードでビルドする必要があるそうです」「--enable-yjitだとreleaseモードでCRust版YJITをビルド、無指定だとYJITなし」

参考: Hello, Cargo! - The Rust Programming Language 日本語版
参考: Getting started - Rust Programming Language

「YJITをC言語で書いているうちは今後のメンテナンスや拡張が相当難しくなることは想像がつくので、Shopifyが今のうちにYJITをRustに移行して技術的負債を返済しておきたいと考えるのは理解できる」「ちなみに早くもRust YJITの勉強会が行われたそうです↓」

なお、上のスライド冒頭に記載されているhttps://github.com/Shopify/ruby/tree/rust-yjit-upstreamingは現在削除されています。

🔗 RubyのカスタムRange(Ruby Weeklyより)


つっつきボイス:「thoughtbotの記事です」「RubyのカスタムRangeとは?」「以下のようにMonthクラスに<=>succを実装する↓: こういうカスタマイズはときどき使うことがあります」

# 同記事より
class Month
  # 他のメソッド

  def <=>(other)
    months <=> other.months
  end

  def succ
    self.class.new(months.succ)
  end
end

「Rangeオブジェクトでは少なくとも<=>succを使うので、MonthをRangeの..演算子で使うときはこれらを実装することになります」「rangeableとあるのはそういうことなんですね」

# 同記事より
Month.from_parts(2021, 10)..Month.from_parts(2022, 3)
# => [October 2021, November 2021, December 2021, January 2022, February 2022, March 2022]

参考: class Range (Ruby 3.1 リファレンスマニュアル)

「これと似たような話はEnumerableにもありますね: カスタムクラスでEnumerableをインクルードしてeachを実装すればenumerable機能が使えるようになる」「そうそう」

参考: module Enumerable (Ruby 3.1 リファレンスマニュアル)

🔗 Ruby 3.1に導入された可変長アロケーション(Ruby Weeklyより)


つっつきボイス:「可変長アロケーションの話題は昨年Ruby 3.1が出る前にもしましたね(ウォッチ20210804)」「サイズプールのスロットサイズが必要に応じて40/80/160/320バイトみたいに可変になるらしい↓」「データベースのメモリ管理でもこんな感じのアロケーション戦略が使われていたりしますね」


同記事より

🔗 Rubyの遅延評価


つっつきボイス:「Rubyでlazyを付けるとどうなるかという解説のようですね」

# 同記事より
data = ["one", "two", "three"]
data2 = ["four", "five", "six"]
pipeline = data
  .lazy
  .map { |item| puts "item: #{item}"; item.reverse }
  .take_while { |item| puts "item: #{item}"; item.length < 6 }
  .zip(data2)

p pipeline.class
p pipeline.to_a

参考: class Enumerator::Lazy (Ruby 3.1 リファレンスマニュアル)

lazyはパフォーマンス向上というよりは、早期復帰させたいときに使うことが多いかな」「ふむふむ」「重い処理にlazyを付けただけで速くなるというわけではありませんが、遅延させた重たい処理を結果的に実行せずに済んだ場合は確実に速くなる」「記事でも、lazyする処理を早期復帰させると速くなる例がありますね↓」

# 同記事より抜粋
$x.lazy.map { _1.reverse }.take(50_000).to_a # 遅い
$x.lazy.map { _1.reverse }.first(5)          # 速い

🔗 その他Ruby


つっつきボイス:「@jnchitoさんのRuby/Rails記事は本当に多いですね」「コードレビューして気になった部分を記事にすることに決めているのかなと想像してみました」「自分たちもコードレビューでこうした点を指摘したりしますけど、毎回ここまで丁寧に書いているのは頭が下がる」「優しさを感じますね」

🔗DB

🔗 RailsのテストでPostgreSQLの時刻をフェイクする(Ruby Weeklyより)


つっつきボイス:「timecop gem↓やRailsのtravel_toとかでやるような時刻がらみのテストで、RDBMS依存の時刻処理も含めてフェイクしたいということかな」

travisjeffery/timecop - GitHub

Rails: Timecopを使わなくても時間を止められた話

「SQLのnow()だとそういうテストができないので、nowish()というカスタム関数をPostgreSQLに追加してやってるんですね↓」「なるほどたしかに」「nowish()、わかるけどすごい名前」

# 同記事より
class CreateNowishFunction < ActiveRecord::Migration[7.0]
  def up
    # Written to mimic the shape of `pg_catalog.now()':
    #
    # CREATE OR REPLACE FUNCTION pg_catalog.now()
    #  RETURNS timestamp with time zone
    #  LANGUAGE internal
    #  STABLE PARALLEL SAFE STRICT
    # AS $function$now$function$
    #
    execute <<~SQL
      CREATE OR REPLACE FUNCTION public.nowish()
      RETURNS timestamp with time zone
      AS
      $$
      BEGIN
      RETURN pg_catalog.now() + (select global_time_offset_seconds
        from public.system_configurations
        limit 1
      ) * interval '1 second';
      END;
      $$
      LANGUAGE plpgsql STABLE PARALLEL SAFE STRICT;
    SQL
  end

  def down
    execute "drop function public.nowish()"
  end
end

「記事ではこのnowish()を本番で使うとパフォーマンスが悪化するので通常のnow()を使うようにとも書かれてる」「nowish()の中でトランザクション使ってるのでたしかに遅くなりそう」

「記事ではnowish()をマイグレーションでスキーマに追加していますけど、デフォルトでこれが使われたらバルクINSERTですごい頻度で呼び出されてさらに遅くなるかも」「もしかすると自分now()呼びまくってたかも😅」「now()なら高速なので問題ないと思いますよ: このnowish()みたいに遅い処理がデフォルトで呼ばれないようにするのが大事」「なるほど」


「テストで時間をフェイクしたいニーズはたしかにありますね」「たとえば外部のAPIサービスなどが営業日や営業時間で振る舞いが変わるのをテストしたいとか」「営業時間終了後のバッチは翌日実行するみたいな要件のテストとか」「キャンペーンのような期間限定の単発機能をテストするとか」「本番はともかく、テストで時間をフェイクするのはかなり大変」「システム時刻をわざと数日進めたテストサーバーも用意して、時間がらみのバグを事前にキャッチするようにしたこともあります」「そんな方法もあるんですか」「でもこれをやると以後のメンテがつらくなるんですよ」

「そういうときに記事でやっているTravelsInTimeみたいなものが欲しくなるんでしょうね↓」「global_time_offset_secondsという名前からやりたいことが伝わってくる」

# 同記事より
class TravelsInTime
  def call(destination_time)
    Timecop.return
    set_pg_time!(destination_time)
    set_ruby_time!(destination_time)
  end

  private

  def set_pg_time!(destination_time)
    SystemConfiguration.instance.update!(
      global_time_offset_seconds: destination_time - Time.zone.now
    )
  end

  def set_ruby_time!(destination_time)
    Timecop.travel(destination_time)
  end
end

「こういう時刻のフェイクはどうしても必要なら作るけど、複雑になって理解が難しくなりがちなので、できれば作らずに済ませたいですね」「やりたくなる気持ちは痛いほどわかります」「この記事いっぱい学びあったけど、実際にやるとなると考えてしまいますね」

「この記事はデータベースレベルで時刻をフェイクしているだけなのでまだいいんですが、たとえば生成したファイルのタイムスタンプもフェイク時刻に同期させようとするとさらに大変」「あ〜そうかも」「そういうときについシステム時刻を変えたくなるんですが、実はシステム時刻を変えるとAWSのAPIアクセスに失敗するという問題がある」「それそれ!そうなんですよ」「たしか30分ぐらいだったかな?とにかく時刻が一定以上ずれると失敗します」

「一見すぐやれそうに見えるのに、今の時代だと時刻をずらすのがこんなに大変だったとは...」「時刻をずらすといろんな弊害が起きる可能性があることは知っておいて損はないと思います」

🔗クラウド/コンテナ/インフラ/Serverless

🔗 Docker Compose V2が正式リリース


つっつきボイス:「V2って既に使っていますけど、今まで正式じゃなかったんですか」「GAは今回からですね」「V1はPythonベースだったけどV2はGo言語ベースに変わった」

参考: GA版(General Availability)とは - IT用語辞典 e-Words

「GAリリースによって変わったことにひとつに、Compose V1のEOL(End Of Life)が確定したことがありますね」「いつですか?」「2023年4月からV2が必須になるので1年後です」「結構早い...」「そのときにはV1とV2を切り替えるcompose-switchも↓使えなくなります」

docker/compose-switch - GitHub

「今V1とV2のどちらなのかを調べるにはどうすればいいんでしたっけ?」「docker compose versionでわかりますし、そもそもV1ではハイフンありのdocker-composeだったのがV2ではハイフン無しのdocker composeに変わります」「う、自分V1だったか...」「docker-composeでV2を動かす方法も一応あるので微妙なんですが、原則としてハイフン無しならV2、ハイフンありならV1を目安にしてよいと思います」「これからはハイフン無しにしないと」

参考: Docker Compose V2で変わったdocker-compose.ymlの書き方

🔗 act: GitHub Actionsをローカルで実行

nektos/act - GitHub


つっつきボイス:「このactは今使ってますけど、ローカルで動かして確かめてからGitHub Actionsにプッシュできるので便利ですよ」「自分がact使ってみたときは思うように動かなかったなぁ」「そうそう、最初なかなか動かないんですけど、動き出すと便利」

参考: GitHub Actionsのローカル実行ツール「act」を使う事でCI/CDコンフィグとローカルでのタスクランナーを1つにする | DevelopersIO

「おそらくGitHub Actionsを何に使うかによるんでしょうね: GitHubの環境に強く依存しているものを扱うには不利なのかも」「確かにそれはありますね」「自分のときは、GitHub Actionsの中でコンテナイメージをビルドして、AWS ECRにプッシュして、Fargateにあるタスク定義のリビジョンを更新するという複雑なことをやってて、それこそローカルで試してからにしたい内容だったんですが思うようにいかなかった」「あ、それと同じことをちょうどやろうとしてます」

参考: Amazon ECR(Docker イメージの保存と取得)| AWS
参考: Amazon ECSの タスク定義 - Amazon ECS

「GitHubランナーの環境はたしかGitHub用のUbuntuで、いろいろカスタマイズされているので、ローカルのUbuntuで動かそうとすると細かな点に違いが見つかる」「そうなんですよ、ローカルだと動くのにプッシュすると動かなかったり」「actのREADMEにもランナーごとの環境の違いが載ってますね↓」


同リポジトリより

「自分のときは諦めて直接GitHub Actionsにプッシュしてテストしましたが、もちろん簡単な内容ならactで十分ローカルテストできると思います」「そうそう」「思いつきですが、最近話題になっているDagger↓とこのactを組み合わせられるか試してみたい気もしますね」

参考: dagger.io
参考: 話題の CI/CD ツール Dagger を体験してみる | 豆蔵デベロッパーサイト

🔗言語/ツール/OS/CPU

🔗 スライド: Chromiumのアーキテクチャ


つっつきボイス:「Chromium解説スライドです」「以前のChromiumはGCで動きがカクカクすることがあった話とかセキュリティとかDOMの話など、詳しく載ってますね」「生ポインタの参照カウンタの話でObjective-Cという言語を思い出した↓」

参考: Objective-C - Wikipedia

「スムーズに動いているように見せるのに必要な最小FPS、これも有名な話ですね↓」


同スライドより

「Chromiumのにはharfbuzz↓というライブラリが使われているのか」「テキストシェイプライブラリって初めて聞くかも」「このharfbuzzが、フォントのリガチャや改行を制御しながら表示位置を決めているんですね」

harfbuzz/harfbuzz - GitHub

参考: 合字 - Wikipedia -- リガチャ

「harfbuzzは、Linuxのcairoというグラフィックライブラリに含まれているPangoと似たようなことをしているみたいですね: 以前PDFにフォントを埋め込む必要があったときにこのあたりを調べたのを思い出しました」

参考: cairo - Wikipedia
参考: Pango - Wikipedia

「ところでどこかで見たスライドだと思ったら、一昨年に2020年版のスライドを見たことがありました↓」「なるほど、上のスライドはこのスライドの更新版でしたか」

参考: 電子情報学特論:Chromiumのアーキテクチャを解き明かす - Google スライド


後編は以上です。

バックナンバー(2022年度第2四半期)

週刊Railsウォッチ:(20220510前編)Active RecordにPromiseと非同期集計メソッドがマージ、climate_control gemほか

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

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

Ruby Weekly


CONTACT

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