distributed-logging について
複数プロセスのログを扱うシステム設計者向けに、分散ログパイプラインの設計を支援するスキルです。収集、バッファリング、インデックス、保持、障害時の動作を検討できます。
主な機能
- 01ログ収集と転送方式の選択
- 02インデックスとストレージの比較
- 03保持期間と階層化の指針
- 04バックプレッシャーと過負荷への対応
- 05構造化イベントとトレースの関連付け
活用シーン
- 01複数サービスのログ集約
- 02サービス間のリクエスト追跡
- 03コンプライアンス向け保持期間の設計
- 04アプリをブロックせずログ急増に対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill distributed-loggingインストール後、Claude Code で/distributed-loggingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.md詳細
分散ロギング
数千のプロセスからログをひとつの検索可能な場所に移し、進行中のインシデントをデバッグできるほど高速に、数か月間保存できるほど低コストで扱います。設計を誤ると、よくある「障害の無視」につながります。ロギングパイプライン自体が分散システムであり、最も必要とするトラフィック急増時に耐えきれなくなるためです。安易な設計では、証拠を失うか、計測対象のアプリを停止させてしまいます。
これを選ぶ場面
複数のプロセスがログを出力し、それらをまとめて検索する必要がある場合、インシデント時にサービスをまたいでリクエストを相関させる必要がある場合、ログ量が1台のマシンでのgrepでは処理しきれなくなった場合、またはコンプライアンス上、ログの保持が求められる場合です。このパイプラインにより、集中検索、サービス間の相関付け、特定のホストに依存しない永続的な記録が可能になります。
選ばない場面
journaldとログローテーションで十分な、1台のホスト上の単一サービスの場合、完全なパイプラインは運用上のオーバーヘッドにすぎません(YAGNI)。「p99レイテンシはどの程度か」「エラー率は上昇しているか」といった数値の時系列に関する質問は、ログの検索ではなくメトリクスの役割です。これはobservabilityの仕事です。ログは「このリクエストに何が起きたのか」を明らかにします。ログ量に見合うコストだと数値で示される前に、すべてのデバッグ行をフルボリュームで送らないでください。まずサンプリングしましょう。
まず確認すること
- ボリュームとピーク — 1秒あたりの行数とバイト数について、平均値とピーク値を確認します(→
back-of-the-envelope)。これによって各段階の規模が決まります。 - 構造化ログかフリーテキストか — プロデューサーは今すぐJSONを出力できますか。それとも解析が必要なレガシーテキストがありますか。
- クエリのレイテンシ要件 — 数秒でのインタラクティブ検索(ホットインデックス)と、時折必要なフォレンジック/監査目的の読み取り(コールドアーカイブ)のどちらですか。
- 保持期間とコンプライアンス — ホットデータとコールドデータをそれぞれどのくらい保持しますか。法的保留やPIIの墨消し要件はありますか。
- 損失許容度 — 過負荷時にログを破棄/サンプリングしてもよいですか。それとも、すべての行が証拠となりますか(監査/金融)。
選択肢
収集(ホスト上のエージェント)
- サイドカー/ノードエージェント(Fluentd、Fluent Bit、Vector、Filebeat): ファイルを監視するか、標準出力を読み取り、メタデータを追加して送信します。アプリがファイルまたは標準出力にログを出力しており、アプリコードに手を加えたくない場合に使います。標準的な選択肢です。
- バスへ直接送信するSDK: アプリが構造化イベントをトランスポートに直接書き込みます。コードを管理でき、より密結合になることを許容して、厳密な構造を求める場合に使います。
トランスポート/バッファー(衝撃吸収材)
- エージェント側のバッファー+インデクサーへの直接送信: 最もシンプルな方法です。エージェントがディスクにバッファーして再試行します。ボリュームが低〜中程度で、コンシューマーが1つの場合に使います。
- 耐久性のあるログバス(
messaging-streaming、例:Kafka): プロデューサーがパーティション分割されたバスに書き込み、インデクサーはそれぞれのペースで読み取ります。ボリュームが大きい場合や、複数の出力先(検索、アーカイブ、分析)が同じストリームを読み取る場合に使います。
インデックス/ストア(検索バックエンド)
- 全文インデックス(Elasticsearch/OpenSearch — ELK/EFKの「E」): 豊富なクエリ機能を備えますが、RAMとディスクを多く消費します。フィールドを使ったインタラクティブ検索が重要な場合に使います。
- ラベルインデックス型ストア(Loki): インデックス化するのはラベルのみで、ログ本文は圧縮して保存します。大幅に低コストで、grepスタイルのクエリが使えます。ボリュームが大きく、主にサービス/ラベルで絞り込んでから検索する場合に使います。
保持/階層化
- ホットインデックス → コールドオブジェクトストア(
blob-store): 検索可能なデータを数日分ホットに保持し、それより古いデータは圧縮オブジェクトとして移します。保持期間がホットウィンドウを超える場合は、費用対効果の観点からほぼ常に使います。
トレードオフ
| 選択肢 | 解決すること | 悪化すること | 切り替えるタイミング |
|---|---|---|---|
| Node エージェント(Fluent Bit/Vector) | アプリの変更が不要。メタデータの一元管理とルーティング | ホストごとにデーモンがもう1つ増える。解析にCPUを消費する。エージェントが遅延することがある | 正確な構造が必要な場合 → アプリから構造化イベントを出力する |
| バスへ直接送信する SDK | クリーンな構造化イベント。ファイル解析が不要 | アプリがトランスポートに依存する。バスが停止するとアプリがブロックされるか、ログが失われる | 依存や可用性が問題になる場合 → エージェントとローカルバッファに戻す |
| エージェントのバッファ → インデクサーへ直接送信 | 構成要素が最も少ない | インデクサーのバックプレッシャーがプロデューサーに影響する。再生できない。シンクが1つ | ボリュームの急増がある場合、またはシンクが複数必要な場合 → ログバスを追加する |
| 永続ログバス(Kafka) | 急増を吸収し、依存を切り離し、再生とファンアウトを可能にする | 運用するシステムが増える。順序はパーティション単位のみ。コストがかかる | ボリュームが少なく、シンクが1つの場合 → バスをなくす |
| 全文インデックス(ES/OpenSearch) | 高速で豊富なフィールド検索 | RAM とディスクを大量に消費する。マッピングが爆発的に増える。規模が大きくなると高コスト | コストが支配的で、クエリがラベルで絞り込まれる場合 → Loki |
| ラベルインデックス(Loki) | 大量データでもストレージが安価 | 全文検索が弱い。カーディナリティの高いスキャンが遅い | 任意のフィールド検索が本当に必要な場合 → 全文インデックス |
| ホットインデックス + コールドアーカイブ | 長期保持のコストを抑える | コールドデータの読み取りは遅く、手動での再取り込みが必要 | 古いデータのフォレンジック調査を高速に行う必要がある場合 → ホット期間を広げる |
高負荷時の動作
パイプラインの障害モードは、インシデントによってログの急増が引き起こされることです。停止時には、パイプラインが最も忙しいタイミングでエラーやスタックトレースが大量に出力されるため、停止を増幅させずに性能を低下させる必要があります。
- プロデューサーのバックプレッシャー: インデクサーが遅くなり、エージェントが同期的にログを送信すると、ログ呼び出しによってアプリがブロックされることがあります。対策: 上限付きのローカルバッファを使用し、あふれた場合は新しいログを破棄する/サンプリングすることで、リクエスト処理を決してブロックしません。永続バスを使うと、ホストからバックログを切り離せます(バックプレッシャーと DLQ のセマンティクスは
messaging-streamingが担当します)。 - ボリュームの増幅: デバッグレベルでログを出力する不適切なデプロイによってボリュームが100倍になり、インデックスがあふれることがあります。対策: エージェントでサービスごとにレート上限を設定し(レート制限は
resilience-failureが担当します)、動的サンプリングを行い、取り込みバイト数/秒に関するアラートを設定します。 - インデックスのホットシャード/マッピングの爆発的増加: カーディナリティの高いフィールド(インデックスフィールドとしての生のユーザー ID)や、極端に大きいシャード1つによって、クラスターが損なわれます。対策: 時間ベースのインデックス、上限を設けたフィールドマッピング、ラベルの適切な管理を行います。
- 順序: バスが順序を保証するのはパーティション内のみです。複数ホストのストリームをマージすると、順序が入り交じります。対策: クエリ時にイベントのタイムスタンプでソートし、単調増加シーケンスまたはトレーススパンの順序を保持します。到着順を信用してはいけません。
- 証拠の喪失: ログを黙って破棄すると、調査中の障害そのものが隠れてしまいます。対策: 破棄率とサンプリング率を計測してアラートを設定し、ログの喪失を可視化します。
監視項目: 取り込みバイト数/秒と行数/秒、エンドツーエンドの送信遅延、バスのコンシューマー遅延、エージェントのバッファ使用率と破棄率、インデックスキュー/拒否率、クエリレイテンシ。
適用方法
- 入力条件を明確にします — ボリューム(平均およびピークの bytes/sec)、構造化データかテキストか、クエリのレイテンシ要件、保持期間、損失許容度を確認します(まず確認することを参照)。ボリュームがごく小さく単一ホストであれば、そこで終了します。ホストローカルのログで十分です(YAGNI)。
- トレードオフ表からステージを選びます — コレクターを選び、永続バスが必要か判断し(ボリューム/スパイク/複数の出力先を基準にします)、クエリ要件に合わせてインデックスバックエンドを選び、ホットウィンドウとコールド階層を決めます。
- 主要な設定値を決めます — すべての行に 相関/トレース ID を付与する構造化スキーマを必須とし、エージェントのバッファーサイズとオーバーフローポリシー、サンプリング率、インデックスのロールオーバー(時間ベース)とシャードサイズ、保持/階層化ポリシーを設定します。
- 選択をストレステストします — ストレス時の挙動の各項目(プロデューサーのバックプレッシャー、デバッグログの大量出力を伴うデプロイ、ホットシャード、順序、気付かれないドロップ)を確認し、アプリをブロックすることなく、サンプリング/バッファリングによってパイプラインの性能が劣化することを確認します。
- 数値でサイズを見積もります — 行数/秒 × バイト数/行 × ピーク係数 × 保持期間を使って、ホットインデックスの GB とコールドアーカイブの GB/月を算出します。バスのパーティション数とインデクサーの台数がピーク時の取り込み量に対応できることを確認します(→ 重要な数値)。
- プロバイダーを選びます — 既定では汎用レシピを使います。ユーザーがクラウドを指定した場合に限り、プロバイダーファイルを開きます(プロバイダーの選択を参照)。
- 回答でトレードオフを示します — ここで行った各選択につき 1 行を設けた
Decision | Solves | Worsens | Change it when…テーブルを提示します(例:選択肢、そのストア、障害時のポリシー)。最後の列には具体的な切り替え条件(数値または観測可能なシグナル)を記載します。そのうえで、最初に壊れるものを示します。
やるべきこと・避けるべきこと
やるべきこと
- エッジで相関/トレース ID を付与して伝播させ、1 つのクエリでリクエストを追跡できるよう、構造化ログ(JSON)を出力します。
- プロデューサーとインデクサーの間に上限付きバッファーを置き、オーバーフロー時はアプリをブロックせず、ドロップまたはサンプリングします。
- ボリュームが急増する場合、または複数の出力先がストリームを読み取る場合は、永続ログバスを使います。
- 積極的に階層化します。検索用のホットウィンドウは短くし、保持用には圧縮したコールドアーカイブを使います。
- 取り込みレート、配送遅延、ドロップ率を計測してアラートを設定します。損失に気付けないことが落とし穴です。
避けるべきこと
- ログ出力の呼び出しでリクエストパスをブロックしたり、クラッシュさせたりしないでください。既定では、ログ出力はベストエフォートです。
- カーディナリティの高い自由形式のフィールドをインデックス化しないでください。クラスターを圧迫します。ラベルの種類は上限を設けてください。
- 因果関係の把握に到着順を当てにしないでください。イベント時刻/シーケンスでソートします(→
sequencer)。 - ここでメトリクス/アラートを再説明したり、ログに SLO を設定したりしないでください。それは
observabilityの役割です。 - コールドデータの読み取りを避けるために、すべてをいつまでもホット状態で保持しないでください。ホットウィンドウはデバッグ期間に合わせて設定します。
重要な数値
ボリュームを基にパイプラインのサイズを見積もります。行数/秒 × バイト数/行で取り込み bytes/sec が求まります。1 行あたり数 KB で毎秒数万行を処理すると、すでに数百 MB/s、数 TB/日になります。ホットインデックスのストレージ ≈ 1 日あたりのバイト数 × ホット日数 ×(1 + レプリカ + オーバーヘッド)。コールドアーカイブ ≈ 1 日あたりのバイト数 × 保持日数で、圧縮後は約 5~10 分の 1 です。インシデント時の大量出力に備え、ピーク係数を適用します。ここでレイテンシ/QPS/ストレージの経験則を繰り返さず、back-of-the-envelope を参照してください。
インターフェイスの概要
ログ行は文字列ではなく、構造化イベントとしての契約です。最小限のフィールドは次のとおりです。
{ "ts": "2026-05-29T12:00:00.123Z", "level": "ERROR", "service": "checkout",
"host": "pod-7", "trace_id": "abc123", "span_id": "f9", "msg": "charge failed",
"user_id": "u42", "err": "timeout", "latency_ms": 812 }
ts はイベント時刻(クエリ時のソートキー)です。trace_id/span_id はサービス間の相関付けに使います。level と service は種類を限定した インデックスラベルです。カーディナリティの高い値(user_id)は、インデックスキーではなく検索可能な本文フィールドに保持します。バスのパーティションキーには通常 service または trace_id を使い、リクエストのログ行がまとめて順序付けられるようにします。
プロバイダーの選択
既定では上記の汎用レシピを使います。ユーザーがクラウドを指定した場合は、マネージドサービスへの対応付け、クォータ/制限、プロバイダー固有のトレードオフについて、references/providers/<provider>.md を読みます。そのプロバイダーのファイルが存在しない場合は、汎用レシピを回答とします。
図
パイプライン(プロデューサー → エージェント → バス → インデクサー/検索 + アーカイブシンク)
またはオーバーフロー/縮退パスを視覚化するには、プラグイン内の architecture-diagram スキルを使います —
耐久性のあるバスをプロデューサーとシンクの間のバッファーとして描き、コールドアーカイブシンクには
blob-store の色を使い、オーバーフロー時に破棄するパスは破線の矢印で示します。
関連する構成要素
observability— 所有する概念は 3 本柱のビューにあります。メトリクス、トレース、アラート、SLO/SLI はそこで説明します。ログも柱の 1 つですが、何にアラートを設定するかはこのスキルではなく、そちらの役割です。messaging-streaming— 耐久性のあるログバスについては、これに依存します。配信保証、順序、バックプレッシャー、DLQ のセマンティクスはそちらが担当し、このスキルはバスをトランスポートとして使うだけです。blob-store— コールドログのアーカイブについては、これにつなぎます。圧縮されたコールドオブジェクトの耐久性、階層化、ライフサイクルはそちらが担当します。sequencer— ホスト間の順序が重要な場合は、これと組み合わせます。単調増加 ID とクロックスキューへの対処はそちらが担当します。system-design— 所有する概念は オーケストレーターにあります。推論ループ、トレードオフの手法、10 個の障害モードを扱います。より広範な設計につなぎます。
参考資料
references/deep-dive.md— バッファリングとバックプレッシャーの仕組み、サンプリング戦略、構造化ロギングと相関 ID の伝播、インデックスのライフサイクル/ロールオーバー、順序、コールド階層化について説明します。パイプラインを詳細に設計するときに読んでください。references/providers/{generic,aws,azure,gcp}.md— 環境ごとのサービス対応表、制限、注意点。