こんにちは、バックエンドエンジニアの近です!
4/24〜4/26にかけてアトランタで開催されたRailsConf 2023にWEARバックエンドブロックから近・小山・高久の3人が参加しました。
去年はコロナの影響もあってオンラインの開催だったのですが、今年はオフラインでの開催となり、大勢が参加していて大盛況でした。
我々が開発・運営しているファッションコーディネートアプリ「WEAR」のバックエンドはRuby on Railsで開発しています。現在では、新機能の開発やリプレイスなど、チームメンバーの全員がRuby on Railsに関わっているため、今回RailsConfにて様々なセッションを聞けたことはとても有意義な経験でした。
RailsConfとは
1年に1回開催されるRuby on Railsに関する世界最大のカンファレンスとなります。(公式サイト)
2020〜2022年はコロナの影響でオンライン開催でしたが、2023年は4年ぶりのオフライン開催となりました。
また毎年開催地が変わり、今年はアメリカのアトランタで行われました。
カンファレンスの様子
以下がRailsConfのメイン会場でした。かなり広かったです。スピーカーの文字起こしもあったので、とてもありがたかったです。
会場はメイン会場に加えて、サブ会場が5箇所もあり、全体的にとても賑わっていました。
また、アメリカの様々なテック企業が参加していて、自分達も知っている所だとGitHub, Shopifyから、アメリカのベンチャー企業まで様々な人たちが参加していました。
発表の種類としてはRailsCoreの話や新しいgemの紹介、マイクロサービスについてなどの技術的な話からエンジニアチームの組織作りやメンターとメンティーの関係構築、ペアプロについての話がありました。更にはワークショップ形式でRailsのバージョンアップを皆で一緒にしたりなど、幅広く様々なセッションやワークショップが行われていました。
また、最終日には1人5分でLTをする時間があり、そこでは自分のエンジニア人生の話やRubyで作った便利ツールの紹介、OSSにコミットした話など、皆気軽にトークしていてとても面白かったです。
この記事では、その中から私たちが興味を持ったセッションをいくつか紹介したいと思います!
セッション紹介
Exploring the Power of Turbo Streams & Action Cable
バックエンドエンジニアの高久です。Kevin Liebholzさんの「Exploring the Power of Turbo Streams & Action Cable」についてご紹介します。
Rails 7ではよりリッチなフロントエンドを実現するためのHotwire、Turboといったライブラリがデフォルトでインストールされるようになりました。 そして、その機能の1つ「Turbo Streams」と既存の「Action Cable」を使ってリアルタイム通信を使ったWebページが簡単に実現できるようになりました。
Action CableとはRailsでWebSocketを利用したリアルタイム通信を実現するフレームワークです。Rails 5から実装されています。
また、Turbo Streamsとは<turbo-stream></turbo-stream>
で囲まれたHTML要素をさまざまなソースをトリガーにして更新できる機能です。
Turbo StreamsとAction Cableを組み合わせることによって、WebSockets通信によるサーバ/クライアント間のリアルタイムでの通信&画面描画を簡単に実現できます。
このセッションでは「まるばつゲーム」のWebページ実装をデモとして、それらのツールを使ってどのように実装するかを紹介していました。
詳しいコードは以下のセッション資料に記載されていますので、気になる方は見てみてください。
An imposter's guide to growth in engineering
次も高久よりEbun Segunさんの「An imposter's guide to growth in engineering」のセッションを紹介します。
RailsConfでは技術系の話だけではなく、キャリア・成長に関するセッションが複数ありました。このセッションではインポスター症候群について概要や、成長との関わり、エンジニアに関連した症状、対処法について話されていました。
インポスター症候群とは「明らかな成功にもかかわらず、圧倒的に不十分だと自己を過小評価してしまう傾向」のことを指します。明らかな成功とは「客観的にみて評価されるべき成果を上げた」ということで、ジュニアレベルを卒業したエンジニアに多くみられるそうです。
「インポスター症候群」という名前を自分は聞いたことがなかったのですが、セッション中の挙手によるアンケートでは会場にいた人のほとんどが知っており、また多くの人が経験していると答えていました。世界的には一般的なようです。
症状として「自分は詐欺師のようだ」「自分が思うよりも周りから賢いと思われている」「みんなを失望させるのが怖い」「自分にはこんなことはできないと思う」というような発言をすることが挙げられます。またエンジニア特有の行動として、以下が挙げられます。
- 話さない、質問しない
- 話せば話すほど、自分がインポスターだと思われてしまうような気がするため
- 会議の場で話さなかったり、とりあえず知的な人の意見に同意してしまう
- 自分の仕事について話さなければいけない時、いつも緊張してしまう
- 話せば話すほど、自分がインポスターだと思われてしまうような気がするため
- 成功するために他のことを探してしまう
- 自分が生産的だと思われたいため
- 目の前の難しいプロジェクトより、自分ができるバグ潰しに専念してしまう
- 自分が生産的だと思われたいため
- コードの完璧主義
- じっくりと時間をかけてコードを完璧にする
- プルリクエストを出す前に、自分のコードにシミや傷がないことを確認する
これらの行動は個人の成長の妨げになり得ます。しかし決して悪いものではなく、まだ成長の余白があることを示しています。
これらを解決するためにEbun Segunさんは「チームやプロジェクトにおける自分の存在意義を把握する」「自分がすでに知っていることの価値に気づく」が大事であると述べていました。セッションではさらに詳細な話をされていました。
ここで紹介されていた症状が自分にも当てはまることが多く、胸が痛いと共に良い気づきを得ることができ、自身の成長に繋げられそうなセッションだったと感じました。また国や文化に関係なく、みんなが起きていること、悩んでいることを知れることが海外カンファレンスのいいところであると実感しました。
Upgrading Rails: The Dual-Boot Way
バックエンドエンジニアの小山です。
RailsConfはセッションだけではなくワークショップも充実していました。今回私はRailsアップグレードのプロセスを学ぶワークショップに参加してきました。
こちらのワークショップは参加者30名程で、講師が用意してくれたスライドの解説を聴きながら、クローンしたサンプルアプリケーションをRails 6.1から7.0にアップグレードするという内容でした。
next_railsというgemを導入し、Railsを複数バージョンで起動するDual Bootという手法を使い、Railsをアップグレードしました。アップグレードの過程で非推奨の警告を確認しRailsとgem依存関係を確認しながらパッチを当てていきました。
next_railsについて補足すると、このgemをインストールすることで、next --init
コマンドが使えるようになります。
このコマンドによりDual Boot用のGemfile.next
とGemfile.next.lock
が生成されます。
next bundle install
を実行することで、新しいRailsバージョンに互換性のあるgemをインストールできます。
また、next rails s
でサーバーを起動すると、Gemfile.next
を使用してサーバーが起動されます。
next_railsの詳細は以下のリポジトリを参照してください。
https://github.com/fastruby/next_rails
ワークショップの内容をかいつまんでご紹介します。
最初にクローンしたアプリケーションに対してgem next_railsをインストールしてDual Boot用のGemfile
を生成します。
# Add Gemfile group :development, :test do gem 'next_rails' end
$ bundle install $ next --init
次にnext_rails用の設定をGemfile
に追記します。
# Add Gemfile def next? File.basename(__FILE__) == "Gemfile.next" end if next? gem 'rails', '~> 7.0.4.3' else gem 'rails', '6.1.7' end
railsバージョンをアップデートします。
$ next bundle update rails
今回のRails 6.1から7.0へのアップグレードではbundlerがgem "railties" で互換性のあるバージョンを見つけられずエラーを吐きました。
Bundler could not find compatible versions for gem "railties": In Gemfile.next: activeadmin (~> 2.10.1) was resolved to 2.10.1, which depends on railties (>= 6.0, < 6.2) rails (= 7.0.4.3) was resolved to 7.0.4.3, which depends on railties (= 7.0.4.3)
activeadminのバージョンを指定することで解消されるためGemfile
に以下の修正を加えてアップデートしました。
# Fix Gemfile if next? gem ‘activeadmin‘ else gem ‘activeadmin', ‘~> 2.10.1’ end
$ next bundle update rails activeadmin
また、ZeitwerkはRails 6で導入され、Rails 7では必須になっています。Zeitwerkの互換性を確認するタスクを実行するとAPIという定数が存在しないとエラーが出力されます。
$ next bin/rails zeitwerk:check rails aborted! NameError: uninitialized constant API mount API::Base => '/api' ^^^ Did you mean? Api
こちらはActiveSupport::Inflector
の語尾の活用機能を用いて略語を指定することでZeitwerkオートローダーの入力時のエラーを修正できます。
# Add to config/initializers/inflections.rb ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym "API" end
最後にデフォルトの各バージョンのRailsの設定を読み込むためにload_defaultsを修正します。この更新を忘れると非推奨の警告がでます。
# Fix Gemfile def next? $next_rails = File.basename(__FILE__) == "Gemfile.next" end # Fix config/application.rb if $next_rails config.load_defaults 7.0 else config.load_defaults 6.1 # or stay with 5.0 end
これらの修正を加えることで非推奨の警告を解消しgemの依存関係を解消してDual BootでRailsを起動できるようになりました。
弊社のサービスであるWEARでは現在Rails 6.0を使用しており7.0へのアップグレードを予定しています。そのため、今回、ワークショップ形式で手を動かしながら7.0へのアップグレードを実践できたことはとても良い経験になりました。
ワークショップで使用した資料が共有されているので、もし興味がある方は手を動かして試してみてください。
Migrating Shopify's Core Rails Monolith to Trilogy
バックエンドエンジニアの近です!
自分からはAdrianna Chang氏の「Migrating Shopify's Core Rails Monolith to Trilogy」というセッションの紹介をしたいと思います。
このセッションでは、TrilogyというMySQL用のクライアントライブラリをShopifyのRailsコアに適用するにあたっての追加機能や、デプロイ時に当たった問題とその解決法の紹介をしていました。
RailsとMySQLを繋げるクライアントライブラリとして有名なのはmysql2がありますが、新しいアダプタとしてTrilogyというものがあるのは今回初めて知りました。
Trilogyの特徴として、以下があるそうです。
- 2022年にGitHubによってOSS化されたMySQLクライアント
- 独自の低レベルネットワークプロトコル実装
- テキスト プロトコルの最も頻繁に使用される以下をサポート
- ハンドシェイク
- パスワード認証
- クエリ、ping、および終了コマンド
- 最小限の動的メモリ割り当て、メモリ効率の最適化
- 柔軟性、性能、組み込みやすさを追求したデザイン
また、他のアダプタと比較したときのTrilogyに乗り換えるメリットとして、以下を挙げていました。
- コンパイルに必要な依存関係が少ない
- libmysqlclient / libmariadbへの依存がなく、インストールがシンプル
- クライアントとサーバーのバージョンの不一致問題を解消
- パケットを扱う際のデータのコピー回数を最小限に抑えられる
- Ruby VMのコンテキストで効率的に動作するよう設計
- 動的メモリ割り当ての意識的な使用
- 可能な限りノンブロッキング操作とI/Oコールバックを使用するようAPIが設計されている
確かに、インストール時にlibmysqlclient等の依存関係でエラーにハマったことのある人は多くいると思うので、この辺りが解消され、更にパフォーマンスの向上が期待できるのは嬉しいですね。
上に書いたメリットの中で、さらにShopifyがTrilogyに乗り換えたい強い理由として、以下が挙げられていました。
- より良い開発体験
- クエリパフォーマンスの高速化
- 強い保守性
これに加えて、Trilogyをコミュニティのスタンダードにしたいという思いもあるようです。
次に、MySQLがサポートしているマルチステートメント機能の話題になりました。 MySQLでは、セミコロンで区切られた複数のステートメントを含む文字列の実行をサポートしています。
これを、Trilogyを用いたRubyにて記述しようとすると、以下のようになります。
require 'trilogy' client = Trilogy.new( host: '127.0.0.1', port: 3306, username: 'root', multi_statement: true, ) sql = <<-SQL DROP TABLE IF EXISTS users; CREATE TABLE users (name VARCHAR(255)); INSERT INTO users VALUES ('John'); SELECT * FROM users; SQL client.query(sql)
マルチステートメントクエリは通常のクエリより高いパフォーマンスを発揮するため、本来上記のように書きたいところなのですが、当時、Trilogyはマルチステートメントの対応はされていませんでした。
Shopifyでは1000を超えるサンプルデータを持っていて、それらが更に100件のデータを持っていたりしているので、これらを効率的にDBへインサートするため、この機能を活用したいと考えていました。
mysql2アダプタでは既にこのマルチステートメントがサポートされているので、Trilogyでもサポートしたいとのことでした。 そして、 multi_statement: trueという構文をサポートするために、C拡張を用いて実装し、その紹介も行っていました。
static VALUE rb_trilogy_initialize (VALUE self, VALUE opts) // ← optsを追加 { struct trilogy_ctx *ctx = get_ctx (self); trilogy_sockopt_t connopt = {0}; trilogy_handshake_t handshake; VALUE val; Check_Type (opts, T_HASH); ... if (RTEST(rb_hash_aref (opts, ID2SYM(id_multi_statement)))) { // ← connopt.flags |= TRILOGY_CAPABILITIES_MULTI_STATEMENTS; // ← } ... } static VALUE rb_trilogy_more_results_exist (VALUE self) // ← { struct trilogy_ctx *ctx = get_open_ctx (self); if (ctx->conn.server_status & TRILOGY_SERVER_STATUS_MORE_RESULTS_EXISTS) { return Qtrue; } else { return Qfalse; } } static VALUE rb_trilogy_next_result(VALUE self) // ← { struct trilogy_ctx *ctx = get_open_ctx(self); if (!(ctx->conn.server_status & TRILOGY_SERVER_STATUS_MORE_RESULTS_EXISTS)) { return Qnil;
実際のPRはこちらです。
中身はC言語ですが、セッションで紹介している内容が実際に見られて面白いです。
https://github.com/github/trilogy/pull/57
https://github.com/github/trilogy/pull/35
他にも、当日のセッションではマルチステートメントの詳細な説明やTrilogy C APIとRubyの連携・実装の話など詳細に語っていて面白かったのですが、長くなるので触りだけの紹介でした。
次に、Trilogyを実際にShopifyの本番環境にデプロイした時の話になりました。 最初にCIをオールグリーンにするため、以下を修正していました。
- まず、MySQLクライアントをMysql2からTrilogyに書き換え
- Trilogy用に、クライアントの小さなAPI変更に対応
- Trilogy用のエラーハンドリング
- コードは全てMysql2::Errorになっていたため、それらをTrilogy::Errorに変更
- もちろん、エラーメッセージも変わるのでテストなどの修正
思いの外、簡単に修正が済んだようです。
この辺りでShopifyのインフラ構成の紹介になりました。
- Railsのコアアプリケーションはモジュラーモノリス型
- ピーク時は1秒間に1400万クエリが実行される(!)
- DB周りのインフラ
- 水平パーティショニング
- MySQLインスタンスはGoogle Cloud上で動作し、Chefで管理されている
- ProxySQL
- クライアント接続数:10万
- バックエンド接続数:2万
1秒間に1400万クエリは全く想像がつきません。
Trilogyのデプロイにあたり、まずは本番環境の1%で動作させたところ、スムーズに動作していたのですが、次に50%でデプロイしてみたところ、エラーレートが上昇しリバートすることになったそうです。
なにがあったのか?
- ProxySQLはバックエンド接続のプールを保持する
- クライアント機能が異なる場合、ProxySQLはCOM_CHANGE_USERを実行する必要があった
- mysql2とTrilogyの両方を用いる場合、ProxySQLではバックエンドを切り替える際に新しい接続オプションを設定するCOM_CHANGE_USERコマンドを実行する必要があった
というような問題がおき、エラーレートが上昇したとのことでした。
加えて、ProxySQLのpod接続状況を確認したところ、mysql2ではCLIENT_MULTI_RESULTSが設定されているのに、Trilogyでは設定されていなかったのも原因だったそうです。
これらを修正し…再度、本番環境に100%デプロイしたところ、無事動作し、なんとMysql2に比べて22%もパフォーマンスが向上していました。
Request Time:
- Mysql2: Avg 3.46ms
- Trilogy: Avg 2.70ms
-22% faster
また、クエリタイムも17%ほど速くなっていたとのこと。
MySQL query time:
- Mysql2: Avg 1.49ms
- Trilogy: Avg 1.24 ms
-17% faster
DBのクライアントを変更しただけでリクエストタイム、クエリタイム共に高速化されているのは非常に凄いですね。特に、Shopifyのような大量のユーザーを抱えているサービスでの恩恵は大きそうです。
今まではGitHubのみが本番運用していたTrilogyですが、今回OSS化されたことによってShopifyのように今後導入するサービスが増えていきそうなので是非チェックしてみてください!
今回紹介したスライドは以下になります。
おまけ
今回参加した3人は海外カンファレンス初参加だったので、慣れない環境での生活や英語でのコミュニケーションに四苦八苦しながらの参加だったのですが、現地の人たちは皆優しく接してくれて、とても助かりました。
日本と違うなーと感じた部分はセッション中に拍手が起きたり、(面白いシーンで)笑いが多かったり、隣の席の人とフランクに喋っていたりと、アメリカンな一面が見えました。また全体を通してセッション終了後のQ&Aにて積極的に質問する姿も見られました。
セッション後にお菓子を食べながら参加者同士でコミュニケーションを取る時間がありました。英語に自信がなかったのでドキドキしていたのですが、最初に雑談した相手がなんと日本語を喋れて盛り上がったりなど、とても面白かったです(LinkedInを交換することに成功しました)
また、会場ではお昼ご飯が提供されていたのですが、日本に比べて全体的に味が濃かったです。開催がアトランタということもあり、南部の郷土料理である「グリッツ」を食べることもできました。
来年のRailsConfの開催地の発表もありました。次回はデトロイトでの開催を予定しているそうです。
最後に
ZOZOではセミナー・カンファレンスへの参加を支援する福利厚生があり、カンファレンス参加に関わる渡航費・宿泊費などは全て会社に負担してもらっています。ZOZOでは引き続きRubyエンジニアを募集しています。以下のリンクからぜひご応募ください。