目次を表示する

共通基盤の設計軸 2026 ─ 抽象・責務・非機能要件を設計する 15 章

非機能 (4) 可観測性 ─ 自分の状態を露出する

非機能 (4) 可観測性 ─ 自分の状態を露出する

可観測性(Observability)は「システムの内部状態を、外から推論できる」性質。利用者ドメインなら自分のサービスを観測すれば良いが、共通基盤は 利用者から自分が観測できる ようにする責務を負う。

利用者は基盤の中身を知らない。だから基盤が自分の状態を分かりやすく公開する 必要がある。

Three Pillars of Observability

可観測性には伝統的に 3 本柱がある:

graph TB
  O[Observability]
  O --> M[Metrics<br/>数値の時系列]
  O --> L[Logs<br/>イベントの記録]
  O --> T[Traces<br/>リクエストの経路]

  Note1[3 本柱が相補的に作用]

  style O fill:#e1f5ff
  style M fill:#e1ffe1
  style L fill:#fff4e1
  style T fill:#ffe1e1

Metrics

集計可能な数値の時系列。

http_requests_total{method="POST", path="/v1/notify", status="200"} 12345
http_requests_total{method="POST", path="/v1/notify", status="500"} 23

# 計算
error_rate = sum(status="500") / sum(any) → 0.18%

Prometheus / Datadog / Grafana が代表的。安価で高頻度に計測できるが、個別 request の追跡には向かない。

Logs

時系列のイベント記録。

{
  "timestamp": "2026-05-09T10:00:00.123Z",
  "level": "ERROR",
  "service": "notify-service",
  "message": "Failed to send notification",
  "user_id": "u_xxx",
  "trace_id": "abc123"
}

個別の事象を追えるが、volume が大きいためコスト高。

Traces

1 リクエストが複数サービスを跨いだ時の経路。

[Frontend] ─┬─→ [API Gateway] ─→ [Auth Service] ─→ [Postgres]
            └─→ [API Gateway] ─→ [Notify Service] ─→ [SES]

Distributed tracing で各 span の duration を可視化。OpenTelemetry がデファクトスタンダード。

OpenTelemetry ─ 統一された規格

OpenTelemetry は CNCF プロジェクトで、Metrics / Logs / Traces を統一規格 で扱える。

import { trace, metrics } from '@opentelemetry/api';

// Trace
const tracer = trace.getTracer('notify-service');
const span = tracer.startSpan('send_email');
span.setAttributes({ 'email.to': maskedEmail, 'tenant.id': tenantId });
// ... 処理 ...
span.end();

// Metrics
const meter = metrics.getMeter('notify-service');
const counter = meter.createCounter('emails_sent');
counter.add(1, { tenant_id: tenantId, status: 'success' });

OpenTelemetry を使うと、ベンダーロックインがない(Datadog / NewRelic / Jaeger に同じデータを送れる)。共通基盤の標準として採用するのが現代的。

Trace Context の伝播

Distributed tracing の鍵は trace context の伝播:

# Service A → Service B のリクエスト
GET /v1/users/123
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trace_id
                                                    ^^^^^^^^^^^^^^^^ span_id

W3C Trace Context 標準で どのサービスでも trace_id を共有 できる。これにより 1 リクエストが複数サービスを跨ぐ全経路が見える。

sequenceDiagram
  participant U as User
  participant API as API Gateway
  participant Auth as Auth Service
  participant Notify as Notify Service
  participant DB as DB

  U->>API: POST /v1/notifications<br/>(no trace)
  Note over API: trace_id 生成: t-001
  API->>Auth: validate token<br/>traceparent: t-001/span-002
  Auth->>DB: SELECT user
  DB-->>Auth: result
  Auth-->>API: ok
  API->>Notify: send<br/>traceparent: t-001/span-005
  Notify-->>API: ok
  API-->>U: 200 OK

  Note over U,DB: t-001 で全 span がリンク

このフローを後で Jaeger / Tempo / Datadog で検索できる。

SLI を Metrics で実装する

第 6 章の SLI を Metrics で実装する:

// SLI: Token validation success rate
const counter = meter.createCounter('token_validations');
counter.add(1, { result: 'success' });  // or 'failure'

// SLO: success_rate ≥ 99.99%
// 計算: success / (success + failure)
// SLI: Token validation latency p99
const histogram = meter.createHistogram('token_validation_duration_ms');
histogram.record(durationMs, { tenant: tenantId });

// SLO: p99 ≤ 50ms

SLO に対応する Metrics を最初から仕込む。後付けは大変。

可観測性のコスト問題

3 本柱のコストは桁違い:

1 req あたりのコスト感月コスト
Metrics1KB(集計済み)数千 USD
Logs10KB - 1MB数千 - 数十万 USD
Traces100KB(sampling 込み)数千 - 数万 USD

全部生で取ると破産する。だから sampling が必須:

// Tail-based sampling: 失敗 / 遅い request は 100% 残し、成功は 1% に
const sampler = new TailBasedSampler({
  decisionAfter: '5s',
  rules: [
    { match: { status: '500' }, rate: 1.0 },
    { match: { duration_ms: { gt: 1000 } }, rate: 1.0 },
    { match: { default: true }, rate: 0.01 },
  ],
});

問題のある request は確実に残し、正常 request は薄く取る。

利用者に “見せる” 観測性

共通基盤の特殊な責務:利用者に自分の状態を見せる

Dashboard

[Notify Service Status]
- 直近 1h 配信成功率: 99.95%
- 直近 1h p99 latency: 87ms
- Tenant 別エラー率: ...
- 各 SLO の達成度: ...

利用者がこれを見られると、自分のサービスの問題か基盤の問題か 即判断できる。

Status Page

外部公開なら GitHub / Stripe / AWS のような status page を提供:

[Platform Status]
✅ Auth Service     Operational
🟡 Notify Service   Partial Outage  (Investigating)
✅ Storage Service  Operational

利用者の信頼を支える基本ツール。インシデント時に黙って何もしないのが最悪

Per-tenant 観測性

利用者ごとに:

// 各 tenant の使用量、エラー率、latency を tenant 自身に公開
GET /v1/tenants/{tenantId}/observability
{
  "usage_1h": 12345,
  "error_rate_1h": 0.001,
  "latency_p99_1h": 45,
  ...
}

利用者は自分の使い方の癖を観測できる。基盤側との会話の根拠になる。

ログの設計

Structured Logging

{
  "timestamp": "2026-05-09T10:00:00.123Z",
  "level": "INFO",
  "service": "notify-service",
  "trace_id": "abc123",
  "tenant_id": "t_xxx",
  "user_id": "u_yyy",
  "action": "send_email",
  "outcome": "success",
  "duration_ms": 87
}

プレーンテキストではなく構造化。検索 / 集計しやすい。

PII の取り扱い

// ❌ 個人情報を生で書く
log.info({ email: '[email protected]', name: 'Alice', ssn: '...' });

// ✅ マスク / Hash
log.info({
  email_masked: 'u***@example.com',
  user_id_hash: hashUserId(userId),
});

ログは長期保存される。個人情報がログに混ざると GDPR / CCPA 違反 になりうる。

ログ量の制御

// ❌ ループ内で大量ログ
for (const item of items) {  // 1000 items
  log.info({ item });
}

// ✅ 集計 or sampling
log.info({ items_processed: items.length, sample: items.slice(0, 5) });

ログがログサーバーを倒す のは典型的事故。sampling と集計の習慣をつける。

観測性のチェックリスト

graph TB
  Q1[Three Pillars] --> Q2[OpenTelemetry 採用]
  Q2 --> Q3[Trace context 伝播]
  Q3 --> Q4[SLI を metrics で]
  Q4 --> Q5[Sampling 戦略]
  Q5 --> Q6[利用者に見せる Dashboard]
  Q6 --> Q7[Per-tenant の観測性]
  Q7 --> Q8[Structured logging + PII マスク]
  Q8 --> OK[完成]

  style OK fill:#e1ffe1

この章の要点

  • Three Pillars: Metrics(集計)/ Logs(イベント)/ Traces(経路)
  • OpenTelemetry が事実上の標準、ベンダーロックイン回避
  • Trace Context の伝播で複数サービス跨ぎが可視化
  • SLI を Metrics で実装、後付けは大変
  • Sampling 必須、特に Tail-based(問題ある request を残す)
  • 利用者に 見せる Dashboard / Status Page / Per-tenant 観測性
  • Structured Logging + PII マスク + 量の制御

次章への問いかけ

状態は見える化した。次は「何かが壊れたとき、被害を最小化する」設計。

次章で 障害伝播の抑制 ── Bulkhead / Circuit Breaker / Backpressure / Timeout / Retry の組み合わせ。