コード品質分析を活用して、コードを効率的にリファクタリングしよう【Findy Team+】

こんにちは!ファインディでプロダクト開発部のVPoEをしている浜田です。

Findy Team+では、コードの複雑さを可視化して技術的負債の解消に役立てられる「コード品質分析」という機能の提供を開始しました。

4つのコード品質に関連する指標を組み合わせて、ファインディ独自のスコアとして可視化しています。実際に活用いただく中で、「スコアが低いとき、限られた工数でどの指標から優先的に改善すればよいか」という、リファクタリングの優先順位に踏み込んだご質問をいただくことが増えてきました。

この記事ではコード品質の可視化が求められる背景から、4つの指標の読み解き方、効率的にリファクタリングを進めるための優先順位、具体的なリファクタリングパターンまでを紹介します。 コード品質分析を使っている方はもちろん、複雑度の指標を使ってリファクタリングを進めたいと考えているすべての方に参考にしていただける内容です。

なぜ今、コード品質の可視化なのか

これまで人がコードを書いてきた時代にも、レビュー負荷の軽減や属人化の防止、リファクタリング投資の判断材料にするなど、コード品質を測る目的はありました。 一方で、人がコードを書くスピードには物理的な上限があるため、品質に課題のあるコードが積み上がるペースにも自然と歯止めがかかっていました。

AIがコードを書く時代になると、この前提が変わります。 コードの生産量が爆発的に増える一方で、人がレビューできる量には限界があるため、品質への配慮が不十分なコードがこれまでにないスピードで積み上がる可能性があります。 さらに、既存のコードはAIがコードを生成するときの入力にもなるため、品質の低いコードを放置すると、そこから生成されるコードの品質にも影響が波及していきます。

このような変化から、コード品質の可視化は「あれば便利」なものから、AI時代の開発組織に欠かせない前提条件に変わりつつあると考えています。

ファインディではAI時代の開発組織が見るべきものを「開発資本(Engineering Capital)」として定義しています。 開発資本は、Speed・Quality・Controlの3つの軸で開発組織の状態を捉えるフレームワークです。 このうち、コンスタントに価値を届けられるかを捉えるQualityと、変更を予測し制御できるかを捉えるControlでは、コード品質の指標が観点の1つになっています。 この記事で紹介するコード品質分析は、開発資本のQualityとControlをコードの複雑さという切り口から支える機能です。

コード品質分析のよくある活用事例

コード品質分析を活用したくなる場面は、大きく次の3つのパターンに分けられます。

活用事例 内容
リファクタリング投資の意思決定・効果測定 どこに負債が溜まっているか、直した結果どう変わったかを数字で示したい
モジュール間やAIと人のコード品質比較 モジュールごとや、AIが生成したコードと人が書いたコードの品質を横並びで把握して、改善に活かしたい
OSS化や外部公開の判断 対外的に公開するコードなので、客観的な指標で品質を確認したい

いずれの活用事例も、根底にあるのは「コードの質を語れるデータが欲しい」という共通の課題です。 この「質を語れるデータ」を提供するのがコード品質分析です。

コード品質分析とは

コード品質分析は、コードの複雑さを可視化し、どこに技術的負債が溜まっているか、どこをリファクタリングすべきかを判断できるFindy Team+の分析機能です。 リポジトリ・ファイル単位で、次の4つの指標を計測しています。

指標 何を測っているか
循環的複雑度 プログラム中の「分岐の数」
認知的複雑度 人間がコードを読むときの「頭の負担」
保守性指数 0〜100の総合スコア。大きいほど良好
最大ネスト深度 「ifの中のifの中の…」という階層の深さ

それぞれの指標はソフトウェア開発で一般的に広く使われているものであり、Findy Team+ではこの4つの指標を組み合わせて独自の品質スコアを算出しています。 「読みづらさ・変更しにくさ」という同じ課題を異なる角度から幅広く捉え、バランスよく評価できるように設計しています。

4つを組み合わせることで、「どこが」「どう悪いのか」を具体的にチームにフィードバックできるようになります。 各指標には「Good」とされる目安の水準があり、どのファイルのどの指標が目安を超えているかをツール上で確認できます。

なお、コード品質分析が測っている「保守のしやすさ」は、システムやソフトウェアの品質を評価する国際規格ISO/IEC 25010が定義する品質特性の1つ「保守性」に対応しています。 「保守性のスコアがどう変化したか」という報告は、感覚値ではなく国際標準の品質特性に基づいた説明になるため、経営層やステークホルダーへの報告にも活用できます。

これらの指標を活用すると、リポジトリごとに技術的負債がどこに溜まっているかを数字で把握し、リファクタリング前後で品質がどう変わったかをグラフで示せるようになります。 モジュールやリポジトリを横並びで比較して組織としての打ち手を決めたり、「開発スピードだけでなく、質も担保している」ことをデータで説明したりと、先ほどの3つの活用事例はいずれもこの機能で実現できます。

品質スコアが低いとき、どの指標から改善するか

コードの品質は「将来の変更のしやすさ」を左右します。 品質が低いコードは、機能追加や不具合修正のたびに時間がかかり、修正時に新しい不具合も生まれやすくなります。

コード品質分析を導入する前は、「最近触っていて読みづらかったから」といった開発者の勘や記憶を頼りに、リファクタリング対象を選ぶことが多いのではないでしょうか。 導入後はファイル単位で指標が可視化されるため、「どのファイルの、どの指標が悪いのか」という数字を起点に着手先を決められるようになります。

とはいえ、4つの指標すべてが悪化しているファイルを前にすると、どこから手を付ければよいか迷ってしまいます。 そんなときは、品質の低いコードを「長くて読みにくいマニュアル文書」にたとえてみると、それぞれの指標の意味を直感的に捉えられます。

指標 文書にたとえると
最大ネスト深度 括弧の中に括弧、さらにその中に括弧…という入れ子の深さ
保守性指数 1つの章が長すぎて全体像がつかめない度合い
認知的複雑度 読む人の頭にかかる負担の総量
循環的複雑度 「Aの場合は…Bの場合は…」という場合分けの数

このたとえを踏まえて、私がおすすめしている改善の順序を紹介します。

まず「入れ子」を浅くする

「もし〜なら、もし〜なら、もし〜なら…」と何重にも条件が重なった箇所は、読む人が頭の中で覚えておくべき前提が積み重なり、理解の妨げになります。 これを「該当しない場合は先に終了する」という書き方に直します。

後述しますが、この改善は作業が小さく、既存の動きを壊すリスクが最も低いのに、読みやすさへの効果が大きいのが特徴です。

次に、大きすぎるファイルを役割のまとまりごとに分ける

品質が低いファイルの多くは、1つのファイルが複数の役割を抱え込んでいます。 文書で言えば「1つの章に無関係な話題が詰め込まれている」状態です。

役割ごとに分けると、探しやすさ・直しやすさが根本的に改善します。 ただし「短くするための機械的な分割」は、かえって話があちこちに飛ぶ読みにくい文書になるのと同じで逆効果なので、あくまで意味のまとまりで分けます。

最後に、残った場合分けを個別に判断する

ここまでの改善を終えると、読み手の負担である認知的複雑度は大部分が自然に解消しています。 最後に残る「場合分けの数」は、業務ルールそのものの複雑さを映していることが多い点に注意が必要です。

実際の業務に10通りの場合分けがあるなら、コードにも10通りの場合分けが必要です。 これを無理に減らすとかえって分かりにくくなるため、「本当に減らせる場合分けか」を見極めながら最後に対応します。

この順序をおすすめする理由は、「安全で小さい改善→構造の整理→慎重な判断が必要な改善」という並びになっていることです。 改善作業そのものが新しい不具合を生むリスクを最小限に抑えながら進められます。

ここまでの「この指標がこう出たらこう判断する」という流れをフローチャートにまとめると、次のようになります。

flowchart TD
    A["品質スコアが低いファイルを特定"] --> B{"最大ネスト深度が深い?"}
    B -->|はい| C["ガード節で入れ子を浅くする"]
    B -->|いいえ| D{"保守性指数が低い?"}
    C --> D
    D -->|はい| E["ファイルを役割のまとまりで分割する"]
    D -->|いいえ| F{"循環的複雑度が高い?"}
    E --> F
    F -->|はい| G{"業務ルール由来の場合分け?"}
    F -->|いいえ| K{"認知的複雑度は下がった?"}
    G -->|いいえ| I["場合分けを整理して減らす"]
    G -->|はい| J["条件に名前を付けて読みやすくする"]
    I --> K
    J --> K
    K -->|下がった| H["読みやすさの改善を確認して完了"]
    K -->|まだ高い| M["残った書き方由来の複雑さを見直す"]

フローの最後に認知的複雑度を確認しているのは、この指標が「どこから直すか」ではなく「どれだけ読みやすくなったか」という成果を映すものさしだからです。 入れ子の解消やファイル分割を進めると認知的複雑度は自然に下がっていくので、リファクタリングの前後で見比べれば、狙いどおり読みやすさが改善したかを数字で確認できます。 まだ高いままなら、書き方由来の複雑さが残っているサインなので、対象のファイルを見直してみてください。

具体的なリファクタリングパターン

改善の順序で挙げた「入れ子を浅くする」「ファイルを役割ごとに分ける」「場合分けを整理する」を、具体的なコード例とともに見ていきます。 言葉だけではイメージが浮かびにくいので、代表的なパターンを順番に紹介します。

ガード節(早期リターン)で入れ子を浅くする

改善順序の1番目に挙げた「該当しない場合は先に終了する」書き方は、ガード節(早期リターン)と呼ばれています。

まずは改善前のコードです。条件を満たす場合をどんどん掘っていく書き方で、ネストが4段になっています。

function applyCoupon(user, order, coupon) {
  if (user !== null) {
    if (user.isActive) {
      if (order.total >= coupon.minAmount) {
        if (!coupon.isExpired) {
          // ここでやっと本題(クーポン適用)
          return order.total - coupon.discount;
        } else {
          return order.total;
        }
      } else {
        return order.total;
      }
    } else {
      return order.total;
    }
  } else {
    return order.total;
  }
}

読む人は「ユーザーがいて、かつ有効で、かつ金額を満たしていて、かつ期限内で…」という前提を頭に積み上げたまま本題にたどり着く必要があります。 さらにelseが下に離れて置かれるため、「このelseはどのifと対応しているのか」を目で追う負担も加わります。

これをガード節で書き直すと、ネストは0〜1段になります。

function applyCoupon(user, order, coupon) {
  // 適用できないケースを先に片付ける
  if (user === null) return order.total;
  if (!user.isActive) return order.total;
  if (order.total < coupon.minAmount) return order.total;
  if (coupon.isExpired) return order.total;

  // ここから下は「クーポンが適用できる」と確定した世界
  return order.total - coupon.discount;
}

ポイントは2つあります。 1つは、前提を覚えておく必要がなくなることです。「returnを通過した=その条件はもうクリア済み」なので、本題のコードを読むときに頭の中は空っぽでよくなります。

もう1つは、動きが何も変わっていないことです。 条件の書き方を裏返して並べ直しただけなので、既存の挙動を壊すリスクが極めて低い改善です。改善順序の最初におすすめした根拠もここにあります。

ループの中ではcontinueが同じ役割

ループの中で条件分岐が積み重なる場合は、continueがガード節と同じ役割を果たします。

// 改善前: ループ内にネストが積み上がる
for (const member of members) {
  if (member.isActive) {
    if (member.email !== null) {
      sendNewsletter(member.email);
    }
  }
}

// 改善後: 対象外を先にスキップする
for (const member of members) {
  if (!member.isActive) continue;
  if (member.email === null) continue;
  sendNewsletter(member.email);
}

「該当しない行は読み飛ばす」という宣言が先頭に並ぶので、ループの本題であるメール送信が一目で分かるようになります。

役割ごとに関数・ファイルを分ける

改善順序の2番目に挙げた「大きすぎるファイルを役割のまとまりごとに分ける」も、まずは関数単位で切り出すと着手しやすくなります。

次の関数は、1つの中で「入力の検証」「金額の計算」「通知の送信」という3つの役割を抱え込んでいます。

// 改善前: 1つの関数が検証・計算・通知を抱えている
function checkout(user, order, coupon) {
  if (user === null) throw new Error("ユーザーが不正です");
  if (order.items.length === 0) throw new Error("カートが空です");

  let total = order.items.reduce((sum, item) => sum + item.price, 0);
  if (coupon !== null && !coupon.isExpired) {
    total -= coupon.discount;
  }

  sendEmail(user.email, `ご注文ありがとうございます。合計 ${total} 円です`);
  return total;
}

役割ごとに関数を切り出すと、呼び出し側は「何をしているか」の目次だけを読めば済むようになります。

// 改善後: 役割ごとに関数へ切り出す
function validateOrder(user, order) {
  if (user === null) throw new Error("ユーザーが不正です");
  if (order.items.length === 0) throw new Error("カートが空です");
}

function calculateTotal(order, coupon) {
  const subtotal = order.items.reduce((sum, item) => sum + item.price, 0);
  const isCouponUsable = coupon !== null && !coupon.isExpired;
  return isCouponUsable ? subtotal - coupon.discount : subtotal;
}

function checkout(user, order, coupon) {
  validateOrder(user, order);
  const total = calculateTotal(order, coupon);
  sendEmail(user.email, `ご注文ありがとうございます。合計 ${total} 円です`);
  return total;
}

文書でいう「話題ごとに章を分け、目次から必要な章に飛べるようにする」操作に相当します。 1つのファイルが大きくなってきたら、こうしたまとまりを別ファイルに移すことで、ファイルあたりの分量が減り、保守性指数の改善につながります。

単純な場合分けは対応表に逃がす

改善順序の3番目に挙げた「場合分けの数」そのものを減らせるのは、分岐が単純な対応関係になっているケースです。

次のコードは、会員ランクごとに割引率を返すだけの分岐が並んでいます。

// 改善前: ランクの数だけ分岐が増える
function getDiscountRate(rank) {
  if (rank === "bronze") return 0.03;
  if (rank === "silver") return 0.05;
  if (rank === "gold") return 0.10;
  if (rank === "platinum") return 0.15;
  return 0;
}

「入力に対して値を1つ返すだけ」の分岐は、対応表(オブジェクト)に置き換えると分岐そのものがなくなります。

// 改善後: 分岐をデータに置き換える
const DISCOUNT_RATES = {
  bronze: 0.03,
  silver: 0.05,
  gold: 0.10,
  platinum: 0.15,
};

function getDiscountRate(rank) {
  return DISCOUNT_RATES[rank] ?? 0;
}

分岐が減るので循環的複雑度が下がり、ランクが増えても表に1行足すだけで済みます。 一方で、分岐ごとに処理の中身が違う場合や、業務ルールとして意味のある場合分けは、無理に表へ押し込めません。その場合は、次のように名前を付けて読みやすくする方向で対応します。

どうしても消えない条件は、名前を付けて外に出す

条件が業務ルールとして本当に必要な場合は、ネストを消すのではなく、条件の塊に名前を付けて1段浅く見せる方法があります。

// 改善前
if (order.total >= coupon.minAmount && !coupon.isExpired && coupon.usedCount < coupon.maxUses) {
  // クーポンを適用する
}

// 改善後: 条件の意味に名前を付ける
function isCouponApplicable(order, coupon) {
  return order.total >= coupon.minAmount
    && !coupon.isExpired
    && coupon.usedCount < coupon.maxUses;
}

if (isCouponApplicable(order, coupon)) {
  // クーポンを適用する
}

文書でいう「長い括弧書きを脚注に逃がして、本文は一言で済ませる」操作に相当します。 本文を読む人は「クーポンが適用できるなら」とだけ理解すればよく、詳細が必要になったときだけ中身を読みに行けます。

指標についてよくいただく質問

最後に、コード品質分析の指標についてよくいただく質問と回答を紹介します。

循環的複雑度と認知的複雑度は何が違う?

循環的複雑度は分岐の「数」を機械的に数える指標で、認知的複雑度は読む人の「頭の負担」を測る指標です。 大きな違いは、認知的複雑度はネストが深い場所にある分岐ほど重くカウントされることです。

たとえば、この記事で紹介したガード節への書き換えでは、分岐の数自体はほとんど変わらないため循環的複雑度はあまり下がりませんが、ネストが解消されるため認知的複雑度は大きく下がります。 「循環的複雑度には業務の複雑さ、認知的複雑度には書き方の複雑さが表れやすい」と捉えると、使い分けしやすくなります。

4つの指標はすべて目安(Good)の値まで下げるべき?

目安はあくまで優先順位を決めるためのシグナルであり、すべてのファイルで達成すべきノルマではありません。 特に循環的複雑度は業務ルールの複雑さを反映していることが多く、無理に下げるとかえって読みにくくなる場合があります。

「目安を超えているファイルから順に中身を見て、書き方由来か業務由来かを見極める」という使い方をおすすめしています。

どのファイルからリファクタリングを始めればいい?

スコアが悪い順ではなく、「今後も変更する予定があるファイル」から始めるのがおすすめです。 コードの品質が問題になるのは変更するときなので、スコアが低くても今後ほとんど触らないファイルの優先度は下げて構いません。

逆に、これから機能追加を予定している箇所にスコアの低いファイルがあれば、着手前にリファクタリングを検討する価値があります。

なお、優先度を下げてよいというのは、やらなくてよいという意味ではありません。 冒頭で触れたとおり、既存のコードはAIがコードを生成するときの入力にもなるため、品質の低いファイルを放置すると、人が直接触らなくてもAIが生成するコードの品質に影響する可能性があります。 変更予定のあるファイルから着手しつつ、残ったファイルも計画的に改善を進めていくことが大切です。

まとめ

この記事では、コード品質の可視化が求められる背景から、コード品質分析の4つの指標の読み解き方、品質スコアが低いときの改善の優先順位、具体的なリファクタリングパターンまでを紹介しました。

4つの指標が同時に悪化していても、「ガード節で入れ子を浅くする→ファイルを役割ごとに分ける→残った場合分けを個別に判断する」という順序で進めれば、リスクを抑えながら効率的に品質を改善できます。 まずは一番安全で効果の大きいガード節から始めてみてください。

AIがコードを書く時代、コードの品質を定量的に把握してデータに基づいて意思決定することは、今後ますます重要になると考えています。 Findy Team+のコード品質分析は、その第一歩として、どこから効率的にリファクタリングを進めればよいかをデータで明らかにします。


Findy Team+に興味を持たれた方は、ぜひこちらのページからお問い合わせください。

jp.findy-team.io

AWS Summit Japan 2026 参加レポート

こんにちは。ファインディのPlatform開発チーム(以降、SREチーム)でSREを担当している原(こうじゅん)、富田(@Cooking_ENG)、松本(@mozumasu)です。

2026年6月25日・26日に幕張メッセで開催された「AWS Summit Japan 2026」に、SREチームの3名で参加してきました。

aws.amazon.com

それぞれが印象に残ったセッションを1本ずつ取り上げ、ファインディの現状と紐づけてお届けします。

スタートアップにAmazon EKSは早すぎる? マルチプロダクト戦略を加速するPlatform Engineeringの実践

SREチームのです。

私が印象的だったのは、株式会社ログラスの中井綾一さんによるセッション「スタートアップにAmazon EKSは早すぎる? マルチプロダクト戦略を加速するPlatform Engineeringの実践」です。

ファインディはECSを中心に構成していますが、今後Platformをどのようにしていくかという判断軸を持ち帰りたいと考え、このセッションに参加しました。

ログラスは経営管理サービス「Loglass」を中心にマルチプロダクト戦略を進めている会社で、本セッションではAmazon EKSをベースにしたPlatform基盤の導入から、1年で社内に広げていくまでの実践が紹介されました。

中井さんがEKS導入を判断した時点でのチーム規模は、次の通り紹介されていました。

  • エンジニア約50名
  • プロダクト2つ
  • SREチーム4名

「マルチプロダクト戦略・SRE4名」という条件で、なぜ「重い」と言われがちなEKSを選んだのか、というのが本セッションの主題でした。

特に印象に残ったのは、技術選定の軸として中井さんが繰り返し言い切っていた「『今の規模』ではなく『数年後の事業にフィットするか』で判断する」という言葉でした。

基盤選定をする際には、「現時点で十分」「今の人数で運用できる」という現在地ベースになりがちですが、中井さんからはPlatform構築は1〜2年かかる長期投資で、課題が顕在化してから着手したのでは間に合わない、というメッセージが繰り返されていました。

実際に、SRE4名で導入を進めたPlatform基盤上で、1年で5チーム・約20サービスまで展開されたとのことです。

もう1つ刺さったのが「AI時代だからこそEKSプラットフォームに価値がある」という主張です。Coding Agentが高速に変更を生む時代には、Namespace分離・RBACやAdmission Controllerによってチーム・サービスごとの権限境界やポリシーを機械的に担保でき、開発効率と品質・統制を両立できる、という観点でした。

ここで気になるのが、ファインディの現在地です。

ファインディは現在、マルチプロダクト展開、SREチーム4名、基盤はAmazon ECS中心で、インフラ構築はTerraform汎用モジュールを介して開発者主体で進められる体制を整えてきました。

tech.findy.co.jp

直近のSREチーム全体の取り組みは、次のFindy Tech Talkでも紹介しています。

tech.findy.co.jp

マルチプロダクトと開発者主体での運用がさらに広がったときに、SREチームがボトルネックにならないPlatformの形をどう先回りで仕込むかの重要性を感じました。「事業に対してPlatformをどう先回りで仕込むか」という考え方は大変勉強になりました。

TBSテレビ「ラヴィット!」大規模配信の裏側とAWSサーバーレス設計

SREチームの富田です。

私が参加したのは、株式会社TBSテレビの亀田遼さんによるブレイクアウトセッション「TBSテレビ『ラヴィット!』大規模配信の裏側とAWSサーバーレス設計」です。

『ラヴィット!忘年会 '25』を、想定視聴者数6万人規模で配信した際の裏側を紹介する内容でした。

x.com

『ラヴィット!忘年会 '25』の配信プラットフォームには、KustamieというTBSテレビが開発している自社サービスが使われています。配信者と参加者の双方向なやりとりを充実させ、「参加感」を高めるためのイベント向けプラットフォームです。

アーキテクチャはサーバーレス中心で、データ管理にAmazon ECSとAmazon Aurora Serverless v2、クライアントAPIにAmazon API GatewayとAWS Lambda、映像とリアルタイム通信にAmazon IVS (Interactive Video Service) を利用しています。

大規模配信に向け、既存のクライアントAPI構成には次のような課題があったそうです。

  • キャッシュを前提とした構成になっていなかった
  • 配信参加時のAPIレスポンスに最長12秒程度かかっていた
  • 負荷試験を実施したことがなく、高負荷時の挙動が未知数だった

これらの課題に対する解決策の中で、特に印象に残ったのが多段キャッシュ構成によるレスポンス改善のお話でした。

採用されたのは、Amazon API Gatewayのステージキャッシュ・AWS Lambda関数のグローバル変数・Amazon ECSと併設したAmazon ElastiCache (Valkey) を組み合わせた3層構成です。

API GatewayステージキャッシュでGET系レスポンスをエッジから返し、Lambdaのグローバル変数でホットスタート時の関数初期化を省き、ElastiCacheで後段のDBアクセスを軽くする、という形で多段にキャッシュが効くようにしていました。

結果として、最長12,000ms程度かかっていた配信参加時のAPIレスポンスが、平均150ms程度まで改善したそうです。

Amazon SQSとAWS Step Functions Expressによる非同期化も合わせて実施されており、設計全体で大きな改善幅が出ていました。

負荷試験にはOSSの負荷試験ツールGrafana k6が使われており、待機配信前→待機配信中→本編配信中の3フェーズで指数的にVUを増やすシナリオで本番のアクセスパターンを再現したそうです。

直近、私もk6とDatadogでファインディの負荷試験環境を構築していたところだったので、シナリオ設計や規模の見立てが自分の作業と重なって、特に身近に感じられた話題でした。

ファインディでの負荷試験環境構築の取り組みは次の記事で紹介しています。

tech.findy.co.jp

6万人規模の同時アクセスをサーバーレス中心の構成でさばく事例として、設計の引き出しを増やせたセッションでした。

ファインディでも「Findy Conference」を中心に瞬間的なアクセス集中は発生するため、「キャッシュをどの層で重ねるか」「どこを非同期に逃がすか」を設計初期から意識する姿勢は参考にしたい考え方でした。エッジ・Lambda・ElastiCacheで多段にキャッシュを効かせる発想は、今後の負荷特性の変化に備える引き出しとして持ち帰りたいと感じています。

また、Amazon IVSのように、ライブ配信向けに使えるAWSサービスをユースケースとセットで知れたことも収穫でした。

Kustamieは2026年秋にベータ版提供開始予定とのことです。どんなイベント体験が広がっていくのか、リリースが楽しみです。

実践!Amazon RDSとAmazon Auroraのコスト最適化とパフォーマンス向上

SREチームの松本です。

私が印象に残ったのは、アマゾン ウェブ サービス ジャパン合同会社の塚井知之さんによるブレイクアウトセッション「実践!Amazon RDSとAmazon Auroraのコスト最適化とパフォーマンス向上」です。

「パフォーマンス向上」と「コスト最適化」は両立できる、というテーマのもと、架空のEC支援企業「AnyCompany」が直面する課題と、その解決策を追体験していく構成のセッションでした。

AnyCompanyには「レポートクエリによる運用DBの圧迫」「複雑クエリでの一時オブジェクト書き出しによるI/Oボトルネック」「メモリに乗り切らないホットデータ」「バックアップコストの肥大化」といった、実務でよく遭遇する課題が並んでいました。

セッション全体で繰り返し問われていたのが、「大きいインスタンスへの垂直スケーリングは、最もコスト効率の良い選択か?」という問いです。

特に印象に残ったのが、垂直スケーリングに頼らずにコストとパフォーマンスを両立する3つのパターンでした。

  • リードレプリカへのクエリオフロード(レポートクエリを逃がす)
  • RDS Optimized Reads(一時オブジェクトをローカルSSDに逃がす)
  • Aurora Optimized Reads(バッファキャッシュをローカルSSDに拡張する)

1つ目は、リードレプリカへのクエリオフロードです。

社内データアナリストのレポートクエリが運用DBを圧迫している状況で、インスタンスを1段階上げる代わりに、小さめのリードレプリカを足してレポートを逃がす構成にすることで、約15%のコスト削減と顧客APIの安定化を同時に実現できる、という試算でした。

「先に分けることでむしろ安く済む」という発想は、Amazon RDSまわりの構成判断でも応用が効く話だと感じました。

2つ目は、RDS Optimized Readsで一時オブジェクトをローカルSSDに逃がすパターンです。

複雑なクエリで生成される一時オブジェクトがAmazon EBSに書き出されてレイテンシが伸びる、というボトルネックに対し、ローカルNVMe SSDを搭載したインスタンスへ載せ替えて一時ファイルの書き出し先をローカルSSDに切り替える、というアプローチでした。

垂直スケーリングと比べて約43%のコスト削減かつ最大2倍の読み取り性能向上というのは、知らないと選べない選択肢だと痛感しました。

3つ目は、Aurora Optimized ReadsでバッファキャッシュをローカルSSDに拡張するパターンです。

メモリに乗り切らないホットデータをディスクから読み直してしまう、というボトルネックに対し、ローカルNVMe SSDをバッファプールの第2階層として使う「階層型キャッシュ」で対処します。

同等のインメモリキャッシュを得るために大きなインスタンスに上げる代わりに、小さめのインスタンス + I/O-Optimizedの組み合わせを選ぶことで、約90%のコスト削減と最大8倍の読み取り性能を実現する事例でした。

判断のステップとしてBufferCacheHitRatioAuroraEstimatedSharedMemoryBytesでワーキングセットを把握してから選ぶ、という手順も合わせて示されており、「まず可視化してから手を打つ」という基本がここでも徹底されていたのが印象的でした。

バックアップ面でも、自動バックアップの保持期間を短くし、それより古い分はAmazon S3へのParquet形式スナップショットエクスポートに切り替えることで、30%のコスト削減ができるという試算が紹介されていました。

障害復旧のためのリストア要件は直近のデータで足りる一方、監査や過去データの参照は必ずしも即時のリストアを必要としないため、S3への安価なエクスポートに逃がせます。この「リストア要件」と「参照要件」を分けて考えるとコスト構造が大きく変わる、というのはファインディの運用でもすぐに見直せそうな観点です。

どの課題でも、Database InsightsやPerformance Insightsでまずワークロードを可視化し、ボトルネックに応じて適切なインスタンスファミリー・ストレージタイプ・キャッシュ階層を選び直す、という流れで進められていたのが印象的でした。

「見えないものは改善できない」という基本を、AnyCompanyの課題を通して追体験できる構成になっていたと感じました。

まずはBufferCacheHitRatioVolumeReadIOPsVolumeWriteIOPsを見直し、I/O-OptimizedやOptimized Readsが効くワークロードがないかを棚卸しするところから始めたいと思います。

まとめ

2日間を通して、各社の事例や最新サービスに直接触れられる濃いインプットの機会となりました。負荷試験のシナリオ設計や、Amazon RDS/Auroraのコスト最適化、マルチプロダクト時代のPlatformの形といった、現地で持ち帰った論点を、SREチームの日々の取り組みに少しずつ活かしていきます。

ファインディでは、SREメンバーを募集しています。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

可視化と改善を合わせて考える ― メトリクスを起点にAI推進を成果へ繋げる回し方

こんにちは。ファインディ株式会社でテックリードをしている戸田です。

先日公開したFindy AI Meetup in Fukuoka #6のイベントレポートで、「AIで開発が速くなったはずなのに、レビューが詰まってトータルの生産性が変わらなかった」という話に触れました。

tech.findy.co.jp

そこで今回は、そのレビュー詰まりを題材に、数値を見る → 仮説を立てる → 改善アクションを打つ → 結果を検証する というワンサイクルを1つの実例として紹介します。

Findy Team+Findy AI+を組み合わせて、AI導入前後の数値を可視化し、そこから仮説を立て、改善アクションを打ち、結果を検証するまでの一連の流れを追っていきます。

AIで速くなったはずなのに、1人あたりの生産性は変わらなかった

開発でAIを使い始めたときの私たちの想定はシンプルでした。AIがコードを書いてくれるなら、1人あたりが生み出すアウトプットは増え、開発生産性も自然と上がる はずだ、というものです。

実際、ファインディ社内でのAIの利用率は順調に伸びていきました。多くのエンジニアが日常的にAIを使うようになり、実装そのものは速くなったという実感の声もありました。

そこで本当に生産性が上がったのかを確かめるため、Findy Team+の詳細比較機能を使ってAI導入前後の各種メトリクスを比較してみました。

すると1人あたりの生産性はほとんど変わっていなかったのです。人が増え、AIで実装も速くなった。それでも1人あたりのアウトプットは増えていない。この食い違いが、最初の重要な問いになりました。

メトリクスを深掘りして見えた「レビュー詰まり」

AI導入前後で比較した結果、1人あたりのプルリクエスト作成数は横ばいのままでした。

1人あたりのプルリクエスト作成数の推移(左: AI導入前の2024年 / 右: AI導入後の2025年)

この理由を探るため、Findy Team+で工程ごとのメトリクスに分解して深掘りしました。

ここで注目したのは、開発フローの段階ごとの指標です。変更のリードタイム/サイクルタイム、プルリクエスト作成数、そしてプルリクエストが作られてからマージされるまでのレビュー時間、オープン時間。これらを並べて、どの段階が想定と違う動きをしているのかを見ていきました。

まず変更のリードタイムに注目しました。すると、コミットからオープンまでの平均時間は10%ほど速くなっていました。 しかし、オープンからレビューまでの平均時間は15%ほど、レビューからアプルーブまでの平均時間は30%ほど遅くなっていた ことがわかりました。

コミットしてからプルリクエストを作成するまでは若干速くなったものの、プルリクエストのオープンから、レビューをもらって最終的にアプルーブをもらうまでの時間がAI導入前よりも悪化していた ことが、数値から読み解けました。

ここまでで 1人あたりの生産性が変わらなかった理由が、プルリクエストを作成してからマージされるまでのフローにある ということが見えてきました。

プルリクエストを作成してからマージされるまでに行われる工程の1つにレビューがあります。そこでレビューの工程に関するメトリクスに注目してみました。すると、プルリクエストに対する平均コメント数が50%ほど増えている ことがわかりました。

ここまで深掘りしてわかったことは、AIによって実装にかかる時間は確かに短くなっている一方で、レビューの工程に問題が出ていたということです。

つまり、AIは開発フローの「実装」という一工程を速くしていました。しかし実装が速くなった分だけプルリクエストがレビュー待ちに積み上がり、フロー全体ではかえって詰まりやすくなっていたのです。

仮説

ここまでの分析から1つの仮説を立てました。AIで実装が速くなった分、プルリクエストの質が下がっているのではないか。その結果レビューでのやり取りが増え、アプルーブまでの時間が延び、最終的にリードタイムが悪化している のではないか、という見立てです。

この仮説の裏付けを取るために、実際にレビューでやり取りが膨らんでいたプルリクエストを取得し、コメントの中身を分類しました。

わかったのは、指摘されていた内容の半分以上が作成者自身のセルフレビューで防げるような内容だったということです。例えば、消し忘れたデバッグコード、命名やフォーマットの不統一、考慮漏れのエッジケースや、そもそもビジネスロジックと要件が一致していないなど、本来は提出前にプルリクエストの作成者が気づかないといけない指摘です。

AIが書いたコードを十分に理解しきらないままレビュー依頼を出してしまい、本来は作成者が気づくべき部分をレビュアーがコメントで補っていたのです。プルリクエストへのコメント数が50%増えていたのは、この肩代わりの表れだったわけです。仮説は、実際のプルリクエストの中身によって裏づけられました。

改善アクション

原因が レビュー依頼前のセルフレビュー不足 にあるとわかれば、打つ手は具体的になります。私たちは、セルフレビューを個人の心がけに任せるのをやめ、仕組みとして組み込むことにしました。

具体的には、複数の観点を同時にチェックするセルフレビュー用のスキルを用意しました。そして、プルリクエストを作成するタイミングでこのスキルが強制的に実行されるようにしました。提出前に、AIが書いたコードをAI自身が複数の観点からレビューする工程を、フローの中に固定したのです。

このセルフレビュー用スキルの設計や中身については、別記事で詳しく解説しています。

tech.findy.co.jp

結果検証

改善アクションを打ったあとは、必ず数値に戻って効果を確かめます。このとき、いきなりリードタイムを見るのではなく、まず 施策がちゃんと使われているか という観点を確認するのがポイントです。

まずFindy AI+で、用意したスキルの実行状況を追いました。導入直後はばらつきがありましたが、運用に乗るにつれてスキルの利用率が上がっていきました。

Findy AI+のSkills Usageで見たセルフレビュー用スキル(self-reviewer)の実行回数

利用率が十分に上がったタイミングで、改めてFindy Team+の数値を確認しました。すると、レビューからアプルーブまでの平均時間が10%ほど速くなっている ことがわかりました。実装が速くなった効果が、ようやくフロー全体の速さとして表れ始めたのです。

また、プルリクエストの平均コメント数や変更障害率も若干の改善が見られました。セルフレビューのスキルを強制したことにより、プルリクエストの質が上がり、不具合も減った ことが数値から読み取れました。

あわせてFindy AI+で、AIの使われ方そのものの推移も見てみました。改善のサイクルを回していくなかで、トークン使用量もセッション数も右肩上がりに伸びていました。そして同じ時期に、プルリクエストの作成数も増えています。

AIの活用と開発生産性がようやく同じ方向に動く、つまりAI推進と生産性がリンクし始めた ことが数値から読み取れました。

Findy AI+で見たトークン使用量とセッション数の推移。改善のサイクルを回すなかでどちらも右肩上がりに増加

もし数値が動いていなければ、仮説か改善アクションのどちらかが外れているということなので、また数値を見るステップに戻ります。当たっていれば、次のボトルネックを探しに行く。これを繰り返すのが、可視化と改善のサイクル となります。

まとめ

このように可視化と改善を繰り返した結果、横ばいが続いていた1人あたりのプルリクエスト作成数も増加に転じました。AI導入前後の2024年から2025年では変化がありませんでしたが、セルフレビューを仕組みに組み込んだあとでは、1人あたりのプルリクエスト作成数が約1.5倍に増えていました。

1人あたりのプルリクエスト作成数の推移(左: 2025年 / 右: 2026年)。約1.5倍に増加

今回お伝えしたかったのは、可視化と改善はセットで考える ということです。

数値を可視化するだけでは、現場は変わりません。今回も、Findy Team+で生産性が横ばいだと可視化できただけでは何も解決せず、そこからメトリクスを深掘り、仮説を立て、プルリクエストを分析して、セルフレビューを仕組みに組み込むという改善まで進めて、初めてリードタイムに変化が出ました。

逆に、可視化のない改善は再現性のないものになります。もし可視化をせずに「レビューが詰まっている」という感覚だけで打ち手を考えてしまうと、原因がプルリクエストのセルフレビュー不足にあることにも、施策が効いたかどうかにも辿り着けませんでした。

可視化が改善の入口を教え、改善の結果がまた数値に表れる。数値を見る → 仮説を立てる → 改善アクションを打つ → 結果を検証する というワンサイクルは、可視化と改善を再現性高く行う ための型です。

AIを導入しただけではアウトプットも生産性も上がりません。可視化された数値を起点に改善のサイクルを回し、ボトルネックを一つずつ解消していく。これによって初めて、AI活用は開発生産性まで繋がってきます。

私たちもまだ、次の問題を探しながら可視化と改善のサイクルを回し続けている途中です。開発生産性の向上やAI推進は、可視化と改善のサイクルを継続することで、継続して積み重ねていくもの なのです。

ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

ゼロから始める負荷試験環境構築 — Grafana Cloud k6とDatadogで本番トラフィックを再現する

こんにちは。 CTO室/Platform開発チームでSREを担当している富田(@Cooking_ENG)です。

ファインディの「Platform開発チーム」は全社横断のSREの役割を担っています。当社のサービスがどれほどの負荷に耐えられるかを把握し、性能の問題を表面化・改善することで、ユーザーに安定したサービスを提供できる状態を目指しています。

そこで、Grafana Labsが提供するGrafana Cloud k6を採用し、負荷試験環境をゼロから構築しました。今回は、Findy Conferenceを対象に負荷試験を実施しています。

Findy Conferenceは、テックカンファレンスに特化したプラットフォームサービスです。

conference.findy-code.io

この記事では、負荷試験ツールの選定から、本番トラフィックを再現するシナリオの作成、Grafana Cloud k6とDatadogを組み合わせた負荷試験の実施までの取り組みを、初めて負荷試験を担当するエンジニアの方々や、これから導入を考えているチームに向けて紹介します。

負荷試験環境を作ることになった背景

Findy Conferenceでは、セッションの開始や終了のタイミングで、参加者の方々が一斉にアクセスする瞬間的なスパイクが発生する特徴があります。

過去のBlogにまとめているので、気になる方はぜひこちらの記事もご覧ください。

tech.findy.co.jp

こうしたスパイクに備えるため、カンファレンス開催前にコンテナ数を増やす運用をしてきました。コンテナ数を決める際の判断はチームの経験値に基づいていたため、本当にその数で十分なのか、過剰になっていないかを根拠を持って判断できない状態でした。

そこで、想定するスパイクに対して実際の計測値に基づいた最適なコンテナ数を設置できる状態を目指し、負荷試験環境の整備に取り組むことにしました。

負荷試験ツールの選定

ファインディで重視した判断軸

ツール選定にあたり、ファインディの状況に合わせて次の判断軸を置きました。

  • インフラ管理が不要であること(環境の構築・運用コストを抑えたい)
  • シナリオ作成の自由度(本番に近いユーザー導線を再現できる)
  • サービスの継続性(SaaSとして長く使えるか)
  • コスト(従量課金で、使わないときに費用が膨らまないこと)

候補ツールの比較

候補として、次のツールを比較しました。

サービス シナリオ記述 提供形態 運営元 特徴・得意領域
Grafana Cloud k6 JavaScript SaaS(OSS版あり) Grafana Labs モダンな開発体験、Grafana統合、専用ダッシュボードが標準。OSS版でローカル実行も可能
BlazeMeter JMeter/GUI SaaS Perforce JMeter資産を活かしやすい。詳細なレポートとGUI操作に強み
Gatling Enterprise Scala/Java SaaS Gatling Corp 大規模負荷試験に強み。Javaエコシステムとの親和性が高い
Locust Python OSS Locustコミュニティ Pythonでシナリオを柔軟に記述できる。軽量で分散実行にも対応
JMeter GUI/XML OSS Apache Software Foundation 長年の実績と豊富なプラグイン。対応プロトコルが幅広い

それぞれのツールには得意な用途があります。今回はファインディの判断軸を基準に評価しました。

評価の結果、Grafana Cloud k6を採用しました。

先に挙げた4つの判断軸それぞれに対する評価は次のとおりです。

判断軸 Grafana Cloud k6での評価
インフラ管理 マネージドサービスのため、負荷試験用の環境を自前で構築・運用する必要がない
シナリオ作成の自由度 JavaScriptベースでシナリオを記述するため、フロントエンド/バックエンド双方のエンジニアが読み書きしやすい
サービスの継続性 運営元のGrafana Labsが監視・可視化領域のサービスを長く提供している実績があり、長期利用の見通しを立てやすい
コスト Proプランの月額が$19、500 VUh/月までは無料枠、超過分も$0.15/VUhの従量課金で、使わないときに費用が膨らまない(2026年6月時点。最新は公式料金ページを参照)

負荷試験のシステム構成

Grafana Cloud k6とDatadogの役割分担

負荷シナリオの作成

ここからは、実際に負荷シナリオをどう作っていったかを紹介します。

har-to-k6を使ったスクリプトの作成

本番に近いユーザー導線を再現するには、ユーザーがどのエンドポイントにどの順でアクセスするかをシナリオとして書き起こす必要があります。当初は手作業でシナリオを書こうとしましたが、ステージング環境ではCognitoに加えてファインディ独自の認証基盤「Findy ID」など複数の認証を突破する必要があり、認証の流れをスクリプトで再現するのは簡単ではありませんでした。

そこで、har-to-k6を使い、ブラウザの操作ログからk6のシナリオを生成する方法に切り替えました。har-to-k6は、ブラウザの開発者ツールで書き出せるHAR(HTTP Archive)ファイルを入力として、k6用のJavaScriptシナリオを生成してくれる公式ツールです。ブラウザで一度認証を通したあとの操作をHARとして記録すれば、認証後のリクエストをそのまま再現できるため、手作業で書くよりも本番に近いシナリオを効率よく用意できました。

おおまかな流れは次のとおりです。

  1. ブラウザの開発者ツールで、再現したい操作を実行し、HARファイルを書き出す
  2. har-to-k6 input.har -o script.jsでk6用のJavaScriptに変換する
  3. 生成されたスクリプトを、シナリオに不要なリクエストの除去や認証情報の環境変数化などに合わせて整える

HARから変換した直後のスクリプトには、画像・CSS・JavaScriptファイルなどの静的アセット取得のリクエストや、HARに含まれていた認証情報(パスワードなど)がそのまま残っています。これらの整理にはClaude CodeなどのAIコーディング支援ツールが便利でした。

ただし、平文のパスワードをそのままAIに渡すのはセキュリティ上避けたいので、先に認証情報を__ENV.TEST_PASSWORDのような形で環境変数化し、それから不要なリクエストの除去をAIに任せました。AIを活用することで、手作業で1つずつ削るよりも短い時間で本番に近いシナリオを用意できました。

負荷のかけ方を調整するOptionsブロックの話

k6のシナリオでは、optionsというブロックで負荷のかけ方を細かく制御できます。今回のシナリオでは、ramping-arrival-rateというexecutorを使って、スパイクの立ち上がり・ピーク・収束を1本のシナリオで再現する構造を採用しました。

なお、k6では負荷シナリオの関数が1回実行されることを「イテレーション」と呼びます。今回のシナリオは1イテレーションのなかで複数のHTTPリクエストを送る構成です。

実際のOptionsブロックは次のような形です(数値は記事用のダミー値であり、対象サービスや想定するスパイク規模に応じて調整します)。

export const options = {
  scenarios: {
    spike: {
      executor: 'ramping-arrival-rate',
      startRate: 0,
      timeUnit: '1m',
      preAllocatedVUs: 50,
      stages: [
        { duration: '10s', target: 100 },
        { duration: '10s', target: 200 },
        { duration: '50s', target: 200 },
        { duration: '10s', target: 0 },
      ],
    },
  },
  cloud: {
    distribution: {
      tokyo: { loadZone: 'amazon:jp:tokyo', percent: 100 },
    },
  },
};

主な設定項目は次のとおりです。

設定項目 説明
executor 負荷のかけ方のパターン。ramping-arrival-rateは、各ステージのtargetに向けてイテレーション数をなめらかに増減させ、山型の負荷曲線を表現できる
startRate 開始時点のレート。今回は0から立ち上げる
timeUnit targetの単位時間。'1m'にすると目標値を「1分あたりのイテレーション数」で指定できる
preAllocatedVUs 目標レートを捌くために事前に確保しておく仮想ユーザー(VU)数
target 各ステージで到達させたい目標イテレーション数/timeUnittimeUnit: '1m'ならtarget: 100は「1分あたり100イテレーション」を意味し、durationをかけてその値までなめらかに増減する

timeUnit'1m'にしているのは、過去のカンファレンスの実測値を「1分あたりのリクエスト数」で把握していたためです。この実測リクエスト数を1イテレーションあたりのリクエスト本数で割ることで、必要なイテレーション数(target)を算出し、シナリオの単位もこれに揃えました。

stagesを4段に分けているのは、本番のスパイクを次のような流れで再現するためです。

  • 1段目(10秒):本番想定の半分まで急速に立ち上げ、アクセス急増の前兆を再現
  • 2段目(10秒):本番想定まで一気に到達させる
  • 3段目(50秒):本番想定の負荷を維持して、システムが安定して捌けるかを観察する
  • 4段目(10秒):目標値を0に戻し、収束させる

cloud.distributionでは、Grafana Cloud k6上で負荷を発生させるAWSのリージョンを指定しています。今回は実際のユーザーアクセスに近づけるため、東京リージョン(amazon:jp:tokyo)を100%としました。

想定する負荷がかけられているかをDatadogを使って確認する方法

ここがいちばん試行錯誤したところでした。

k6側のレポートで「目標としていたイテレーション数を発行した」と表示されていても、それがアプリケーションのコンテナに実際に届いたリクエスト数と直接一致するわけではありません。今回のシナリオは1イテレーションで複数のリクエストを送るためです。

そこで、まず「イテレーション数 × 1イテレーションあたりのリクエスト本数」で、アプリケーションに届くはずのリクエスト数(期待リクエスト数)を見積もりました。そのうえで、k6側のtargetを微調整しながら、期待リクエスト数どおりの負荷がかかるように調整していきます。

調整したリクエストが実際に届いているかは、Datadogで確認します。ファインディではもともとDatadogでオブザーバビリティに取り組んでおり、各サービスのダッシュボードを運用しています。負荷試験のメトリクスも普段の監視と同じダッシュボードで確認できるため、状態を把握しやすい環境がすでに整っていました。

ただし、どのメトリクスを見るかで数字の意味が変わります。観測候補をいくつか比較した結果、ALBのレスポンス数を見ることにしました。今回はレスポンス数を、アプリケーションが実際に処理した負荷の指標として扱っています。

観測対象 負荷確認の用途として 理由
ALBのレスポンス数 適している アプリケーションが実際に返したレスポンス数。見積もった期待リクエスト数と突き合わせやすい
CloudFrontのリクエスト数 向かない CDNキャッシュや静的アセット分が水増しされ、k6の発行数とずれる
APMのリクエスト数 やや向かない サンプリングで一部を間引いて記録するため、実際より低めに見えることがある

ALBのレスポンス数の推移を見て、見積もった期待リクエスト数どおりの負荷が処理されているかを判断します。値が想定より少ない場合は、CloudFront側で弾かれているリクエストがあるか、シナリオ側で意図しないエンドポイントを叩いていないかを見直すサインになります。

次の図は、ある負荷試験でのALBのレスポンス数の推移です。シナリオで設計したとおり、短時間で立ち上がってピークに達し、収束していく山型になっていることが確認できます。

負荷試験中のALBのレスポンス数の推移

作成でつまずいたポイント

シナリオを書く過程でつまずきがあったので、これから取り組む方向けに共有します。

Options内の値はリテラル必須

Grafana Cloud k6のプレビュー画面では、Optionsブロックの値を静的解析してVUh(仮想ユーザー時間)を試算します。このとき、Math.round()のような関数呼び出しや、外部の定数を参照する書き方は評価できず、VUhが正しく計算されません

// NG(VUh が計算されない)
const MaxTarget = 200;
export const options = {
  scenarios: {
    spike: {
      stages: [
        { duration: '50s', target: MaxTarget }, // 変数参照は評価されない
      ],
    },
  },
};

// OK(リテラル数値で書く)
export const options = {
  scenarios: {
    spike: {
      stages: [
        { duration: '50s', target: 200 },
      ],
    },
  },
};

DRYに書きたくなる気持ちはありますが、Optionsブロックに限ってはリテラルで書くことを徹底すると安全です。

認証付きエンドポイントの扱い

ステージング環境を本番相当にスケールアップして試験する場合、認証が挟まる導線ではCloudFront → Cognito → アプリケーションのようにリダイレクトが連鎖します。最初から本番想定のシナリオを流すと、認証で弾かれてうまく負荷がかからないことがあります。

そこで、まずは/healthのような疎通確認用エンドポイントにredirects: 0を付けてリクエストを送り、想定どおりに200や302が返ってくるかを確認してからシナリオを組み立てるのがおすすめです。

負荷試験の実施と結果

ここまでで作ったシナリオを使い、ステージング環境を本番相当にスケールアップしたうえで負荷試験を実施しました。ここでは、ファインディがどのように「想定スパイクごとに必要なコンテナ数」を割り出したのかを紹介します。

次の図は、Grafana Cloud k6で負荷試験を実行したときの結果サマリです。設計したとおりに負荷がかかってピークに達し、その間エラーを出さずに処理できていることが確認できます。

Grafana Cloud k6の負荷試験結果サマリ

※画像はイメージです。

負荷規模と必要コンテナ数

過去のカンファレンスでの参加者数と実測リクエスト数から、想定する負荷規模(1分あたりのリクエスト数)を複数段階用意し、負荷規模ごとに試験しました。

コンテナ数を判定する指標は、次のように定めました。

項目 内容
判定指標 ecs.fargate.cpu.percent(Datadog)
観測対象 Findy Conferenceのなかで最もリクエストが集中するバックエンドサービス
閾値 50%

ボトルネックになりやすいバックエンドサービスのCPU使用率を直接観測することで、コンテナ数の過不足を判断します。

閾値を50%にした背景

オートスケールはより低いCPU使用率で発動するように運用していますが、スパイク時は追いつく前にアクセスが集中してしまいます。

そのため、発動を待つよりも、CPU使用率が50%を超えた時点でコンテナを増やす判断をしたほうが、カンファレンス運営では安全だと考えました。

試験は次のサイクルで進めました。

  1. 想定する負荷規模のシナリオをGrafana Cloud k6から実行する
  2. DatadogでバックエンドサービスのCPU使用率とSLOに関わるメトリクスを観測する
  3. CPU使用率が閾値(50%)を超えた場合はコンテナ数を増やし、同じシナリオを再実行する
  4. SLOの範囲内に収まる最小のコンテナ数を、その負荷規模での必要数として記録する

この流れを負荷規模ごとに繰り返した結果、想定するカンファレンスの規模ごとに必要なコンテナ数を割り出すことができました。これにより、開催前のコンテナ調整を経験頼みではなく計測値に基づいて行えるようになっています。

Datadogで取ったメトリクス

試験中はCPU使用率のほかにも、次のメトリクスをDatadogで観測しました。

メトリクス 観測する目的
ECSのCPU・メモリ使用率 コンテナのリソースに余裕があるか(コンテナ数判定の主指標)
APMのレイテンシ(p95) 遅いリクエストがSLOの範囲に収まっているか
DBのコネクション数 データベース側がボトルネックになっていないか
エラー発生率 負荷によってエラーが増えていないか

また、試験結果の分析にはDatadogのBitsAIも活用しました。試験中のメトリクスからサマリを生成してNotionに集約し、開発チームへの共有や改善提案につなげています。

まとめ

今回は、Grafana Cloud k6とDatadogを組み合わせて、Findy Conferenceの負荷試験環境をゼロから構築した取り組みを紹介しました。コンテナ数の判断を、チームの経験値から計測値に基づくものへ変えられたことが大きな成果です。

ここで整理した手順は他のサービスにも応用できると考えており、今後はファインディで提供している各サービスでも横断的に負荷試験を実施できる体制へ広げていきたいです。

この記事が、これから負荷試験に取り組むエンジニアの方々やチームの参考になれば嬉しいです。最後までお読みいただきありがとうございました!

ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

Findy AI Meetup in Fukuoka #6 を開催しました — AIと共に変わるレビューの形

こんにちは。ファインディ株式会社でテックリードマネージャーをしている戸田です。

2026年6月17日に、Findy AI Meetup in Fukuoka #6を福岡で開催しました。当日参加くださったみなさま、ありがとうございました!

findy-inc.connpass.com

今回のテーマは「AI×レビュー AIと共に変わるレビューの形」です。

AIで開発が速くなった一方で、レビューが新たなボトルネックになりつつあります。この記事では、当日のメイン登壇「生成AI時代のレビューを再設計する」を振り返りながら、ファインディが1500以上のPRに適用してきたレビューの仕組みを紹介します。

なお今回の登壇は、先日のAI Engineering Summit Tokyo 2026での登壇内容をベースに、レビューにフォーカスして再構成したものです。

tech.findy.co.jp

Findy AI Meetup in Fukuokaについて

Findy AI Meetupは、ファインディのエンジニアが主催する技術系オフラインイベントです。

生成AIやAIエージェントの活用を通じた開発生産性の向上をテーマに、社内での実践事例の紹介やエンジニア同士の交流を目的としています。

福岡での開催は今回で6回目となりました。回を重ねるごとに参加者同士の顔なじみも増え、福岡のエンジニアコミュニティとして根付いてきたことを実感しています。

今回はメイン登壇1本とLT2枠の構成で、定員35名は満席となりました。この記事では、そのうちメイン登壇の内容を振り返ります。

生成AI時代のレビューを再設計する

メイン登壇では、ファインディ社内でレビューに何が起きていたのか、そこからどうレビューを設計し直したのかをお話ししました。

AIで速くなったはずなのに、レビューが詰まり始めた

最初に共有したのは、2025年のファインディ社内の開発データです。前年と比べると、次のような変化が起きていました。

指標 変化 補足
稼働人数 1.5倍 メンバー数は増加
1人あたりのPR作成数 横ばい 個人あたりの生産量は伸びず
レビューからApproveまでの平均時間 +20分 前年比で延びた
レビュー1件あたりの平均コメント数 +30% 指摘量が増えた

数値はいずれも2025年通年の社内データを前年と比較したものです。人数が1.5倍になればチーム全体の開発量は増えるはずなのに、1人あたりのPR作成数は横ばいで、レビューにかかる時間とコメント量はむしろ増えていました。

AIで開発を速くしたはずが、品質の確認とレビューが新しいボトルネックになる。この逆転現象が、社内のあちこちで起き始めていたのです。

何が起きていたか — 4つの連鎖

詰まりの正体を分解すると、次の4段階の連鎖になっていました。

  1. AIでコード生成が高速化し、PRの提出ペースが上がる
  2. 書いたコードを十分に理解しないままPRを提出する
  3. 「本来は作成者が理解しておくべき部分」までレビュアーが肩代わりする
  4. レビュアーに負荷が集中し、人間レビューが詰まりの主因になる

つまり、AIで速くなった工数がそのままレビュアー側に流れ込んでいた、ということです。上流の確認が追いつかないまま、その負担を下流のレビューが引き受ける構造になっていました。

AIが出力したコードの責任は「人間」にある

ここで整理したのが、工程ごとの担当と責任の所在です。コードを書くのもPRを作るのもAIに任せられますが、品質と最終判断の責任は人間が引き受けます。

工程 担当 責任
コード変更
Pull request作成
セルフレビュー
AI コード変更に対する責任
レビュー 人間 作っているものに対する責任

ポイントは、セルフレビューまでを「コード生成」の工程に含めてしまうことです。「AIに書かせて終わり」ではなく「AIに書かせ、AIにセルフレビューさせるところまでが生成」と捉え直すと、人間のレビュアーは初めて見るコードの粗探しから解放され、責任の境界がはっきりします。

コードレビューで「AIに任せる範囲」と「人間が見る範囲」を分ける

責任の境界が決まると、レビューで見るべき観点も自然と整理できます。登壇では、レビューを次のように2つの領域へ切り分けました。

AIに任せる
(セルフレビュー)
人間が確認する
(レビュー)
コード規約・命名 ビジネスロジックが要件をクリアするか
型定義 アーキテクチャ・設計の妥当性
テストコード・テストケース 明確なセキュリティリスク

AIに機械的なチェックを任せることで、人間の視点はコードそのものから一段引き上がり、より抽象的な観点に集中できます。レビューが「規約に沿っているか」の確認作業から、「この設計でいいのか」を考える時間へと変わっていく感覚です。

実際にファインディで運用しているレビュー用のSkillなどの解説は、登壇資料や以前の記事でも紹介していますので、是非ご覧になってください。

tech.findy.co.jp

tech.findy.co.jp

まとめ

生成AIの登場で、レビューには変わったことと変わらないことの両方があります。

変わったのは、AIに任せられる範囲が広がったことです。規約・命名・型・テストといった機械的なチェックはAIに委ね、人間のレビューはビジネスロジックや設計、要件への適合といった、より抽象的な観点へと移りました。

変わらないのは、最終的な品質と判断の責任を人間が引き受けるという点です。AIがどれだけコードを書いても、それを世に出す責任は人間に残ります。

AIに任せる範囲 人間が確認する範囲
コード規約・命名 ビジネスロジック
型定義 アーキテクチャ・設計
テストコード・テストケース セキュリティリスク
機械的なチェック全般 要件への適合

この境界を成り行きに任せず、意図的に設計すること。それが、生成AI時代のレビューだと考えています。

そして早くも次回開催が決定しました!2026年8月5日(水)に開催予定です。

皆さんの熱いご支援のお陰で、Findy AI Meetup in Fukuokaは次回で1周年となっております。そこで次回は「AI × これまでと、これから AI開発の"今まで"と"これから"を語り尽くそう」と題しまして、AIに関して熱く語る会にしたいと思っています。ぜひご参加ください。

findy-inc.connpass.com

ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

【エンジニアの日常】これが私のご褒美ランチ!〜日々の開発を支えるおすすめの店〜 Part2

こんにちは、Findy AI+のエンジニアをしている山岸です。

エンジニアの毎日は、頭をフル回転させることの連続です。

難しい課題に向き合い、コードと格闘した日ほど、ランチの一皿がじんわり沁みる――。

午後のスイッチを入れ直してくれるそんな一食は、エンジニアにとってちょっとした活力源だと思っています。

前回好評だった天ぷら特集のPart1に続き、今回はPart2を執筆しました。

焼肉と唐揚げ、それぞれの「これが私のご褒美ランチ!」をお届けします!

■ dan / AI+開発 ■

普段はFindy AI+でバックエンド・フロントエンドの開発をしているdanです。

大阪・梅田:精肉卸問屋直営焼肉店 牛次郎

お店の基本情報

  • 場所: 大阪府大阪市北区曽根崎2-5-22 日宝パティオ曽根崎 1F
  • 営業時間: 月〜金 15:30〜27:00 / 土日祝 12:00〜27:00
  • 価格帯: ¥1,000〜
  • 雰囲気: 客層は幅広く、ひとりでふらっと入っても、仲間とワイワイ行っても馴染める気取らないお店

梅田ダンジョンに飲み込まれたくない方は、BIGMAN → HEP FIVE → お初天神のルートがおすすめです。

お初天神通りの入り口

梅田周辺は飲食店が多すぎて、逆にどこに行くか迷いがち。牛次郎はお初天神の通りをまっすぐ進んでいくとたどり着けるので、梅田に慣れていない人でも比較的行きやすい場所にあります。

おすすめはハラミとユッケ。

ランチ写真

若干厚切りのハラミ

ハラミは焼いて食べてみると、脂っこすぎず、でもしっかり肉の旨みがあります。タレでごまかす感じではなく、肉だけでちゃんと美味しい。これ食べたらもう〆に行っていいレベルです。

ユッケ

ユッケは、焼肉屋でよくある細長くカットされたスタイルではなく、薄切りのお肉をお皿に広げ、その上に卵黄と薬味がのった独特のスタイル。薬味は少しスパイシーで、卵黄のまろやかさと合わさってパンチのある味わいです。

食べる前後の楽しみもあります。ミナミの雰囲気はちょっと怖いけど大阪らしさを感じたい、グラングリーン大阪でチルしたいなど、牛次郎の周辺には寄り道したくなるスポットが揃っています。美味しい焼肉と街歩きをセットで楽しめるのも、梅田ならではの魅力です。

■ 山岸真悠子 / CTO室 AI推進 / AI+開発担当 ■

こんにちは、CTO室のAI推進でFindy AI+の開発を担当している山岸です。 ファインディのオフィスは大崎にあるので、ランチには隣の五反田までよく足を運んでいます。

今回ご紹介するのは、五反田駅前にある創作和食のお店、話食よしです。ランチの唐揚げ定食がとにかく美味しいので、ぜひ知っていただきたく筆を執りました!

東京・五反田:話食よし

お店の基本情報

  • 場所: 東京都品川区西五反田1-7-1 リビオ五反田プラグマGタワー2F(五反田駅から徒歩1分、大崎広小路駅から徒歩6分)
  • 営業時間: ランチ 平日11:30〜14:00 / ディナー 17:00〜23:30(不定休)
  • 価格帯: ランチ ¥1,000〜¥1,999 / ディナー ¥5,000〜¥7,999
  • 雰囲気: 五反田駅前なのに落ち着いていて、ひとりでもカジュアルに入れる和食店。半個室があるので会食にも使えます

五反田の駅前というと賑やかなイメージがあるかもしれませんが、ここは一歩入ると驚くほど落ち着いた雰囲気です。 気取らずにふらっと入れるのに、ちゃんと美味しい。そんなちょうどいいお店です。

店前

ランチメニュー

ランチ写真

おすすめは唐揚げ定食。 まずは全景から。メインの唐揚げに、ごはんと味噌汁、それに卵焼きやひじき、千切りキャベツ、漬物の小鉢がついた王道の定食スタイルです。

彩りも良く、運ばれてきた瞬間にテンションが上がります。

唐揚げ定食の全景

そして主役の唐揚げ。衣がカリッ、サクッとしていて、おそらく片栗粉で揚げているタイプです。

中はジューシーで、ポン酢と大根おろしでさっぱり、マヨネーズでこってり、と一皿で二度おいしいを楽しめます。

輝く唐揚げの衣

ごはんはツヤツヤ。一粒一粒がしっかり立っていて、これだけでも箸が止まりません!

さらに嬉しいことに、味噌汁はおかわり自由です。

ツヤツヤの白米

ちなみに夜に行っても外れなし。鮮魚を使った創作和食が美味しく、半個室があるので少人数の会食やチームの集まりにもぴったりです。

最近五反田ではお気に入りのお店の閉店が続いていて、「もっと通っておけばよかった」と思うことが何度かありました。

美味しいお店との出会いも、結局は通い続けてこそだよなあと。推しのお店には、無理のないペースで足を運んでいきたいですね。

五反田でランチに迷ったら、ぜひ話食よしへ。みなさんもぜひご賞味あれ〜

おわりに

今回は焼肉と唐揚げのお店をご紹介しました。どちらもエンジニアの日々を支えてくれる、元気の源になるランチです。

ファインディには「社内コミュニケーション補助」という制度があり、組織や拠点を超えた交流が促進されています。おすすめのランチを共有し合えるのも、このシリーズの楽しみのひとつです。

また、今回ご紹介したように、ファインディでは全国各地からさまざまなエンジニアが活躍しています。技術にこだわり、より良いものを追求する仲間とともに働いてみませんか? 現在、新しいメンバーを募集しています。興味のある方は、ぜひこちらからチェックしてみてください!

DevinからClaude Code Actionsへ ── アカウント管理の自動化基盤を移行した判断とアーキテクチャ

こんにちは。ファインディのPlatform開発チーム(以降、SREチーム)でSREを担当している原(こうじゅん)です。

SREチームでは、AWSのユーザーとGitHubのアカウントの管理をTerraformで運用しています。Slackから申請が来たらTerraformのコードを書き換えてPRを出す、という作業を以前はDevinで自動化していました。

しかしDevinのアカウント利用形態が変わったことをきっかけに、この自動化基盤をClaude Code Actionsへ移行しました。同じようにAIツールでインフラ運用を自動化している方や、Claude Code Actionsの実践的な使い方を探している方に向けて、移行に至った判断理由と、移行後のアーキテクチャについて紹介します。

背景:Devinでアカウント管理を自動化していた

ファインディでは、GitHub Teamへのメンバー追加やチームの作成・廃止といったアカウント管理を、SREチームがTerraformで運用しています。

エンジニア・ビジネスサイド問わず、メンバーがSlackから申請を出すと、SREがTerraformのコードを手動で書き換えてPRを出し、レビュー・マージ後にapplyされる、という流れでした。毎回決まったパターンのYAML編集とPR作成を手作業でやるのは、典型的なトイルです。

このトイルを減らすために、Devinを使って自動化していました。Slackの申請内容をDevinに渡すと、Terraformリポジトリのコードを編集してPRを作ってくれる仕組みです。この取り組みの詳細は次の記事で紹介しています。

tech.findy.co.jp

移行を決めた理由

きっかけは、2026年4月1日のDevinのアップデートです。このアップデートでDevinアカウントを持たないユーザーはSlackのDevinスレッドに参加できなくなりました。

アカウントを持たない申請者が@Devinに返信してもDevinが反応せず、「動かない」というSREへの問い合わせが増えました。コンプライアンス上、全メンバーへのアカウント一律払い出しも現実的ではありません。そのため、Devinからの移行に踏み切りました。

移行先の選定にあたっては、次の3つを重視しました。

  • 全メンバーが個人アカウントなしで使えること — Anthropic公式の@Claude(Claude in Slack)も検討しましたが、各ユーザーにClaude Codeのアカウントが必要で同じ問題を抱えて断念しました
  • 申請者の体験を変えないこと — Slack Workflowのフォームはそのまま、バックエンドだけを差し替えます
  • チームのツール統一 — 開発チーム全体がすでにClaude中心の開発体制になっており、自動化基盤もそれに合わせました

この選定理由より、今回のアーキテクチャとなりました。

移行後のアーキテクチャ

移行後の全体構成は次のとおりです。

flowchart TD
    subgraph Slack["Slack (申請者の体験)"]
        F1["メンバー申請"]
        F2["チーム申請"]
        Thread[("申請者スレッド")]
    end

    subgraph GHA["GitHub Actions"]
        Triage["triage\n(Claude Code Action)\n整合性チェック・名前解決"]
        RoutinePR["routine-pr\n(Claude Code Action)\nファイル編集・PR作成"]
        Escalate["escalate\n自動処理不可を通知"]
    end

    SRE["SRE承認・マージ"]
    Apply["terraform apply"]

    F1 -->|workflow_dispatch| Triage
    F2 -->|workflow_dispatch| Triage

    Triage -->|routine| RoutinePR
    Triage -->|escalate| Escalate

    RoutinePR -->|PR作成| SRE
    RoutinePR -->|受付通知| Thread
    Escalate -->|エスカレ通知| Thread

    SRE -->|マージ| Apply
    Apply -->|完了通知| Thread

    classDef unchanged fill:#e6f4ea,stroke:#34a853,stroke-width:2px,color:#1b5e20;
    class F1,F2,Thread unchanged
    style Slack fill:#f1f8f4,stroke:#34a853,stroke-width:2px;

Slack Workflowからの申請がGitHub Actionsのworkflow_dispatchをトリガーし、正常系ではtriageジョブとroutine-prジョブの2つのClaude Code Actionが順に処理する構成です。

ただし、Slack Workflow Builderから直接workflow_dispatchは呼べません。間にSlack Custom Function(Deno Slack SDK)を挟み、GitHub APIへの認証付き呼び出しを行っています。

api.slack.com

sequenceDiagram
    participant WF as Slack Workflow Builder
    participant CF as Custom Function<br/>(Deno Slack SDK)
    participant GH as GitHub API

    WF->>CF: フォーム入力値を渡す
    CF->>CF: GitHub App秘密鍵でJWT署名
    CF->>GH: JWT → Installation Access Token取得
    GH-->>CF: Token返却
    CF->>GH: workflow_dispatch(Token認証)
    GH-->>CF: 202 Accepted

申請者から見ればSlackのフォームに入力するという体験は変わっていませんが、裏側ではCustom FunctionがGitHub Appの認証(JWT署名 → Installation Access Token取得)を行い、workflow_dispatchを呼び出しています。

triageジョブ:申請の振り分け

最初のClaude Code Actionは、申請内容の整合性チェックと振り分けを担当します。

チーム名の名前解決(申請者が入力した表記ブレを、リポジトリ上の実際のディレクトリ名に解決する)、申請内容のバリデーション、そしてroutine(自動処理可能)かescalate(自動処理不可)かの判定を行います。

triageとPR作成を1つのジョブにまとめることもできますが、責務と権限を分離するためにあえて分けました。triageジョブは--max-turns 5と少ないターン数に制限し、使えるツールもReadBash(ls:*)だけに絞っています。判定がおかしければPR作成に進まない安全弁にもなります。

判定結果はJSON Schemaで構造化出力させるので、後続のジョブが確実にパースできます。

claude_args: |
  --max-turns 5
  --allowedTools Read,Bash(ls:*)
  --json-schema '{"type":"object","properties":{"verdict":{"type":"string","enum":["routine","escalate"]},...}}'

routine-prジョブ:ファイル編集とPR作成

triageでroutineと判定された申請は、2つ目のClaude Code Actionが処理します。TerraformリポジトリのYAMLファイルを編集し、ブランチを切ってPRを作成するジョブです。

--max-turns 20とターン数を多めに確保し、ファイル編集やgit操作、PR作成に必要なツールを許可しています。

claude_args: |
  --max-turns 20
  --allowedTools 'Read,Edit,Write,Bash(git checkout:*),Bash(git add:*),...,Bash(gh pr create:*)'

PR作成後はSlackの申請スレッドに受付通知を送り、SREがレビュー・マージすればterraform applyが走って完了通知が届きます。

escalateジョブ:自動処理できない申請の通知

triageが自動処理できないと判断した申請は、Slackの申請スレッドにその旨が通知されます。Claude Code Actionは使わず、シェルスクリプトで通知を送るだけのシンプルなジョブです。

まとめ

Devinのアカウント利用形態変更をきっかけに、アカウント管理の自動化基盤をClaude Code Actionsへ移行しました。Claude Code Actionをtriage(振り分け)とroutine-pr(PR作成)の2段階に分け、それぞれの責務と権限を絞った設計にしています。

現在、GitHubアカウント管理の移行は完了し、運用を開始しています。今後はAWSのユーザー管理なども移行を進めていきます。


ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers