Upgrade to Pro — share decks privately, control downloads, hide ads and more …

APMをちゃんと使おうとしたら、いつのまにか独自gemを作っていた話

 APMをちゃんと使おうとしたら、いつのまにか独自gemを作っていた話

Kaigi on Rails 2023 セッションの発表資料です https://kaigionrails.org/2023/talks/kokuyouwind/

kokuyouwind

October 28, 2023
Tweet

More Decks by kokuyouwind

Other Decks in Programming

Transcript

  1. APM
    をちゃんと使おうとしたら、
    いつのまにか独⾃gem
    を作っていた話
    Leaner Technologies, Inc.
    黒曜
    (@kokuyouwind)

    View full-size slide

  2. $ whoami
    黒曜 / @kokuyouwind
    Leaner Technologies Inc.
    所属
    Rails
    エンジニア
    インフラ・SRE
    的なこともやっている
    APM
    など開発周辺ツールの管理

    View full-size slide

  3. APM
    の話をします

    View full-size slide

  4. APM
    とは
    Application Performance Monitoring
    の略
    リクエスト数や応答速度を監視・管理できる
    代表的な APM
    サービス
    etc.

    View full-size slide

  5. APM
    の導⼊⾃体は結構簡単
    https://docs.datadoghq.com/ja/tracing/trace_collection/dd_libraries/ruby/
    ⼤抵の APM
    は エージェントソフトウェアと
    gem
    を設定するだけ
    # Gemfile
    gem 'ddtrace', require: 'ddtrace/auto_instrument'
    # config/initializers/datadog.rb
    Datadog.configure do |c|
    c.service = 'my-service'
    end
    1
    2
    3
    4
    5
    6
    7

    View full-size slide

  6. APM
    の画⾯例 (1)
    https://docs.newrelic.com/jp/docs/apm/new-relic-apm/getting-started/introduction-apm/

    View full-size slide

  7. APM
    の画⾯例 (2)
    https://docs.datadoghq.com/ja/tracing/other_telemetry/connect_logs_and_traces/

    View full-size slide

  8. 🤔
    すごいけど、何を⾒ればいいの…

    View full-size slide

  9. APM
    導⼊後のあるある
    導⼊で満⾜して、その後画⾯を開かなくなった
    応答速度の遅いエンドポイントを⾒て、
    「やっぱりここは重いよね〜」と話して終わる
    ソースコード上でどこが重いのか、結局わからない
    筆者は過去に全部⼼当たりがあります…(
    ⼩声)

    View full-size slide

  10. ひとつの参考事例として、
    弊社での APM
    の使い⽅を紹介します

    View full-size slide

  11. アジェンダ
    簡単な APM
    の使い⽅
    トレースを使ったパフォーマンス・チューニング
    APM Traceable gem
    を作った話
    具体的なチューニング事例紹介
    まとめ

    View full-size slide

  12. アジェンダ
    簡単な APM
    の使い⽅
    トレースを使ったパフォーマンス・チューニング
    APM Traceable gem
    を作った話
    具体的なチューニング事例紹介
    まとめ

    View full-size slide

  13. 簡単な APM
    の使い⽅
    ⼤抵の APM
    は集計単位ごとにページに分かれている
    サイト全体
    エンドポイントごと
    リクエストごと
    最初は「サイト全体」の指標で⼤まかな傾向を⾒ると良い
    細かく掘り下げていくときに「エンドポイントごと」
    「リクエストごと」の順に⾒ていく

    View full-size slide

  14. ここから先は Datadog APM
    を例に
    スクリーンショットを出します
    (
    他APM
    でも同様の画⾯があるはず)

    View full-size slide

  15. APM
    の⾒⽅ -
    全体の統計

    View full-size slide

  16. APM
    の⾒⽅ -
    全体の統計

    View full-size slide

  17. APM
    の⾒⽅ -
    全体の統計
    リクエスト数も応答速度も、極端な変化がないか確認
    曜⽇や時間帯で傾向が違うため、前週同曜⽇と⽐較する
    サービスによっては⽉末・年末なども考慮する
    異常値があった場合はトリアージする
    ⼀時的要因であれば様⼦⾒、問題がありそうなら調査・対応

    View full-size slide

  18. APM
    の⾒⽅ -
    個別エンドポイント

    View full-size slide

  19. APM
    の⾒⽅ -
    個別エンドポイント

    View full-size slide

  20. APM
    の⾒⽅ -
    個別エンドポイント

    View full-size slide

  21. APM
    の⾒⽅ -
    確認タイミング
    週1
    で「APM
    を⾒る会」を実施している
    悪化傾向を⾒つけられるよう定期的に⾒る機会を作る
    トリアージもこのタイミングで実施
    開発・運⽤に関わるメンバー全員で⼀緒に⾒ると良い
    悪化しているエンドポイントに機能追加などの
    ⼼当たりがあるか確認しやすい

    View full-size slide

  22. アジェンダ
    簡単な APM
    の使い⽅
    トレースを使ったパフォーマンス・チューニング
    APM Traceable gem
    を作った話
    具体的なチューニング事例紹介
    まとめ

    View full-size slide

  23. 重いエンドポイントを改善する際、
    リクエストごとのトレースが⾜がかりになる

    View full-size slide

  24. リクエストごとのトレース
    何に時間がかかっているか(
    スパン)
    を表示できる
    DB
    アクセスやWeb API
    呼び出しのスパンを⾃動で収集

    View full-size slide

  25. N+1
    クエリへの対処
    SQL
    クエリを確認し、適切に includes
    などを設定する
    特定条件やデータセットがないと発⽣しないN+1
    クエリは
    bullet gem
    などで⾒つけづらいケースがある

    View full-size slide

  26. 重いクエリへの対処
    まずは EXPLAIN
    で実⾏計画を確認する
    インデックス追加だけで改善することも多い

    View full-size slide

  27. 時間が⾜りないので
    詳しいチューニング⼿法は割愛

    View full-size slide

  28. https://slides.com/kokuyouwind/rails-performance-tuning
    詳細は以前の Kaigi on Rails 2020
    で話した
    「Rails
    パフォーマンス・チューニング⼊⾨」をご覧ください!

    View full-size slide

  29. パフォーマンス・チューニングについては
    他の⽅も発表されていたのでおすすめです!
    Rails
    アプリの 5,000
    件の N+1
    問題と戦っている話
    初めてのパフォーマンス改善〜君たちはどう計測す(
    はか)
    るか〜

    View full-size slide

  30. アジェンダ
    簡単な APM
    の使い⽅
    トレースを使ったパフォーマンス・チューニング
    APM Traceable gem
    を作った話
    具体的なチューニング事例紹介
    まとめ

    View full-size slide

  31. ⾃動トレースは便利だが、
    標準のトレースだけだと困るときがある

    View full-size slide

  32. 困るとき 1. SQL
    の発⾏場所がわからない
    ActionController
    スパン直下に SQL
    クエリのスパンがある
    単なるメソッドの呼び出しは標準だとスパンにならない
    クエリ構築がどのコードで⾏われているかわからないと
    修正しようがない
    (
    重いクエリの例を再掲)

    View full-size slide

  33. 困るとき2 . Ruby
    での処理が重いケース
    Ruby
    処理は⾃動でスパンに区切られない
    原因が全くわからないので対処もできなくなってしまう

    View full-size slide

  34. ⼿動インストゥルメンテーション
    https://docs.datadoghq.com/ja/tracing/trace_collection/dd_libraries/ruby/
    Datadog::Tracing.trace(name, **options) do |span, trace|
    #
    計測したい処理をここに記⼊
    end
    1
    2
    3
    Datadog
    では Datadog::Tracing.trace
    を使って
    ⼿動でスパンを追加できる
    渡したブロック内がスパンとして区切られる

    View full-size slide

  35. Datadog::Tracing
    の微妙な使いづらさ
    # ↓
    標準だとこう書く必要がある
    class User
    def awesome_method
    Datadog::Tracing.trace(
    'awesome_method',
    service: 'my-service',
    resource: 'User') do
    # ...
    処理
    end
    end
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    処理対象をブロックで囲むとインデントが変わる
    差分が⼤きくなって気軽につけ外ししづらい
    引数が多く、記述がやや冗⻑

    View full-size slide

  36. APM Traceable gem
    # ↓
    こう書けるようにした
    class User
    include ApmTraceable::Tracer
    trace_methods :awesome_method
    def awesome_method
    # ...
    処理
    end
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    trace_methods
    にメソッド名を指定するとスパンとして表示
    UseCase
    やPresenter
    など、独⾃レイヤークラスの呼び出し
    重いメソッドを切り分けたプライベートメソッド

    View full-size slide

  37. APM Traceable gem
    各Presenter#call
    をtrace_methods
    した結果

    View full-size slide

  38. APM Traceable gem
    元々は1
    モジュールでlib
    以下に置いていた
    以前 に書いた話
    複数プロダクトに必要だったのでgem
    に切り出した
    APM Traceable gem
    本体
    Datadog
    への送信⽤アダプタ
    アダプタを作れば他の APM
    にも切り替えられるようにした
    ブログ記事
    apm_traceable
    apm_traceable-datadog

    View full-size slide

  39. APM Traceable gem -
    仕組み
    module ApmTraceable::Tracer
    def self.trace_methods(*method_names)
    wrapper = Module.new do
    method_names.each do |method_name|
    define_method method_name do |*args, **options, &block|
    trace_span(method_name.to_s) { super(*args, **options, &block) }
    end
    end
    end
    prepend(wrapper)
    end
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    trace_methods
    で各メソッドを持つモジュールを作り、
    prepend
    して呼び出しをラップするだけ

    View full-size slide

  40. APM Traceable gem -
    仕組み
    Class User
    awesome_method
    prepended Module
    awesome_method
    メソッド呼び出し
    Datadog Adapter
    trace_span
    トレース送信

    View full-size slide

  41. アジェンダ
    簡単な APM
    の使い⽅
    トレースを使ったパフォーマンス・チューニング
    APM Traceable gem
    を作った話
    具体的なチューニング事例紹介
    まとめ

    View full-size slide

  42. 事例 1. SQL
    クエリの速度悪化
    MySQL 8.0
    に更新した際、特定エンドポイントだけ遅くなった

    View full-size slide

  43. 事例 1. SQL
    クエリの速度悪化

    View full-size slide

  44. 事例 1. SQL
    クエリの速度悪化
    テンポラリテーブルでの SELECT COUNT(*)
    の問題っぽい…
    https://zenn.dev/hmatsu47/articles/mysql80-count-slowdown

    View full-size slide

  45. 事例 1. SQL
    クエリの速度悪化
    元のクエリが GROUP BY
    してから最初の⾏を取っており、
    実質 DISTINCT
    相当の処理がテンポラリテーブルで⾏われていた
    DISTINCT
    を使えばテンポラリテーブルを使わなくなるので直した

    View full-size slide

  46. 事例 1. SQL
    クエリの速度悪化
    元々 1.0sec ->
    悪化して 4.0sec ->
    改善後 100ms
    結果的に元の10
    倍に⾼速化🚀

    View full-size slide

  47. 事例 2.
    外部API
    呼び出しのトレース
    外部 API
    を並列で呼び出す箇所のトレースが取れていなかった
    (
    単独だと取れるが、2
    箇所以上Parallel
    だと取れない)

    View full-size slide

  48. 事例 2.
    外部API
    呼び出しのトレース
    Parallel
    をまたぐとスパンの親⼦関係が途切れるようなので、
    ⼿動で親ダイジェストを引き渡すよう修正
    def search_parallel(clients)
    + trace_span('search_parallel') do |_span, trace|
    results = Parallel.map(clients, in_processes: clients.count) do |client|
    + trace_span(client.name, continue_from: trace.to_digest) do
    client.search
    + end
    end
    + end
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View full-size slide

  49. 事例 2.
    外部API
    呼び出しのトレース
    この変更で、きれいにトレースが⾒られるようになった 🎉

    View full-size slide

  50. 事例 2.
    外部API
    呼び出しのトレース
    トレースを精査したところ、外部API
    呼び出しよりも
    XML
    レスポンスのパース処理に時間がかかっていた

    View full-size slide

  51. 事例 2.
    外部API
    呼び出しのトレース
    ローカルで再現させ、 Stackprof
    でプロファイルを取ったところ
    DidYouMean::Jaro.distance
    が⼤半を占めている…

    View full-size slide

  52. 事例 2.
    外部API
    呼び出しのトレース
    XML
    で未知の attribute
    が出てきた際、
    const_get
    でNameError
    が起こる実装になっていた。
    const_defined?
    で確認を挟むよう実装を修正
    klass = "CXML::#{camelize(key)}"
    - send("#{key}=", Object.const_get(klass).new(val))
    + send("#{key}=", Object.const_get(klass).new(val))
    + if Object.const_defined?(klass)
    + send("#{key}=", Object.const_get(klass).new(val))
    + else
    1
    2
    3
    4
    5
    6

    View full-size slide

  53. 事例 2.
    外部API
    呼び出しのトレース

    View full-size slide

  54. 事例 2.
    外部API
    呼び出しのトレース
    エンドポイントのレスポンスタイムも、
    修正を境に⾶び上がった値が出ることがなくなった

    View full-size slide

  55. アジェンダ
    簡単な APM
    の使い⽅
    トレースを使ったパフォーマンス・チューニング
    APM Traceable gem
    を作った話
    具体的なチューニング事例紹介
    まとめ

    View full-size slide

  56. まとめ
    APM
    は本番で起こっている問題を⾒つけるのに便利
    統計値を定期的に⾒て、変化に気づけるようにすると良い
    トレースを⾒ると重い原因を⾒つける⾜がかりになる
    重要なメソッドにスパンを仕込んでおくと原因を掘り下げやすい
    これをやりやすいように独⾃gem
    を作った
    トレースで⽬星をつけて、掘り下げた調査を別で⾏うこともある
    APM
    全然使いこなせてないので、いい使い⽅教えてください!
    休憩時間や懇親会でお話しましょう

    View full-size slide

  57. 補⾜資料

    View full-size slide

  58. 各 APM
    での⼿動トレース機能
    New Relic
    add_method_tracer
    が trace_method
    とほぼ同等機能
    Scout APM
    だけで⼗分かもしれない
    Open Telemetry
    Ruby
    カスタムインストゥルメンテーション
    Custom Instrumentation
    Auto Instruments
    Manual Instrumentation

    View full-size slide