Slide 1

Slide 1 text

Akina Matsushima 2025/03/01 TokyoWoman.rb #1 Ruby on Railsで 持続可能な開発を行うために 取り組んでいること

Slide 2

Slide 2 text

松嶋 瑛奈 (Matsushima Akina) dely株式会社 バックエンドエンジニア ● 2019/11~: 現職にSREとして入社 ● 2022/10~: バックエンドエンジニアに転向 ● Ruby/Rails歴3年目 💎 ● Kaigi on Rails 2024のスポンサーLTが初登 壇、 今回で2回目です! 📣 自己紹介 @25_aaa 3 @akingo55

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

今回のテーマは、「持続可能な開発」

Slide 6

Slide 6 text

持続可能な開発? 🤔

Slide 7

Slide 7 text

プロダクトの性質やフェーズにあわせて、 「品質」と「開発速度」のバランスを取る こと

Slide 8

Slide 8 text

バランスを取りやすくするためには、 どうすればいいのか?

Slide 9

Slide 9 text

不要な複雑さを減らし、ソフトウェアを 「単純」にすること

Slide 10

Slide 10 text

今日話すこと 1. デッドコードを定期的に削除している話 2. Datadog APMを用いてパフォーマンス改善している話 💡 9年目を迎えたRailsアプリケーションを運用する中で、私たちがソフト ウェアを「単純」にするために取り組んできた2つのことについて話します。

Slide 11

Slide 11 text

1.デッドコードの削除 🧹

Slide 12

Slide 12 text

消そう。以上。 実行されないコードは消すだけだ。 引用:「Tidy First? 2章 デッドコー ド」

Slide 13

Slide 13 text

なぜ不要なコードが蓄積されてしまうのか? ● 時間を捻出できない ⏰ ○ 普段は機能開発に追われているため、改善系のタスクに着手する時間がとれない ○ 改善チームやPjTを立ち上げられたらいいが、そもそも人手不足 ● 後回しにして忘れ去られてしまう 󰣼 ○ とりあえずバックログに入れるが、優先度低くて一生やらないタスクになる ○ そして忘却の彼方に ● そういう文化がない 😢 ○ 機能開発こそが価値を生むものだなど、運用を軽視する文化が根強いと放置されがち ○ 誰かが声を上げない or 障害が起きて大惨事にならない限り変わらない

Slide 14

Slide 14 text

私たちの解決策 ● 時間を捻出できない ⏰ or 後回しにして忘れ去られてしまう 󰣼 ○ 機能開発が落ち着く年末に、早めにコードフリーズして時間つくる(去年は12/19) ○ 高頻度である必要はないし、年に1回程度だと調整・説得しやすい ○ 誰でも運用を回せるように、あらかじめ削除フローは整備しておく ○ 年1回のコードの大掃除としてイベント化する ■ 「年末 = 大掃除」のイメージがあるので定着しやすい ● そういう文化がない 😢 ○ 一朝一夕で文化は変わらない ○ 強い気持ちで、根気強く地道に浸透させていくしかない

Slide 15

Slide 15 text

どのような手段を使うか? ● 動的解析ツールを使って、プロダクションコードの使用率を計測す る (ex. coverband) ● 静的解析ツールを使って、ソースコードから使われていないメソッ ドやクラスを抽出する(ex. debride) ● プロダクション環境のアクセスログからAPI単位で不要なコードを抽 出する

Slide 16

Slide 16 text

どのような手段を使うか? ● 動的解析ツールを使って、プロダクションコードの使用率を計測す る (ex. coverband) ● 静的解析ツールを使って、ソースコードから使われていないメソッ ドやクラスを抽出する(ex. debride) ● 👉プロダクション環境のアクセスログからAPI単位で不要なコード を抽出する 既存のログ基盤を活用できること && 年1回の運用なので運用コストが 一番少ない方法を選択

Slide 17

Slide 17 text

何を消していくのか? ● サポート対象外のバージョンで使われていたモバイルのAPI ● 機能として既に存在しないWebのAPI ● 誰も使わなくなった社内の管理サイトの機能 ○ 1年以上アクセスがないページや使われた痕跡がない機能は基本的に削除する ● 運用が既に止まっているrakeタスク ● 不要なバッチ処理 ○ 効果の薄いプッシュ通知、誰も見ていないSlack通知 etc.

Slide 18

Slide 18 text

削除フロー例 1. 現在使われているAPIのパスとメソッド一覧をペアにしてCSV形式で出力 a. モバイルAPIはモバイルエンジニアに依頼する b. アクセスログから抽出する 2. ActionDispatch::Routingで出力したルーティング一覧と照らし合わせ る ※削除対象のAPIを抽出するスクリプトをチームで使い回せるようにしてい るが、年1回の実施なので完全自動化まではしていない

Slide 19

Slide 19 text

コードの大掃除の効果 ● 毎年1万弱のコードが削除されている ○ 2024年は約9200行削除できた 🎉 ○ 年1運用なので、負担も少なく継続しやすかった ● CIの時間が短くなる ○ CIの時間を短縮するTipsは様々あるが、コード削除だけでも1分以上短くできる ● 年末早めのコードフリーズで、運用や改善タスクを実施する時間も割 けるようになった ○ gemのバージョンアップ ○ APIのパフォーマンス改善 ○ 後回しにしていたアプリケーションエラーの解消等

Slide 20

Slide 20 text

2. パフォーマンス改善 󰝋💨

Slide 21

Slide 21 text

そもそも何でパフォーマンスを改善するのか? 理由は以下の3つしかないと言われている 1. 顧客体験を向上させる 2. 運用コストを減らす 3. トラフィック増加による障害を防ぐ 引用:「Ruby on Rails パフォーマンスアポクリファ 第1章」 ● 現代のWebアプリケーションは、高速であることが「当たり前」 ● Googleの研究によると、検索結果の表示が100ms~400ms遅くなるだけ で0.2~0.6%の利用ユーザーが減少したと報告されている 引用:https://research.google/blog/speed-matters/

Slide 22

Slide 22 text

パフォーマンス改善のお作法 ● 推測するな、計測せよ && まずボトルネックを解消せよ ○ APMを導入し、トレースデータを活用すると改善が捗りやすい(Datadog, NewRelic etc.) ○ パフォーマンスの良し悪しを判断する指標があるとベター(SLOなど) ● ダッシュボードを作成し、パフォーマンスを可視化する ○ 作っておしまいにならないことが大事!!! ○ 弊社の場合 ■ 週次のサーバーサイド定例でダッシュボードを眺める時間を確保 ■ 短期と長期の観点で見て異変がないか ■ 改善が必要と判断されたものはチーム単位で改善タスクとして積む

Slide 23

Slide 23 text

効果的にパフォーマンスを改善するには? 🌟 最もユーザー体験の改善に繋がるものから順番に改善していく! どんな指標をもとに判断すればいい? ● 99パーセンタイル値のレイテンシが悪い順? ● リクエスト数✕平均レイテンシの値が悪い順?

Slide 24

Slide 24 text

個人的におすすめなやり方 ● レイテンシの閾値を設定し、エン ドポイントごとにこの閾値を超え た回数をカウントする ● その回数が最も多いエンドポイン トから改善していく ● ユーザーに負の体験を与えている ものから効果的に改善できる 実際のDatadogのダッシュボードから抜 粋

Slide 25

Slide 25 text

実際の改善事例の紹介 🐶

Slide 26

Slide 26 text

事例①:eager_loadの罠 ● ページングありで取得するレコード数も制限されている ● N+1対策のためのeager loadもしていて問題なさそう ・・・? ● 早いときもあるし遅いときもあるぞ

Slide 27

Slide 27 text

事例①:eager_loadの罠 ● レシピに紐づくユーザーのレーティングに関するレコードが膨大 になっていた ● 人気なレシピの場合、レーティングが数万件ついてることも ・・・ 👈 犯人!

Slide 28

Slide 28 text

事例①:eager_loadの罠 ● SQL実行時に ActiveSupport::Notifications経由で instantiation.active_recordイベントを取 得しているので、トレースデータのスパンタグ にセットされる ● SQLの実行には取得レコード数が増えても 0.1s以下だが、ActiveRecordはインスタン ス化するのに時間がかかってしまう

Slide 29

Slide 29 text

事例①:改善方法 UserRatingはリアルタイムに取得する必要はないので、recipesテーブル にuser_rating_countカラムを追加、バッチ処理で更新するスタイルに変 更 ● その結果、P99の値が平均して1/10まで減少した 👏 ● includesメソッドは便利で容易に使いがちだが、取得されるレコー ド数も考慮にいれないとパフォーマンスに影響する ✏

Slide 30

Slide 30 text

事例②: IN演算子の使い方には気を付けよう ● あれ、謎の空白が目立つな? ● トレースデータに何も表示されず、何が起きてるのか分からない ● 発行されているSQLはINNER JOINしているな ● SQLも改善できそうだけど、空白の部分の時間が長すぎるぞ

Slide 31

Slide 31 text

事例②: IN演算子の使い方には気を付けよう ①で取得したIDを②のクエリのIN演算子に渡しているので、インスタンス化する時に オーバーヘッドが生じてそう 🤢(JOINするとトレースに表示されない ⚠) ① ② IN演算子に大量のIDが渡っ てきているこれは・・・ 😇

Slide 32

Slide 32 text

事例②: IN演算子の使い方には気を付けよう ● 初期からある内部リンク用API で、ロジックの見直しは今まで されていなかった ● 記事に関連するレシピを取得し ているが、MIN_LIMITだけ決 めて、MAX値を決めてなかった のでIN演算子に大量のIDを渡し てしまっていた ● IN演算子も大量の要素を渡して しまうと、インデックスが使わ れずフルスキャンが走る可能性 があるので注意!

Slide 33

Slide 33 text

事例②: 改善方法 IN演算子に渡すID数に制限を設け、オーバーヘッドをなくす 全体で見ると1/10、クエリの速度は1/20まで改善 🎉 ① ②

Slide 34

Slide 34 text

事例③:効果的なインデックス貼れてないかも・・・ ● 複数のテーブルをINNER JOINいるが、最適なインデックスが存在していな いことで、非常に遅いクエリが実行されていた 😇

Slide 35

Slide 35 text

事例③:効果的なインデックス貼れてないかも・・・ ● explainで実行計画見ると、一番 カーディナリティの低いインデック スが使用されていた ● そのインデックスは消去法で選択さ れており、他に使われている箇所は なく、有用なインデックスではな かった ● sys.schema_index_statisticsで インデックス使用率見れるよ (MySQL)

Slide 36

Slide 36 text

事例③:改善方法 ● 不要なインデックスは削除し、WHERE句で指定されているカラムで複合インデッ クスを貼ることで改善した(2.61s → 347ms) ● 複合インデックスは左から順に使われるので、WHERE句に指定する順番には注意 ⚠(leftmost prefixルール) ● Railsだとscope使うときにインデックスがどう貼られているか気をつけると 󰢐

Slide 37

Slide 37 text

改善結果 ● 他にもエンドポイントごとに様々な改善を実施した ○ APIのレスポンスデータは余分なものを減らし、必要最低限なものだけ 返すようにしたり ○ 過剰に発行されているクエリを削減したり ○ 過度なDRYによって、非効率なクエリが発行されているコードを最適 化したり etc. ● 最終的に、全体のP99.9の値が800-900msから300ms台まで減らせた 🎉

Slide 38

Slide 38 text

まとめ ● プロダクトの品質と開発速度のバランスを取ることは難しいですが、ソフ トウェアを単純にしていくことで、両者のバランスは実現可能 ● 単純さの追求は、Railsアプリケーションを安全に楽しく開発できること にもつながるよ ● そのための手段として、「デッドコードの削除」や「パフォーマンス改 善」を効果的に行うための事例を紹介しました ● 年末のコードのお掃除会、おすすめなのでぜひ! ● APMはそれなりに高いけどパフォーマンス改善が捗るし、どの部分がボト ルネックになりやすいのか理解が深まるのでおすすめ!

Slide 39

Slide 39 text

ありがとうございました!