0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

外部 API 障害でも止まらない fail-safe 設計 — 重み再配分で「縮退モード」を実装する

0
Last updated at Posted at 2026-06-06

この記事は約 6 分で読めます。

筆者プロフィール: ソフトウェアエンジニア。「知った気にならない。いつまでも学び続ける」を信条に、業務と個人開発の両輪で技術を磨いています。AI 駆動開発で複数の個人開発アプリを構築・運用中。
👉 ポートフォリオ: 筆者ホームページ

外部 API (Voyage / Anthropic 等) を提案エンジンに使うと、「外部 API が落ちたらユーザに何を見せるか」 が必ず問題になります。本記事では、運用中の SaaS 「たすきば Knowledge Relay」 で採用した 重み再配分縮退モード の設計判断を整理します。

サービスの機能紹介・画面イメージ・コンセプトは公式プロダクトページをご覧ください。
👉 たすきば Knowledge Relay — 公式プロダクトページ

なぜハードカット (429) を採用しないか

業務 SaaS で API 利用上限を超えたとき、よくある対応は HTTP 429 Too Many Requests を返してユーザを止める こと。

—— たすきばは、この方式を 採用しません

理由 内容
ユーザの作業が中断される 「リスクを起票したい」のに API 制限で止まると、ユーザは何もできない
本体データの保存まで止まる 提案に使う付随処理 (embedding 生成) のために、メインデータの保存まで止めるのは過剰
コスト超過は予測不能 ユーザはどこまで使ったら制限に当たるか事前には分からない

代わりに、Graceful Degradation Mode (ADR-0008) を採用しました。

「本体データは正常保存。提案精度だけが下がる」

という縮退設計です。


1. 縮退時の挙動

操作 平常時 縮退時
新規ナレッジ作成 embedding 生成 + 保存 embedding は NULL のまま保存
新規リスク起票 embedding 生成 + 保存 embedding は NULL のまま保存
提案画面表示 (既存) 3 軸合算スコアでランキング 3 軸合算スコア (NULL は意味類似度 0 として扱う)
提案画面表示 (縮退中の新規) 3 軸合算スコア 重み再配分縮退モード に自動遷移

重要: 本体データの保存は常に成功します。ユーザの作業フローは止まりません。


2. 重み再配分の仕組み

平常時の重み:

タグ類似度 × 0.3 + 文字列類似度 × 0.2 + 意味類似度 × 0.5 = 最終スコア

縮退時 (embedding が NULL):

タグ類似度 × 0.6 + 文字列類似度 × 0.4 + 意味類似度 × 0.0 = 最終スコア
                                       ↑
                                   ここを 0 にし、
                                   その分を残り 2 軸に再配分

具体的には:

  • 意味類似度の重み 0.5 を、残り 2 軸の比率で按分
  • タグ : 文字列 = 0.3 : 0.2 = 3 : 2
  • 0.5 を 3 : 2 で按分 → +0.3 と +0.2
  • 新しい重み: タグ 0.3 + 0.3 = 0.6、文字列 0.2 + 0.2 = 0.4

合計の重みは常に 1.0 を維持します。


3. なぜ「NULL を除外」ではなく「重み再配分」か

「embedding NULL の候補をフィルタアウトする」設計も検討しましたが、不採用にしました。

不採用案 (NULL を除外) 問題点
- 作りたて (縮退中作成) の候補が永遠に提案に乗らない
- 縮退と平常で提案結果が極端に違う
- 月初 cron で補完しても、補完までユーザに見えない

「全候補は常に同じ土俵 (3 軸合算) で評価される」 というポリシーを維持することで、縮退・平常の切替が ユーザに見えない 設計になります。


4. 実装イメージ

// src/services/suggestion.service.ts
export async function findSuggestions(
  queryEmbedding: number[] | null,  // 縮退時は null
  queryText: string,
  queryTags: string[],
  context: { viewerTenantId: string },
) {
  const isDegraded = queryEmbedding === null;

  const weights = isDegraded
    ? { tag: 0.6, text: 0.4, embed: 0.0 }
    : { tag: 0.3, text: 0.2, embed: 0.5 };

  return prisma.$queryRaw`
    SELECT
      id,
      title,
      tag_jaccard(business_domain_tags, ${queryTags}) * ${weights.tag}
        + similarity(title || ' ' || content, ${queryText}) * ${weights.text}
        + COALESCE(1 - (content_embedding <=> ${queryEmbedding}::vector), 0) * ${weights.embed}
        AS final_score
    FROM knowledges
    WHERE tenant_id = ${context.viewerTenantId}
      AND visibility = 'public'
      AND deleted_at IS NULL
    ORDER BY final_score DESC
    LIMIT 10
  `;
}

ポイント:

  • COALESCE(..., 0) で NULL を 0 に置換
  • 重みを動的に切り替える (isDegraded フラグ)
  • SQL 自体は同じクエリ (条件分岐を SQL の外に出す)

5. ハードキャップ超過時の挙動

「ハードキャップを超えても embedding を保存しない」のは、課金透明性 のためでもあります。

状態 挙動
ハードキャップ未到達 通常の embedding 生成 + ApiCallLog 記録 + 課金
ハードキャップ超過 embedding 生成スキップ + 課金しない + 本体データは保存
翌月 cron 前月分の NULL embedding を一括補完 (補完分は 無料化)

これにより:

  • ユーザは「当月の操作で当月分が請求される」という直感的なモデルを維持
  • 月初 cron でデータが揃うので、翌月以降は通常の精度に戻る

6. fail-safe な提案として保証する 4 つのこと

ADR-0008 では、提案機能の SLA として以下を保証しています。

保証 内容
本体データの可用性 外部 API 障害時でも、ユーザのデータ保存は止まらない
提案機能の可用性 外部 API 障害時でも、提案画面はエラーを返さない
提案精度の縮退 縮退時は精度が下がるが、結果は返る
後追い補完 月初 cron で前月分の NULL を補完 (無料)

100% の精度より、100% の可用性」を選ぶ設計判断です。


7. 横展開: 他機能でも同じパターン

この Graceful Degradation パターンは、たすきばの他の機能でも使っています。

機能 縮退モード
メール送信失敗 ログだけ残し、ユーザ操作は完了させる
添付ファイルのウイルススキャン失敗 一時的に「スキャン中」状態にし、アップロードは完了
Stripe Webhook 失敗 内部キューに積み、ユーザの操作は通す

「外部システムの障害を、ユーザに見せない」 をポリシーとして全機能で実践しています。


おわりに — 外部 API の障害をユーザに見せない

外部 API 連携を設計するとき、必ず「この外部 API が落ちたら、ユーザに何が見えるか」を考えます。

最悪のシナリオが「ユーザが何もできなくなる」ならば、それは設計が間違っています。

ユーザの作業は、外部 API の可用性に依存してはいけない。

外部 API は「あれば嬉しい補助機能」と位置づけ、それなしでもサービスのコア体験 (本体データ保存・閲覧) は完結する設計が望ましいです。

たすきばは、この思想を徹底するために、ADR-0008 を最初に書きました。

本記事の縮退モード設計は、運用中の SaaS 「たすきば Knowledge Relay」 で実装しています。
👉 たすきば Knowledge Relay — 公式プロダクトページ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?