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

「大丈夫そう?」をObservabilityで確かめる

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

 「大丈夫そう?」をObservabilityで確かめる

Avatar for Yusuke Muramatsu

Yusuke Muramatsu

September 25, 2026

Other Decks in Technology

Transcript

  1. 01 microCMSとは 管理画⾯やAIエージェントからコンテンツを⼊稿し API で取得するヘッドレス CMS microCMS microCMS 編集者 管理画⾯で

    コンテンツを⼊稿 ⼊稿 Web / アプリ 取得 管理画⾯ コンテンツAPI 開発者が API で コンテンツを取得 コンテンツAPI は、実際のユーザー向けサービスから⽇常的に呼ばれている経路 5
  2. 01 microCMSとは 今回の話に関わる microCMS の特徴 01 APIスキーマ 利⽤者ごとにスキーマを⾃由に設計できる 02 様々なAPIスキーマの種類

    Media (画像‧ファイルなど)‧参照コンテンツ‧カスタムフィールド ‧repeater‧richEditorなどなど... 7
  3. 01 microCMSとは microCMSでのGrafana活⽤(⼀例) TODAY 1. 2. 3. 異常を検知‧通知する 原因を追う 本番に出してよいか判

    断する Metrics / Alerting / OnCall Loki / Tempo 今⽇はこの話 Trace IDでログとトレースを関連付ける 9
  4. 02 柔軟さゆえの難しさ コンテンツAPI の fields と depth title と eyecatch

    だけを返してほしいリクエスト GET /api/v1/articles?fields=title,eyecatch&depth=1 article fields title 返す eyecatch(Media) 返す 返す項⽬を絞る depth body(richEditor) 参照を何段まで展開するか 本⽂の画像 A‧B‧C 返さない この記事 relatedArticles(参照) depth=1 はここまで ↓ 参照先の記事 参照先の本⽂画像 D‧E‧… 返さない ↓ その先の参照 11
  5. 02 柔軟さゆえの難しさ 課題:リクエストのたびにMediaを⼤量に読んでいた 返さなくていい Media まで読むので、DynamoDB への読み取りが膨らんでいた コンテンツAPI に リクエスト

    返さない Media まで 読み取る DynamoDB の 読み取り負荷が上昇 読み取りスロットル が発⽣ リクエストのたびに アクセスが多いほど DB への読み取り負荷が⾼まり、問題が起きていた 12
  6. 02 柔軟さゆえの難しさ 当時の実装は、fields / depth で絞る前に Media を取得していた 処理の順番 コンテンツ

    API リクエスト 1. 2. 3. コンテンツと参照先 コンテンツを取得 Media を取得 (DynamoDB) fields / depth で絞 る レスポンス 絞る前に Media を取得していた = 返さない Media まで先に DynamoDB から読んでいた 13
  7. 02 柔軟さゆえの難しさ 改善後:絞ってから必要な Media だけ取得する ②と③を⼊れ替えただけ コンテンツ API リクエスト 前の③

    前の② 1. 2. 3. コンテンツと参照先 コンテンツを取得 fields / depth で絞 る Media を取得 (DynamoDB) レスポンス ②と③を⼊れ替えただけ 取得そのもののやり⽅は変えず、返すと決まった Media だけを読む 14
  8. 03 本番環境でも安全と⾔えるかわからない 直し⽅は単純でも本番の形が読めない 直し⽅ 返す Media だけ読む でも本番にあるのは ⇄ ‧利⽤者ごとに違うスキーマ

    ‧fields と depth の無数の組み合わせ ‧参照‧カスタムフィールド‧repeater ‧richEditor の新旧、旧形式の Media ‧何年も前に作られたデータ やることはこれだけ 16
  9. 03 本番環境でも安全と⾔えるかわからない テストが通っても全てを網羅しているとは⾔えない テストサイズごとに段階を分けて、最後にリリースしている ✓ ✓ ✓ Small test Medium

    test Large test リリース STG環境での E2E ? 本番のすべてのデータの形はリリース前のサイクルで再現しきれない 本番にしか存在しないデータの形がある以上、事前の確認だけでは全体の正しさを⾔い切るのが困難 17
  10. 03 本番環境でも安全と⾔えるかわからない かつ、間違っていても正常に⾒える 絞り込みロジックにバグがあったとき ダッシュボード レスポンス HTTP status 200 OK

    Latency 改善 Error rate 正常 画像 でも ? 画像 必要な画像が 1 枚だけ⽋ける レイテンシやエラー率だけでは、画像の⽋落に気づけ ない 18
  11. 04 何がわかると安全と⾔えるのか 何が観測できれば本番に出してよいと⾔えるか コードを書く前に、判断条件を決めた 01 02 03 効果 正しさ 観測の健全性

    どれくらい Media の読み取 り候補を減らせるか 必要な Media を候補から落 としていないか 候補の計算そのものが失敗 していないか この三つが確認できたら切り替えると先に決めてから実装に⼊った 21
  12. 04 何がわかると安全と⾔えるのか 観測中は挙動を変えず裏側で新旧を⽐べた レスポンスも DynamoDB の読み取りも、観測中は従来どおり リクエスト コンテンツと 参照先を取得 Media

    を広く 読み取り fields / depth で絞る レスポンス 裏側で同時にやったこと 改善後ならどの Media を読むかをメモリ上で計算(追加の DB アクセスなし) 効果 実際に読み取った件数 ⇄ 改善後の候補件数 正しさ 返す Media が候補から漏れていないか 観測中は新しい候補で DB を読まない。切り替え後にはじめて、実際の読み取りに使う 23
  13. 04 何がわかると安全と⾔えるのか 観測① スパン属性で減らせる件数を⽐べた(Tempo) 1 リクエスト=1 トレース。親⼦のスパンに、⽐べたい 2 つの数字を記録した contents-api

    親 span(コンテンツAPI の処理) media.candidate_count 改善後なら読む候補の件数 request.fields_count / request.depth / request.reference_count / request.source どんなリクエストで減るのかを⾒分けるため。fields の中⾝そのものは記録していない ⽐べる media-read media.read_count ⼦ span(Media の読み取り) 実際に読み取った件数 この 2 つを 1 リクエストずつ⽐べれば、どれだけ減らせるかが分かる ※ スパン名‧属性名は説明のために簡略化 24
  14. 04 何がわかると安全と⾔えるのか 観測② 構造化ログで、取りこぼしと計算失敗を拾った(Loki) 起きてはいけないことが起きた瞬間に1 件ずつ記録する MEDIA_DROP レスポンスで返す Media が、新しい候補に⼊っていなかった

    = 切り替えていたら、この画像が⽋けた可能性がある media_id / source / service_id を⼀緒に残す CANDIDATE_ERROR 改善後の候補を計算する処理そのものが失敗した = 観測の結果を信じてよいかが分からなくなる 本番のレスポンスには影響させない この 2 つが 0 件であることを切り替えてよいかの条件にした ※ ログ名は説明のために簡略化 25
  15. 05 判断のためのTempo‧Loki活⽤ Tempo:なぜトレースにしたか 件数だけでなく、どんなリクエストで減るのかを⼀緒に⾒たかった リクエストの⽂脈と⼀緒に⾒たい 数字は親⼦の span に分かれている どんな fields

    / depth のリクエストで、どれだけ減 るのか 集計クエリ 1 つでは差が取れず、トレースを開い て並べて読んだ 旧形式の Media がまだ使われているかを確認したクエリ { resource.service.name = "contents-api" && span:name = "media-read" && span.media.old_format_count > 0 } | count_over_time() 27
  16. 05 判断のためのTempo‧Loki活⽤ Loki:なぜログにしたか 起きてはいけない出来事は、頻度ではなく起きた瞬間の詳細が要る 起きた瞬間の詳細が欲しい ハマったところ どの Media が、どこから参照され、どの利⽤者で 起きたか

    2 つのコード名を並べると AND になり常に 0 件。 OR は正規表現で書く 取りこぼしと計算失敗をまとめて探したクエリ例 { service_name="contents-api" } |~ `MEDIA_DROP|CANDIDATE_ERROR` 28
  17. 05 判断のためのTempo‧Loki活⽤ 取りこぼしも計算失敗も観測した範囲では 0 件 Loki でヒット 0 件 =

    観測範囲では、取りこぼしも観測処理の失敗も検知しなかった 注意 MEDIA_DROP CANDIDATE_ERROR 「起きていない」の証明ではない 0 件 0 観測できた範囲では検知しなかったという意味。 件 30
  18. 06 観測結果をどう判断につなげたか 観測‧テスト‧異常時の備えを合わせて、切り替えを判断した 観測した期間‧条件で 0 件だった結果に、テストと異常時の備えを重ねて判断した ① 観測で確認した条件 ② 事前のテスト

    ③ 切り替え‧異常時の備え どんな条件で観測した結果なのか 観測とは別に、テストでも確認したこと 切り替えたあとに備えたこと ‧重いサービス ‧さまざまな fields∕depth ‧⼀覧∕個別、richEditor の新旧 ‧ピーク時間帯 ‧テストで、必要な Media の レス ポンスが残ることを確認 ‧対象:repeater、relation、 richEditor の新旧 ‧観測の導⼊と、実際の切り替え を分けて実施 ‧処理が例外になった場合は、従 来の全件読み取りへ戻す ‧異常を検知した場合は revert す る準備をしておくなど 切り替えの判断 33
  19. 06 観測結果をどう判断につなげたか 効果:DynamoDB の読み取り負荷が減った DynamoDB の読み取り量 読み取りスロットル 約 68% 減

    約 84〜86% 減 ‧返さない Media の読み取りが減った ‧DynamoDB への無駄な読み取りと負荷が減った ‧読み取り集中によるスロットルの抑制にもつながった 主な成果は、不要な Media 読み取りと DynamoDB 負荷の低減。 まだ発⽣するスロットルに対しては別途対応中... 35
  20. 07 まとめ 分かったこと‧残った課題 できたこと 無駄な Media 読み取りは減らせた ⾔えないこと 0 件は「絶対に安全」の証明ではない

    残った課題 ‧メディアのテーブル再設計は、別の課題として残っている ‧観測にもコストがあるため、必要な期間と粒度を考える必要がある 39