非機能 (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 あたりのコスト感 | 月コスト |
|---|---|---|
| Metrics | 1KB(集計済み) | 数千 USD |
| Logs | 10KB - 1MB | 数千 - 数十万 USD |
| Traces | 100KB(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 の組み合わせ。