data-scale · Stage 3
利用者journeyからSLI、SLO、alert、runbookを導く
内部resourceではなく利用者に見える結果をgood eventへ変換し、error budgetとburn rateから行動可能な運用契約を作る。
到達目標
利用者journeyのvalid eventとgood eventを定義し、window、target、error budgetを計算できる
- 利用者ジャーニーから導いたSLI、SLO、アラート、ランブック
- good event、burn window、telemetry品質、on-call行動を再設計する回答
error budget burnをmulti-windowで評価し、pageとticketを行動可能性で分けられる
- 利用者ジャーニーから導いたSLI、SLO、アラート、ランブック
- SLOとSLA、SLIと内部resource metric、pageとticketを区別する5分説明
telemetryのversion、stability、correlation、PII、cardinality、samplingを運用契約として管理できる
- good event、burn window、telemetry品質、on-call行動を再設計する回答
- 利用者可視の信頼性境界を変えてSLIとSLOを再計算した記録
能力の進行
recognize
SLI、SLO、SLA、error budget、burn rate、page、ticketを区別できる
証拠: 利用者ジャーニーから導いたSLI、SLO、アラート、ランブック
explain
100%目標とtelemetry量の最大化が信頼性を保証しない理由を説明できる
証拠: SLOとSLA、SLIと内部resource metric、pageとticketを区別する5分説明
apply
固定eventからgoodとvalidを分類し、window targetとmulti-window alertを計算できる
証拠: 利用者ジャーニーから導いたSLI、SLO、アラート、ランブック
diagnose
利用者impactからtrace、metric、logへ相関し、samplingと欠測の限界を示せる
証拠: good event、burn window、telemetry品質、on-call行動を再設計する回答
lead
error budget方針、on-call action、PIIとcardinalityの境界を関係者と更新できる
証拠: 利用者可視の信頼性境界を変えてSLIとSLOを再計算した記録
なぜ重要か
serviceの内部metricが正常でも、利用者は購入を完了できないことがある。信頼性を運用可能にするには、利用者journeyのどのeventを分母にし、何をgoodと数え、どのwindowでどこまで許容し、超過時に誰が何をするかを一続きの契約へ変える必要がある。
observabilityはtelemetry量の最大化ではない。利用者impactから原因候補へ移れるsignalを、version、意味、correlation、privacy、cardinality、samplingの制約付きで維持する能力である。行動できないpageは、signalではなくon-call負荷になる。
メンタルモデル
利用者journeyをvalid eventの集合として定義し、そのうち期待した結果と時間境界を満たすものをgoodとする。SLIはgood÷valid、SLOはwindowとtarget、error budgetは許容できるbadの割合である。burn rateは観測したbad割合がbudgetを何倍の速さで消費するかを表す。
pageは人が直ちに取る行動があり、error budgetを急速に失う時に限定する。長いwindowの緩やかなburnや容量傾向はticketにし、参考情報はdashboardへ置く。thresholdを緩める前に、利用者impact、false positive、runbookの有効性を確認する。
注記
図を読む際の補足情報です。
- この注記は旧図の読み順を保持する補助です。
- Journey: 利用者が達成したい結果とvalidな試行を定める。
- SLI: goodの結果とlatency境界をevent単位で計算する。
- SLO: window、target、error budget、例外を合意する。
- Alert: 短窓と長窓のburnをpageとticketへ分ける。
- Runbook: impact確認、mitigation、rollback、escalationを結ぶ。
- Telemetry: traceで原因へ相関し、意味とprivacyを検証する。
どのSLIとburn evidenceがpageを起動し、mitigation後の何を確認してloopを終了するか。
- Journey
- 利用者が達成したい結果とvalidな試行を定める。
- SLI
- goodの結果とlatency境界をevent単位で計算する。
- SLO
- window、target、error budget、例外を合意する。
- Telemetry
- traceで原因へ相関し、意味とprivacyを検証する。
- Alert
- 短窓と長窓のburnをpageとticketへ分ける。
- Runbook
- impact確認、mitigation、rollback、escalationを結ぶ。
- 復旧証拠成立
- 利用者結果、burn rate、mitigation後のservice状態を再観測し、pageを閉じられる。
- Journey → SLI: valid eventからgoodを計算する
- SLI → SLO: windowとtargetを合意する
- SLO → Telemetry: SLIを安定したsignalとして収集する
- Telemetry → Alert: 短窓と長窓のburnを評価する
- Alert → Runbook: pageからmitigationへ進む
- Runbook → Telemetry: mitigation後の利用者結果を再観測する
- Runbook → 復旧証拠成立: 復旧とburn正常化を確認してpageを閉じる
- 終了状態
- 復旧証拠成立
- 回復状態
- Alert
JourneyからSLI・SLO・telemetry・alert・runbookへ進み、復旧証拠が揃ったterminal stateを示せる。
| パラメータ | 選択肢 | 既定値 |
|---|---|---|
| error-window scenario | normal: failureなし、直近5分に1 failure、直近12分がfailure | normal |
- budget healthy: 28日、target=90%、error budget=10%。60/60 good、SLI=100%、5分/60分のburn=0、page=false。; 条件
error-window=normal; nodejourney、sli、slo; edgejourney-to-sli、sli-to-slo - 短窓だけfast burn: 28日、target=90%、error budget=10%。5分は1/5 badでshort burn=2、60分は1/60 badでlong burn=0.1667、page=false。; 条件
error-window=short-only; nodeslo、telemetry、alert; edgeslo-to-telemetry、telemetry-to-alert - multi-window page: 28日、target=90%、error budget=10%。5分は5/5 badでshort burn=10、60分は12/60 badでlong burn=2、page=true。; 条件
error-window=both; nodealert、runbook; edgealert-to-runbook
| イベント | 開始 | 終了 | 条件 |
|---|
| 結果 | 状態 |
|---|---|
| 両windowがthreshold未満なのでpageせず、通常観測を続ける。 | budget-healthy |
| short burn=2でもlong burn=0.1667のためAND条件はfalse。pageしない。 | fast-burn |
| short burn=10かつlong burn=2。購入impactを確認しcheckoutを縮退する。 | page-triggered |
現在の状態: budget healthy — 28日、target=90%、error budget=10%。60/60 good、SLI=100%、5分/60分のburn=0、page=false。
このモデルは例示的かつ決定的であり、実システムの完全な再現ではありません。
動く例で考える
購入完了journeyからmulti-window alertを計算する
- 前提
- 2026-07-31T12:00:00+00:00を観測終了とするlesson-defined synthetic eventを使い、production SLOとは主張しない。購入完了と検索成功のeventは同じfixtureへtimestamp付きで保持する。
- 入力
- statusと300 msのlatency境界からgoodを分類する。28日window、target 90%という教材fixtureで、直近5分と60分のeventをtimestampから別々に選び、bad割合とburn rateを計算する。
- 操作
- 短窓と長窓がともにpage thresholdを越えた時だけpageする。正常、短窓だけ異常、両窓異常の三fixtureでAND条件を反証する。OpenTelemetry 1.59.0とSemantic Conventions 1.43.0をversion固定し、resource属性、trace correlation、PII、cardinality、samplingを検査する。
- 観測
- 正常では両条件false、直近5分に1 failureだけ加えると短窓だけtrue、直近12分をfailureにすると両窓trueとなる。transferでは同じevent、target、window、telemetryを保ち、検索成功率から購入完了率へjourney境界だけを変える。
- 結論
- SLOはdashboardの装飾ではなく、利用者impact、変更判断、on-call actionをつなぐ境界である。fixtureのtargetは教材用であり、実serviceでは利用者期待、事業価値、過去実績とerror budget方針から決め直す。
python3.13 - <<'PY'
from datetime import datetime, timedelta, timezone
import json
HARNESS = "reliability_slo_lab_v1"
TARGET = 0.9
LATENCY_LIMIT_MS = 300
SHORT_WINDOW_MINUTES = 5
LONG_WINDOW_MINUTES = 60
PAGE_BURN_THRESHOLD = 2.0
OBSERVATION_END = datetime(
2026,
7,
31,
12,
0,
tzinfo=timezone.utc,
)
REQUIRED_RESOURCE_ATTRIBUTES = [
"service.name",
"deployment.environment.name",
"service.version",
]
DEPRECATED_RESOURCE_ATTRIBUTES = {
"deployment.environment",
}
def make_event(event_id, journey, minutes_ago, good):
return {
"id": event_id,
"journey": journey,
"eligible": True,
"timestamp": (
OBSERVATION_END - timedelta(minutes=minutes_ago)
).isoformat(),
"status": "ok" if good else "failed",
"latency_ms": 100 if good else 500,
}
def purchase_fixture(name, bad_offsets):
return [
make_event(
f"{name}-purchase-{offset:02}",
"purchase-completion",
offset,
offset not in bad_offsets,
)
for offset in range(LONG_WINDOW_MINUTES)
]
SCENARIO_EVENTS = {
"normal": purchase_fixture("normal", set()),
"short-only": purchase_fixture("short-only", {0}),
"both": purchase_fixture("both", set(range(12))),
}
SEARCH_EVENTS = [
make_event(
f"search-{offset:02}",
"search-success",
offset,
offset != 9,
)
for offset in range(10)
]
FIXTURE_EVENTS = SCENARIO_EVENTS["both"] + SEARCH_EVENTS
REQUEST_FIXTURE = [
{"request_id": "request-1", "trace_id": "trace-a"},
{"request_id": "request-2", "trace_id": "trace-b"},
{"request_id": "request-3", "trace_id": "trace-c"},
]
TRACE_FIXTURE = [
{"request_id": "request-1", "trace_id": "trace-a"},
{"request_id": "request-2", "trace_id": "trace-b"},
{"request_id": "request-3", "trace_id": "trace-c"},
]
LOG_FIXTURE = [
{"request_id": "request-1", "trace_id": "trace-a"},
{"request_id": "request-2", "trace_id": "trace-b"},
{"request_id": "request-3", "trace_id": "trace-c"},
]
METRIC_SERIES_FIXTURE = [
{
"journey.name": "purchase-completion",
"result": "ok",
"deployment.environment.name": "lesson",
},
{
"journey.name": "purchase-completion",
"result": "failed",
"deployment.environment.name": "lesson",
},
{
"journey.name": "search-success",
"result": "ok",
"deployment.environment.name": "lesson",
},
{
"journey.name": "purchase-completion",
"result": "ok",
"deployment.environment.name": "lesson",
},
]
METRIC_SERIES_BOUND = 4
def derive_sli(events, journey):
valid = []
classified = []
for event in events:
if event["journey"] != journey or not event["eligible"]:
continue
valid.append(event)
good = event["status"] == "ok" and event["latency_ms"] <= 300
classified.append(
{
"id": event["id"],
"timestamp": event["timestamp"],
"good": good,
}
)
assert valid, f"journey-boundary: no valid events for {journey}"
good_events = sum(item["good"] for item in classified)
bad_events = len(valid) - good_events
return {
"journey": journey,
"total_events": len(events),
"valid_events": len(valid),
"good_events": good_events,
"bad_events": bad_events,
"ratio": good_events / len(valid),
"classified": classified,
}
def burn_window(events, journey, window_minutes):
window_start = OBSERVATION_END - timedelta(minutes=window_minutes)
selected = [
event
for event in events
if (
window_start
< datetime.fromisoformat(event["timestamp"])
<= OBSERVATION_END
)
]
sli = derive_sli(selected, journey)
observed_bad_fraction = sli["bad_events"] / sli["valid_events"]
return {
"window_minutes": window_minutes,
"start_timestamp": window_start.isoformat(),
"end_timestamp": OBSERVATION_END.isoformat(),
"valid_events": sli["valid_events"],
"good_events": sli["good_events"],
"bad_events": sli["bad_events"],
"observed_bad_fraction": observed_bad_fraction,
"rate": observed_bad_fraction / (1 - TARGET),
}
def evaluate_alert(events, journey):
windows = {
"short": burn_window(
events,
journey,
SHORT_WINDOW_MINUTES,
),
"long": burn_window(
events,
journey,
LONG_WINDOW_MINUTES,
),
}
conditions = {
name: window["rate"] >= PAGE_BURN_THRESHOLD
for name, window in windows.items()
}
return windows, {
"multi_window": True,
"combination": "short-and-long",
"page": {
"conditions": conditions,
"threshold": PAGE_BURN_THRESHOLD,
"triggered": conditions["short"] and conditions["long"],
"actionable": True,
"action": "購入impactを確認しcheckoutを縮退する",
},
"ticket": {
"window_minutes": LONG_WINDOW_MINUTES,
"threshold": 1.0,
"triggered": windows["long"]["rate"] >= 1.0,
"actionable": True,
"action": "翌営業日までにbudget消費原因を追跡する",
},
}
def validate_telemetry(resource_attributes, metric_labels):
errors = []
for attribute in REQUIRED_RESOURCE_ATTRIBUTES:
if not resource_attributes.get(attribute):
errors.append("missing-resource-attribute:" + attribute)
for attribute in DEPRECATED_RESOURCE_ATTRIBUTES:
if attribute in resource_attributes:
errors.append("deprecated-resource-attribute:" + attribute)
forbidden = {"email", "user.id", "order.id", "trace_id"}
if forbidden.intersection(metric_labels):
errors.append("pii-or-high-cardinality-label")
return sorted(errors)
def derive_trace_correlation(requests, traces, logs):
# A trace ID alone is insufficient evidence: matching request and trace
# identities must be present in both the span and log fixtures.
trace_pairs = {
(item["request_id"], item["trace_id"])
for item in traces
}
log_pairs = {
(item["request_id"], item["trace_id"])
for item in logs
}
request_ids = sorted(item["request_id"] for item in requests)
correlated_request_ids = sorted(
item["request_id"]
for item in requests
if (
(item["request_id"], item["trace_id"]) in trace_pairs
and (item["request_id"], item["trace_id"]) in log_pairs
)
)
correlated_requests = len(correlated_request_ids)
total_requests = len(request_ids)
return {
"passed": correlated_requests == total_requests,
"correlated_requests": correlated_requests,
"total_requests": total_requests,
"correlated_request_ids": correlated_request_ids,
"request_ids": request_ids,
"fixture": {
"requests": requests,
"traces": traces,
"logs": logs,
},
}
def derive_cardinality(metric_series, series_bound):
# Cardinality is counted from unique label sets so duplicate observations
# do not falsely consume the bounded-series budget.
unique_series = {
tuple(sorted(sample.items()))
for sample in metric_series
}
unique_series_count = len(unique_series)
return {
"bounded": unique_series_count <= series_bound,
"unique_series_count": unique_series_count,
"series_bound": series_bound,
"metric_series_fixture": metric_series,
}
def main():
sli = derive_sli(FIXTURE_EVENTS, "purchase-completion")
burn_windows, alerts = evaluate_alert(
SCENARIO_EVENTS["both"],
"purchase-completion",
)
scenario_reports = {}
for name, events in SCENARIO_EVENTS.items():
windows, scenario_alerts = evaluate_alert(
events,
"purchase-completion",
)
scenario_reports[name] = {
"windows": windows,
"conditions": scenario_alerts["page"]["conditions"],
"page_triggered": scenario_alerts["page"]["triggered"],
}
alerts["scenarios"] = scenario_reports
assert {
name: (
result["conditions"]["short"],
result["conditions"]["long"],
result["page_triggered"],
)
for name, result in scenario_reports.items()
} == {
"normal": (False, False, False),
"short-only": (True, False, False),
"both": (True, True, True),
}, "multi-window-causal-invariant"
resources = {
"service.name": "checkout",
"deployment.environment.name": "lesson",
"service.version": "1.0.0",
}
metric_labels = {
"journey.name",
"result",
"deployment.environment.name",
}
telemetry_errors = validate_telemetry(resources, metric_labels)
assert telemetry_errors == []
deprecated_probe_errors = validate_telemetry(
{
**resources,
"deployment.environment": "lesson",
},
metric_labels,
)
assert deprecated_probe_errors == [
"deprecated-resource-attribute:deployment.environment"
]
trace_correlation = derive_trace_correlation(
REQUEST_FIXTURE,
TRACE_FIXTURE,
LOG_FIXTURE,
)
cardinality_check = derive_cardinality(
METRIC_SERIES_FIXTURE,
METRIC_SERIES_BOUND,
)
assert trace_correlation["passed"]
assert cardinality_check["bounded"]
baseline_sli = derive_sli(FIXTURE_EVENTS, "search-success")
transferred_sli = derive_sli(
FIXTURE_EVENTS,
"purchase-completion",
)
baseline_contract = {
"journey": "search-success",
"target": TARGET,
"window_days": 28,
"event_ids": [event["id"] for event in FIXTURE_EVENTS],
"telemetry_errors": validate_telemetry(resources, metric_labels),
}
transferred_contract = {
**baseline_contract,
"journey": "purchase-completion",
"event_ids": [event["id"] for event in FIXTURE_EVENTS],
"telemetry_errors": validate_telemetry(resources, metric_labels),
}
changed_fields = sorted(
field
for field in baseline_contract
if baseline_contract[field] != transferred_contract[field]
)
journey_boundary_transfer = {
"changed_fields": changed_fields,
"baseline_sli": baseline_sli,
"transferred_sli": transferred_sli,
"baseline_event_ids": baseline_contract["event_ids"],
"transferred_event_ids": transferred_contract["event_ids"],
"unchanged": {
"event_fixture": (
baseline_contract["event_ids"]
== transferred_contract["event_ids"]
),
"target": (
baseline_contract["target"]
== transferred_contract["target"]
),
"window": (
baseline_contract["window_days"]
== transferred_contract["window_days"]
),
"telemetry_contract": (
baseline_contract["telemetry_errors"]
== transferred_contract["telemetry_errors"]
),
},
"telemetry_errors_before": baseline_contract["telemetry_errors"],
"telemetry_errors_after": transferred_contract["telemetry_errors"],
}
assert changed_fields == ["journey"]
assert baseline_sli["ratio"] != transferred_sli["ratio"]
assert journey_boundary_transfer["baseline_event_ids"] == (
journey_boundary_transfer["transferred_event_ids"]
)
report = {
"harness": HARNESS,
"fixture": "timestamped-user-journey-events-v2",
"fixture_events": FIXTURE_EVENTS,
"fixture_metadata": {
"kind": "synthetic",
"provenance": "lesson-defined timestamped user-journey events",
"purpose": "SLI、window別burn、alert、journey境界の因果を反証する",
"limitations": "production traffic、SLA、利用者期待を表さない",
},
"sli": sli,
"slo": {
"window_days": 28,
"target": TARGET,
"error_budget_fraction": 1 - TARGET,
"decision_scope": "internal reliability objective, not SLA",
},
"burn_windows": burn_windows,
"alerts": alerts,
"runbook": {
"owner": "checkout-on-call",
"user_impact_check": "購入完了eventとclient errorを確認する",
"mitigation": "高負荷機能を止めcheckoutへ容量を戻す",
"rollback": "直前releaseとの相関があればrollbackする",
"escalation": "決済依存またはprivacy incidentを各ownerへ上げる",
},
"telemetry_contract": {
"otel_specification": "1.59.0",
"semantic_conventions": "1.43.0",
"required_resource_attributes": REQUIRED_RESOURCE_ATTRIBUTES,
"schema_validation": {
"deprecated_attribute_rejected": bool(
deprecated_probe_errors
),
"deprecated_probe_errors": deprecated_probe_errors,
},
"trace_correlation": trace_correlation,
"pii_check": {
"passed": not telemetry_errors,
"forbidden_metric_labels": ["email", "user.id", "order.id"],
},
"cardinality_check": {
**cardinality_check,
"metric_labels": sorted(metric_labels),
"trace_id_location": "span-log-correlation-only",
},
"sampling_limitations": (
"tailやrare failureの推定はsampling policyと欠測率を伴う"
),
"stability": (
"stableとdevelopmentのsemantic fieldsをversionごとにreviewする"
),
},
"journey_boundary_transfer": journey_boundary_transfer,
"mastery_evidence": {
"lab_steps": [
{"step": 1, "evidence": "timestamp付きfixtureとclassified"},
{"step": 2, "evidence": "SLI、SLO、window別burn rate"},
{"step": 3, "evidence": "normal、short-only、bothのAND判定"},
{"step": 4, "evidence": "owner付きrunbook"},
{"step": 5, "evidence": "telemetry検証とjourney境界だけのtransfer"},
],
"assessments": [
{"assessment": 1, "evidence": "purchase journey境界とtrace correlation"},
{"assessment": 2, "evidence": "burn windowとactionability"},
],
"rubric_dimensions": [
"technical-correctness",
"judgment",
"evidence",
"communication",
],
"transfer": {
"task": (
"検索成功率から購入完了までへ利用者可視の"
"信頼性境界を変え、SLIとSLOを再設計する"
),
"changed_assumption": "user-visible-reliability",
"evidence": (
"同じevent、target、window、telemetryで"
"journeyだけを変えたSLI再計算"
),
},
},
"external_network_used": False,
}
return report
print(json.dumps(main(), ensure_ascii=False, indent=2))
PY
トレードオフと失敗モード
| 利用者影響 | 通知 | 必要な行動 | 再評価条件 |
|---|---|---|---|
| 短窓と長窓の両方で急速なbudget消費 | page | 即時impact確認とmitigation | pageされても人が取る行動がない時 |
| 長窓だけで緩やかなbudget消費 | ticket | 営業日内の原因除去と計画更新 | 短窓でも急増し始めた時 |
| budgetへ未影響の内部resource変化 | dashboard | 診断contextとcapacity傾向の保持 | 利用者SLIとの先行相関が検証された時 |
- 誤診: SLOは顧客とのSLAと同じなので違反を避けるには100%にすべきだ。反証: SLOは内部の目標と判断境界であり、SLAの契約結果とは分ける。100%はerror budgetを失わせ、変更とriskのtrade-offを隠す。
- 誤診: metric、log、traceを全量保存すればobservabilityは最大になる。反証: 意味の不一致、PII、高cardinality、費用、sampling、欠測を管理しなければ、量が増えても利用者impactから行動へ到達できない。
- 失敗モード: health checkを利用者requestの分母へ混ぜると、実journeyが失敗していてもSLIを水増しできる。
- 失敗モード: trace IDやuser IDをmetric labelへ置くとcardinalityとprivacy riskが増える。相関IDはspanとlogで扱い、集約metricのlabelをboundedにする。
知識チェック
- good eventとvalid eventを分ける理由を購入journeyで説明せよ。
- burn rate 1と3はerror budgetの消費速度として何が違うか。
- pageとticketをwindowとactionabilityでどう分けるか。
- samplingとsemantic conventionのstabilityが既存alertへ与えるriskは何か。
出典と次の学習
SLI、SLO、error budgetの定義はGoogle SRE Chapter 4、multi-window burnとactionable alertはSRE Workbook Chapter 5、pager負荷とrunbookはChapter 8を基準にする。telemetry contractはOpenTelemetry Specification 1.59.0とSemantic Conventions 1.43.0へversion固定して照合する。
次のhuman/product領域では、systemが記録したsuccessだけでなく、keyboard、zoom、読み上げ、usabilityを含む利用者側のjourneyへ証拠境界を広げる。
実践ラボ
購入完了journeyのSLOとmulti-window alertを作る
提出成果物: 利用者ジャーニーから導いたSLI、SLO、アラート、ランブック
- timestamp付きの固定synthetic eventを購入完了journeyのvalid、good、badへ分類する
- good÷validのSLI、28日window、target、error budget、5分と60分それぞれのbad割合とburn rateを計算する
- 正常、短窓だけ異常、両窓異常を再現し、短窓と長窓のAND条件でpageを定義する
- runbookへuser impact確認、mitigation、rollback、escalation、ownerを結ぶ
- OpenTelemetry契約を検査し、同じevent、target、window、telemetryのまま検索成功率から購入完了率へjourney境界だけを変えてSLIを再計算する
説明して理解を確かめる
5分で、SLOとSLAが同義でない理由、CPU metricを利用者SLIにできない場合、100%目標とtelemetry全量保存の代償を説明する。
アセスメント
問い: API可用性は高いが購入完了率が低下した。SLI境界と診断telemetryをどう変えるか。
期待する証拠: journeyのvalidとgood、client側結果、依存先、trace correlation、欠測、二重計上を含む再設計
問い: 短時間のburnで毎回pageし、on-callが行動できない。何をpage、ticket、dashboardへ分けるか。
期待する証拠: error budget影響、multi-window、actionability、false positive、runbook、pager負荷の比較
別問題へ転用する
検索成功率から購入完了までへ利用者可視の信頼性境界を変え、SLIとSLOを再設計する
復習スケジュール
- 1日後
利用者journeyのvalid eventから除外する条件を一つ説明する
- 7日後
短窓pageと長窓ticketを分ける理由は何か
- 30日後
high-cardinalityとPIIをtelemetry契約でどう防ぐか
- 90日後
利用者journeyのvalid eventから除外する条件を一つ説明する
評価ルーブリック
| 観点 | 未達 | 発展途上 | 熟達 | 卓越 |
|---|---|---|---|---|
| technical-correctness | 分母とgood eventを定義せず内部metricをSLOと呼ぶ | SLIとtargetはあるがwindow、error budget、欠測を扱わない | valid、good、window、target、budget、burn、alertを一貫して計算する | 重複、欠測、sampling、telemetry version変更でも計算契約を維持する |
| judgment | すべてを100%目標と即時pageにする | thresholdを置くが利用者impactと行動を説明しない | error budget、短窓と長窓、on-call actionからpageとticketを選ぶ | 事業価値、変更速度、pager負荷をerror budget方針へ反映する |
| evidence | dashboardの印象だけでSLO達成を主張する | event計算はあるがfixture出自またはtelemetry欠測を示さない | 固定event、計算式、alert、runbook、mutationを再実行可能に残す | clientとserverの異種signalを相関しsamplingによる不確実性を定量化する |
| communication | metric名だけを共有し利用者journeyとownerが不明である | SLO文書はあるがalert時の行動とescalationがない | 利用者impact、計算、threshold、owner、runbookを一つの契約で追える | 開発、運用、productが信頼性境界と変更判断を共同更新できる |
出典
以下の外部資料は利用者が選択したときだけ開きます。
- Service Level Objectives (primary)
- Alerting on SLOs (primary)
- On-Call (primary)
- OpenTelemetry Specification 1.59.0 (standard)
- OpenTelemetry semantic conventions 1.43.0 (standard)