chore: initialize from backend template 0a6dd0e

This commit is contained in:
DongHyeonka
2026-08-13 20:31:02 +09:00
commit e64e701fe5
3223 changed files with 388401 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — ADAPTER_DISABLED (런타임 어댑터 비활성화 호출)
category: INTERNAL
error_codes: [ADAPTER_DISABLED]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: ADAPTER_DISABLED (`runbook://adapter/adapter-disabled`)
## Symptoms
- HTTP 500 with `error.code=ADAPTER_DISABLED`
- Code invoked an optional adapter (Kafka/Redis/Slack/Email) that is disabled in this deployment
## Diagnosis
- Check adapter name in log (`adapter_name` field)
- Review deployment config — which optional adapters are enabled?
## Action
- Enable the adapter in deployment configuration (env flag)
- Or update application logic to skip disabled-adapter paths
## Escalation
- Escalate to deployment team if adapter should be enabled but isn't
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_AUDIENCE_MISMATCH (대상 불일치)
category: AUTH
error_codes: [AUTH_AUDIENCE_MISMATCH]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_AUDIENCE_MISMATCH (`runbook://auth/audience-mismatch`)
## Symptoms
- HTTP 401 with `error.code=AUTH_AUDIENCE_MISMATCH`
- Token `aud` claim does not include this service's expected audience
## Diagnosis
- Check token `aud` claim value
- Compare against configured `spring.security.oauth2.resourceserver.jwt.audiences`
## Action
- Verify client is requesting tokens scoped to the correct audience
- Update audience configuration if service identifier changed
## Escalation
- Escalate to auth-platform team if misconfiguration is system-wide
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_CLAIM_MAPPING_FAILED (클레임 매핑 실패)
category: AUTH
error_codes: [AUTH_CLAIM_MAPPING_FAILED]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_CLAIM_MAPPING_FAILED (`runbook://auth/claim-mapping-failed`)
## Symptoms
- HTTP 401 with `error.code=AUTH_CLAIM_MAPPING_FAILED`
- Token validated but required claims (sub, roles, tenant) missing or unexpected type
## Diagnosis
- Inspect token payload claims via logs
- Check claim extractor configuration
## Action
- Verify IdP token template includes required claims
- Update claim mapping configuration if IdP schema changed
## Escalation
- Escalate to auth-platform team if IdP changed claim schema
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_ISSUER_MISMATCH (발급자 불일치)
category: AUTH
error_codes: [AUTH_ISSUER_MISMATCH]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_ISSUER_MISMATCH (`runbook://auth/issuer-mismatch`)
## Symptoms
- HTTP 401 with `error.code=AUTH_ISSUER_MISMATCH`
- Token `iss` claim does not match configured expected issuer
## Diagnosis
- Compare token `iss` against `spring.security.oauth2.resourceserver.jwt.issuer-uri`
- Check if IdP environment changed
## Action
- Update issuer config if IdP migrated
- Reject tokens from unexpected issuers
## Escalation
- Escalate to platform-security if unexpected issuer detected
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_JWKS_UNAVAILABLE (JWKS 엔드포인트 장애)
category: TRANSIENT_DEPENDENCY
error_codes: [AUTH_JWKS_UNAVAILABLE]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_JWKS_UNAVAILABLE (`runbook://auth/jwks-unavailable`)
## Symptoms
- HTTP 503 with `error.code=AUTH_JWKS_UNAVAILABLE`
- All authentication failing; JWKS refresh attempts failing
## Diagnosis
- Check IdP JWKS endpoint health: `curl -sf https://<idp-host>/.well-known/jwks.json`
- Check network connectivity from app pods to IdP
## Action
- Enable cached JWKS fallback if available
- Coordinate with IdP team for restoration
## Escalation
- P1 page: IdP team immediately if JWKS endpoint unreachable > 2 minutes
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_KID_UNKNOWN (키 ID 미인식)
category: AUTH
error_codes: [AUTH_KID_UNKNOWN]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_KID_UNKNOWN (`runbook://auth/kid-unknown`)
## Symptoms
- HTTP 401 with `error.code=AUTH_KID_UNKNOWN`, `retryable=true`
- Token `kid` header not present in cached JWKS
## Diagnosis
- Check if IdP key rotation occurred recently
- Verify JWKS cache TTL and refresh timing
## Action
- Force JWKS cache refresh
- Confirm new key is published in IdP JWKS endpoint
## Escalation
- Escalate to IdP team if new kid not appearing in JWKS after 10 minutes
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — INTERNAL_AUTH_MISCONFIGURATION (공개 경로 설정 오류)
category: INTERNAL
error_codes: [INTERNAL_AUTH_MISCONFIGURATION]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: INTERNAL_AUTH_MISCONFIGURATION (`runbook://auth/public-path-misconfiguration`)
## Symptoms
- HTTP 500 with `error.code=INTERNAL_AUTH_MISCONFIGURATION`
- Security filter misconfiguration detected at runtime
## Diagnosis
- Check `verifyPublicPathSnapshot` output in CI
- Review recent changes to `SecurityConfig` or `application.yml` public path list
## Action
- Revert misconfigured public path change
- Run `./gradlew verifyPublicPathSnapshot` to compare snapshot
## Escalation
- P1 immediate: if auth bypass is possible due to misconfiguration
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_TOKEN_EXPIRED (토큰 만료)
category: AUTH
error_codes: [AUTH_TOKEN_EXPIRED]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_TOKEN_EXPIRED (`runbook://auth/token-expired`)
## Symptoms
- HTTP 401 with `error.code=AUTH_TOKEN_EXPIRED`
- Spike may indicate clock skew or long-lived token usage
## Diagnosis
- Check `exp` claim vs server clock
- Check NTP sync on token-issuing host
## Action
- Client must refresh tokens before expiry
- Verify clock skew tolerance is configured (default 60s)
## Escalation
- Escalate if spike is widespread or clock drift is confirmed
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_TOKEN_INVALID_SIGNATURE (서명 검증 실패)
category: AUTH
error_codes: [AUTH_TOKEN_INVALID_SIGNATURE]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_TOKEN_INVALID_SIGNATURE (`runbook://auth/token-invalid-signature`)
## Symptoms
- HTTP 401 with `error.code=AUTH_TOKEN_INVALID_SIGNATURE`
- `log_level=ERROR` — may indicate forged tokens or wrong signing key
## Diagnosis
- Check if JWKS endpoint returned a new key set
- Check for token forgery attempts in logs
## Action
- Verify JWKS key IDs match token headers
- Alert security team if forgery suspected
## Escalation
- Immediate P1 escalation if forgery indicators present
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_TOKEN_MALFORMED (토큰 파싱 실패)
category: AUTH
error_codes: [AUTH_TOKEN_MALFORMED]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_TOKEN_MALFORMED (`runbook://auth/token-malformed`)
## Symptoms
- HTTP 401 responses with `error.code=AUTH_TOKEN_MALFORMED`
- Token present but fails JWT parse (not 3-part, non-base64, etc.)
## Diagnosis
- Inspect raw Authorization header value in logs
- Check if token generation tooling has a bug
## Action
- Identify source of malformed tokens
- Fix or update client token generation
## Escalation
- Escalate if spike suggests infrastructure issue
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_TOKEN_MISSING (인증 토큰 누락)
category: AUTH
error_codes: [AUTH_TOKEN_MISSING]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_TOKEN_MISSING (`runbook://auth/token-missing`)
## Symptoms
- HTTP 401 responses with `error.code=AUTH_TOKEN_MISSING`
- Client missing Authorization header or Bearer token
## Diagnosis
- Check request logs for missing Authorization header
- Verify client SDK configuration
## Action
- Confirm API clients are sending Authorization header
- Check gateway/proxy configuration for header stripping
## Escalation
- Escalate if widespread or affecting critical workflows
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,69 @@
---
title: Runbook — JWT key rotation 시 인증 실패 spike
category: AUTH
error_codes: [AUTH_TOKEN_EXPIRED, AUTH_KID_UNKNOWN, AUTH_JWKS_UNAVAILABLE, AUTH_TOKEN_INVALID_SIGNATURE]
severity: P1
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: JWT key rotation 시 인증 실패 spike
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `auth_401_error_rate_high` 또는 `jwks_refresh_failure_spike`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `runbook_link`, `dependency_name`
- 임계: 401 error rate > 5% 5분 지속 OR JWKS refresh failure count > 10건/분
## 2. First Response (5분 이내)
### Step 1 — 확인
1. JWKS endpoint health check: `curl -sf https://<idp-host>/.well-known/jwks.json | jq '.keys | length'`
2. log query에서 `error.code` 분포 확인 — `AUTH_KID_UNKNOWN` 비중이 높으면 rotation 원인 강력 시사
3. IdP rotation schedule 확인 (직전 24h 내 rotation 이벤트가 있었는지)
### Step 2 — 임시 격리
- JWKS cache TTL을 짧게(예: 60s) 강제하여 새 kid 전파 가속
- 새 kid가 JWKS에 publish되어 있는지 확인. 누락이면 IdP에 republish 요청
## 3. Diagnosis
- log query (Loki/CloudWatch): `{service="auth"} | error.category="AUTH" | dependency_name="jwks-endpoint"`
- metric panel: `auth_jwks_cache_hit_ratio`, `auth_jwks_refresh_failure_total`, `auth_kid_unknown_total`
- trace: 실패한 request 1건에서 `traceId` 추출 → IdP outbound span 확인
- 가능한 원인:
- 새 kid가 JWKS에 publish되기 전 token 발급 → 24h overlap window 안에 있는지 확인
- JWKS endpoint 장애 (5xx, timeout) → IdP status page 확인
- 시계 skew로 인한 만료 오판 → NTP sync 상태 확인
## 4. Mitigation
- 단기: old kid를 임시 재허용 (rollback). overlap window를 48h로 일시 확장
- IdP에 새 JWKS publish 재시도 요청
- 장기: rotation 절차에 "publish → 24h 대기 → switch" 단계 강제. observability에 kid 분포 metric 추가
## 5. Escalation
- P2 → P1 격상 조건: 401 error rate > 20% 또는 다중 tenant에 동시 발생
- 다음 on-call로 page: 10분 내 회복 안 되면 IdP team 또는 platform-security team page
## 6. Recovery / Verification
- 회복 확인 metric: `auth_401_error_rate < 1%` 5분 지속, `AUTH_KID_UNKNOWN` 건수 0
- post-incident:
- rotation 절차 RCA 작성
- JWKS overlap window 정책 문서 업데이트
- kid 분포 dashboard 영구화
## 7. Related
- error-codes.yaml rows: `AUTH_TOKEN_EXPIRED`, `AUTH_KID_UNKNOWN`, `AUTH_JWKS_UNAVAILABLE`, `AUTH_TOKEN_INVALID_SIGNATURE`
- metrics.yaml: `auth_jwks_cache_hit_ratio`, `auth_jwks_refresh_failure_total`
- 관련 branch: [[feature-security-operational-baseline]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 IdP 종류·rotation 정책·JWKS endpoint URL·dashboard 링크로 보강 필요.
@@ -0,0 +1,72 @@
---
title: Runbook — cross-tenant 접근 시도 감지
category: AUTHZ
error_codes: [AUTHZ_INSUFFICIENT_PERMISSION, AUTHZ_TENANT_MISMATCH]
severity: P2
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: cross-tenant 접근 시도 감지
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `authz_cross_tenant_violation` 또는 `authz_403_spike`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `principal_id_pseudonymized`, `tenant_id`, `runbook_link`
- 임계:
- P2: 403 with `error.code=AUTHZ_TENANT_MISMATCH` > 10건/5분
- P1 격상: 동일 principal에서 3개 이상 tenant 시도 OR 5분 내 100건 초과
## 2. First Response (5분 이내)
### Step 1 — 확인
1. log query로 위반 principal 식별 (pseudonymized): `error.code=AUTHZ_TENANT_MISMATCH`
2. principal의 정상 tenant scope 확인 (IdP claim 또는 entitlement table)
3. `CROSS_TENANT_ADMIN` capability 보유 여부 확인 — 보유자라면 false positive 가능성
### Step 2 — 임시 격리
- 명백한 위반 패턴이면 principal session 강제 만료 (token revocation list 추가)
- security incident channel 통보 (`#sec-incident`)
- 위반 request의 source IP / user-agent 기록
## 3. Diagnosis
- log query: `{service="api"} | error.category="AUTHZ" | principal_id_pseudonymized="<hash>"`
- metric panel: `authz_denied_total{reason="tenant_mismatch"}`, `authz_principal_tenant_distribution`
- trace: 위반 request의 `traceId`로 호출 chain 확인. token claim의 `tenant_id`와 요청 path의 `tenant_id` 비교
- 가능한 원인:
- account takeover (계정 탈취) → 즉시 session revoke + 비밀번호 reset 요구
- client bug (잘못된 tenant id 전송) → product team에 통보
- 정상 admin operation 누락된 capability → entitlement 보정
## 4. Mitigation
- 단기: principal session revoke, source IP rate-limit 강화
- 위반이 client bug면 client patch release 협조
- 장기: tenant boundary 검증 layer를 controller가 아닌 repository 진입점에서 강제 ([[feature-repository-access-permission-contract]])
## 5. Escalation
- 다음 on-call로 page: 보안 incident channel 즉시 page. 5분 내 security on-call 응답 없으면 CISO escalation
- legal/compliance 통보 필요 여부 판단 (개인정보 noted시)
## 6. Recovery / Verification
- 회복 확인 metric: `AUTHZ_TENANT_MISMATCH` 건수 정상 baseline 복귀
- post-incident:
- account takeover면 forensics 수행 + audit log 보존
- cross-tenant 검증 unit test 추가
- 위반 패턴 detection rule 영구화
## 7. Related
- error-codes.yaml rows: `AUTHZ_INSUFFICIENT_PERMISSION`, `AUTHZ_TENANT_MISMATCH`
- metrics.yaml: `authz_denied_total`, `authz_principal_tenant_distribution`
- 관련 branch: [[feature-tenant-context-policy]], [[feature-repository-access-permission-contract]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 tenant 모델·capability 정의·security team 연락 체계로 보강 필요.
@@ -0,0 +1,34 @@
---
title: Runbook — AUTHZ_INSUFFICIENT_PERMISSION (권한 부족)
category: AUTHZ
error_codes: [AUTHZ_INSUFFICIENT_PERMISSION]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTHZ_INSUFFICIENT_PERMISSION (`runbook://authz/insufficient-permission`)
## Symptoms
- HTTP 403 with `error.code=AUTHZ_INSUFFICIENT_PERMISSION`
- Valid token but missing required role or permission
## Diagnosis
- Check user's assigned roles in IdP
- Review endpoint's required permission annotation
## Action
- Grant correct role/permission to user
- Verify endpoint permission requirement is correct
## Escalation
- Escalate to access-management team if bulk users affected
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTHZ_TENANT_MISMATCH (테넌트 cross-access 시도)
category: AUTHZ
error_codes: [AUTHZ_TENANT_MISMATCH]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTHZ_TENANT_MISMATCH (`runbook://authz/tenant-mismatch`)
## Symptoms
- HTTP 403 with `error.code=AUTHZ_TENANT_MISMATCH`
- `log_level=ERROR` — cross-tenant access attempt detected
## Diagnosis
- Extract `traceId`, check `X-Tenant-Id` vs token tenant claim
- Determine if this is misconfigured client or intentional attack
## Action
- Block repeat offenders at gateway level
- Alert security team for investigation
## Escalation
- P1 if confirmed malicious cross-tenant access attempt
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — CACHE_STAMPEDE_LOCK_TIMEOUT (캐시 스탬피드 락 타임아웃)
category: TRANSIENT_DEPENDENCY
error_codes: [CACHE_STAMPEDE_LOCK_TIMEOUT]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: CACHE_STAMPEDE_LOCK_TIMEOUT (`runbook://cache/stampede-lock-timeout`)
## Symptoms
- HTTP 503 with `error.code=CACHE_STAMPEDE_LOCK_TIMEOUT`
- Multiple concurrent cache misses on same key; lock contention
## Diagnosis
- Check cache hit ratio metrics
- Identify cache keys with high miss rates
## Action
- Verify stampede lock TTL is configured appropriately
- Pre-warm cache for high-traffic keys on startup
## Escalation
- Escalate if backend load spike accompanies stampede
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — CACHE_UNAVAILABLE (캐시 연결 불가)
category: TRANSIENT_DEPENDENCY
error_codes: [CACHE_UNAVAILABLE]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: CACHE_UNAVAILABLE (`runbook://cache/unavailable`)
## Symptoms
- HTTP 503 with `error.code=CACHE_UNAVAILABLE`
- Redis connection errors in logs
## Diagnosis
- Check Redis cluster health
- Verify network connectivity from app to Redis
## Action
- Check Redis sentinel/cluster status
- Enable cache degradation path if configured for optional caches
## Escalation
- P1 if required cache is down and no degradation path exists
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_DEADLOCK (데드락)
category: CONFLICT
error_codes: [DB_DEADLOCK]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_DEADLOCK (`runbook://db/deadlock`)
## Symptoms
- HTTP 409 with `error.code=DB_DEADLOCK`
- SQLState 40P01 in Postgres logs
## Diagnosis
- Check `pg_locks` and `pg_stat_activity` during deadlock
- Identify conflicting transaction lock order
## Action
- Client should retry (retryable=true)
- Fix lock ordering in code if recurring
## Escalation
- Escalate to DBA if deadlock rate is sustained > 1% of transactions
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_IDLE_IN_TX_TIMEOUT (트랜잭션 idle 타임아웃)
category: TRANSIENT_DEPENDENCY
error_codes: [DB_IDLE_IN_TX_TIMEOUT]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_IDLE_IN_TX_TIMEOUT (`runbook://db/idle-in-tx-timeout`)
## Symptoms
- HTTP 503 with `error.code=DB_IDLE_IN_TX_TIMEOUT`
- SQLState 25P03; transaction held open too long without activity
## Diagnosis
- Check `idle_in_transaction_session_timeout` Postgres setting
- Look for application-level long-running transaction holders
## Action
- Reduce transaction scope in application code
- Verify `spring.jpa.properties.hibernate.connection.timeout` is bounded
## Escalation
- Escalate to DBA if connection pool exhaustion results
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_QUERY_CANCELED (쿼리 취소)
category: TRANSIENT_DEPENDENCY
error_codes: [DB_QUERY_CANCELED]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_QUERY_CANCELED (`runbook://db/query-canceled`)
## Symptoms
- HTTP 503 with `error.code=DB_QUERY_CANCELED`
- SQLState 57014; query exceeds statement timeout
## Diagnosis
- Check `statement_timeout` in Postgres
- Identify slow queries in `pg_stat_statements`
## Action
- Optimize slow query or add index
- Adjust statement timeout if query is legitimately long
## Escalation
- Escalate to DBA for query optimization if recurring
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_SERIALIZATION_FAILURE (직렬화 실패)
category: CONFLICT
error_codes: [DB_SERIALIZATION_FAILURE]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_SERIALIZATION_FAILURE (`runbook://db/serialization-failure`)
## Symptoms
- HTTP 409 with `error.code=DB_SERIALIZATION_FAILURE`
- SQLState 40001; high concurrent transaction contention
## Diagnosis
- Check DB transaction isolation level
- Identify hot rows / hot tables under high concurrency
## Action
- Client should retry with exponential backoff (retryable=true)
- Optimize transaction scope if spike is sustained
## Escalation
- Escalate to DBA if sustained serialization failure rate > 5%
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_UNAVAILABLE (데이터베이스 연결 불가)
category: TRANSIENT_DEPENDENCY
error_codes: [DB_UNAVAILABLE]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_UNAVAILABLE (`runbook://db/unavailable`)
## Symptoms
- HTTP 503 with `error.code=DB_UNAVAILABLE`
- SQLState 08* connection errors in logs
## Diagnosis
- Check DB server health and connection pool exhaustion
- Review network connectivity from app pods to DB
## Action
- Check DB primary health; failover to replica if available
- Drain connection pool and reconnect
## Escalation
- P1: immediate if DB primary is down
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_4XX_CLIENT (업스트림 클라이언트 오류)
category: PERMANENT_DEPENDENCY
error_codes: [DEPENDENCY_4XX_CLIENT]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_4XX_CLIENT (`runbook://dependency/4xx-client`)
## Symptoms
- HTTP 502 with `error.code=DEPENDENCY_4XX_CLIENT`
- Upstream returned 401/403/400 — credential, scope, or request format issue
## Diagnosis
- Check upstream response body in logs for error detail
- Verify API credentials and scopes are valid
## Action
- Rotate credentials if expired
- Fix request format if API contract changed
## Escalation
- Escalate to upstream API owner if contract change is suspected
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_5XX_SERVER (업스트림 서버 오류)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_5XX_SERVER]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_5XX_SERVER (`runbook://dependency/5xx-server`)
## Symptoms
- HTTP 502 with `error.code=DEPENDENCY_5XX_SERVER`
- Upstream returned 5xx; transient server-side failure
## Diagnosis
- Check `dependency_name` tag for which upstream is failing
- Review upstream service status page
## Action
- Client should retry (retryable=true)
- Monitor upstream recovery
## Escalation
- P1 if critical upstream is in sustained 5xx state
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_CIRCUIT_OPEN (서킷 브레이커 개방)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_CIRCUIT_OPEN]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_CIRCUIT_OPEN (`runbook://dependency/circuit-open`)
## Symptoms
- HTTP 503 with `error.code=DEPENDENCY_CIRCUIT_OPEN`
- Circuit breaker (Resilience4j) in OPEN state for a dependency
## Diagnosis
- Check Resilience4j circuit breaker metrics for the dependency
- Check upstream health; circuit opens after failure threshold breached
## Action
- Wait for circuit half-open probe (automatic after wait duration)
- Resolve upstream issue to allow circuit to close
## Escalation
- P1 if circuit remains open > 5 minutes on a critical dependency
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_CONNECT_FAILED (외부 의존성 연결 실패)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_CONNECT_FAILED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_CONNECT_FAILED (`runbook://dependency/connect-failed`)
## Symptoms
- HTTP 503 with `error.code=DEPENDENCY_CONNECT_FAILED`
- TCP connection refused or network unreachable to upstream
## Diagnosis
- Check `dependency_name` tag for which upstream is unreachable
- Verify network path and firewall rules
## Action
- Check upstream service availability
- Verify service discovery / DNS resolution
## Escalation
- P1 if upstream is a critical service dependency
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_DNS_FAILED (DNS 조회 실패)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_DNS_FAILED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_DNS_FAILED (`runbook://dependency/dns-failed`)
## Symptoms
- HTTP 503 with `error.code=DEPENDENCY_DNS_FAILED`
- DNS resolution failure for upstream hostname
## Diagnosis
- Test DNS resolution from app pod: `nslookup <upstream-host>`
- Check cluster DNS (CoreDNS) health
## Action
- Verify upstream hostname configuration
- Check CoreDNS / cluster DNS health
## Escalation
- P1 if cluster DNS is degraded
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_TIMEOUT (외부 의존성 타임아웃)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_TIMEOUT]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_TIMEOUT (`runbook://dependency/timeout`)
## Symptoms
- HTTP 504 with `error.code=DEPENDENCY_TIMEOUT`
- Upstream service did not respond within configured timeout (default: global 10s)
## Diagnosis
- Check `dependency_name` in log for which upstream is timing out
- Review upstream service latency metrics
## Action
- Check upstream service health
- Verify timeout settings match SLA expectations
## Escalation
- P1 if critical upstream is timing out at scale
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+81
View File
@@ -0,0 +1,81 @@
---
title: Runbook — 외부 의존성 unavailable
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_TIMEOUT, DEPENDENCY_CONNECT_FAILED, DEPENDENCY_DNS_FAILED, DEPENDENCY_CIRCUIT_OPEN, DEPENDENCY_5XX_SERVER, CACHE_UNAVAILABLE, DB_UNAVAILABLE]
severity: P1
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: 외부 의존성 unavailable
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `dependency_error_rate_critical` 또는 `circuit_breaker_open`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `dependency_name`, `dependency_kind`(required|optional), `runbook_link`
- 임계:
- P1: required dependency의 error rate > 50% 1분 OR circuit_open state 활성
- P2: optional dependency degraded (fail-open으로 동작 중)
## 2. First Response (5분 이내)
### Step 1 — 확인
1. `dependency_name` 별 status page 확인 (외부 SaaS면 vendor status, internal이면 해당 service dashboard)
2. log query로 실패 패턴 확인: timeout / connect / DNS / 5xx 중 어떤 모드인지
3. runtime-health Dependency Matrix에서 required vs optional 분류 확인
4. circuit breaker state 확인 (Resilience4j metric)
### Step 2 — 임시 격리
- required dep이면 readiness probe로 traffic 차단 (회복 대기) — cascade failure 방지
- optional dep이면 fail-open with degraded mode 확인. degraded banner를 client에 노출
- DNS failure면 resolver/coredns 상태 확인. cache 강제 flush 검토
## 3. Diagnosis
- log query: `{service="app"} | dependency_name="<name>" | stats count by error.code`
- metric panel:
- `resilience4j_circuitbreaker_state{name="<name>"}`
- `resilience4j_retry_calls_total{kind="failed_without_retry"}`
- `hikaricp_connections_active`, `hikaricp_connections_pending` (DB_UNAVAILABLE)
- `http_client_requests_seconds_count{outcome="SERVER_ERROR"}`
- trace: 실패 request의 outbound span에서 timeout/connect/DNS 분류, target endpoint 확인
- 가능한 원인:
- vendor outage → status page 확인, 회복 대기
- 네트워크 문제 (DNS, security group, NAT) → infra team 확인
- connection pool 고갈 (Hikari) → pool size/timeout 점검
- circuit breaker open 후 half-open 전환 실패 → 수동 reset 검토
- retry-storm으로 인한 self-DoS → retry budget 축소
## 4. Mitigation
- 단기: required면 회복 대기 + traffic 차단, optional이면 degraded mode로 유지
- pool 고갈이면 일시 pool size 상향 + leak detection 활성화
- circuit이 stuck이면 수동 reset (`actuator/circuitbreakerevents`)
- 장기: retry budget·timeout·circuit 임계 재조정, fallback path 보강, vendor SLA 재협상
## 5. Escalation
- 다음 on-call로 page: required dep 5분 내 회복 안 되면 외부 dep team 또는 vendor에 page
- 다중 dep 동시 장애면 incident commander 호출 (네트워크 전반 문제 의심)
## 6. Recovery / Verification
- 회복 확인 metric: dependency error rate < 1% 5분 지속, circuit_breaker_state = CLOSED, pool utilization 정상
- post-incident:
- vendor postmortem 요청 (외부 SaaS면)
- timeout/retry/circuit 설정 재검토
- degraded mode가 사용자 경험에 미친 영향 측정
- chaos test에 해당 시나리오 추가
## 7. Related
- error-codes.yaml rows: `DEPENDENCY_TIMEOUT`, `DEPENDENCY_CONNECT_FAILED`, `DEPENDENCY_DNS_FAILED`, `DEPENDENCY_CIRCUIT_OPEN`, `DEPENDENCY_5XX_SERVER`, `CACHE_UNAVAILABLE`, `DB_UNAVAILABLE`
- metrics.yaml: `resilience4j_circuitbreaker_state`, `hikaricp_connections_active`, `http_client_requests_seconds_count`
- 관련 branch: [[feature-outbound-http-client-baseline]], [[feature-persistence-failure-baseline]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 dependency 목록·required/optional 분류·vendor 연락 체계·circuit/timeout 임계로 보강 필요.
@@ -0,0 +1,34 @@
---
title: Runbook — DOWNLOAD_STREAMING_FAILURE (스트리밍 다운로드 실패)
category: TRANSIENT_DEPENDENCY
error_codes: [DOWNLOAD_STREAMING_FAILURE]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DOWNLOAD_STREAMING_FAILURE (`runbook://file/download-streaming-failure`)
## Symptoms
- HTTP 503 with `error.code=DOWNLOAD_STREAMING_FAILURE`
- Streaming response truncated; backpressure or timeout (60s / 100MB limit)
## Diagnosis
- Check streaming response timeout configuration
- Review download size vs 100MB limit
## Action
- Verify storage backend is reachable
- Check for network congestion on download path
## Escalation
- Escalate to infra if storage backend is degraded
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+74
View File
@@ -0,0 +1,74 @@
---
title: Runbook — 5xx Internal error spike
category: INTERNAL
error_codes: [INTERNAL_ERROR, INTERNAL_AUTH_MISCONFIGURATION, JVM_OOM]
severity: P1
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: 5xx Internal error spike
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `http_5xx_error_rate_critical`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `request_id`, `traceId`, `runbook_link`
- 임계: 5xx error rate > 5% 5분 지속 OR > 10% 1분
## 2. First Response (5분 이내)
### Step 1 — 확인
1. 가장 최근 deploy 시각 확인 (CI/CD dashboard, artifact registry digest)
2. JVM metric 확인: heap usage, GC pause, CPU, thread count
3. log에서 실패 request 1건 추출 → `request_id`, `traceId` 확보
4. error.code 분포 확인: `INTERNAL_ERROR` vs `JVM_OOM` vs `INTERNAL_AUTH_MISCONFIGURATION`
### Step 2 — 임시 격리
- 직전 deploy가 의심되면 즉시 rollback (artifact registry에서 직전 image digest pin)
- OOM 패턴이면 affected pod evict → ASG/HPA로 replacement 유도
- LB에서 unhealthy pod 격리 (readiness probe failure 유도)
## 3. Diagnosis
- log query: `{service="app"} | http.status>=500 | stats count by error.code`
- metric panel: `jvm_memory_used_bytes{area="heap"}`, `jvm_gc_pause_seconds`, `process_cpu_seconds_total`, `http_server_requests_seconds_count{status=~"5.."}`
- trace: 실패 request의 `traceId`로 span chain 확인 → stack trace에서 root exception 추출
- heap dump 위치: `/var/tmp/heap/heapdump-<pid>.hprof` (JVM ergonomics: `-XX:MaxRAMPercentage=75 -XX:+HeapDumpOnOutOfMemoryError`)
- 가능한 원인:
- 직전 deploy의 회귀 버그 → rollback
- JVM OOM (메모리 leak 또는 부하 증가) → heap dump 분석
- 외부 의존성 설정 오류 (`INTERNAL_AUTH_MISCONFIGURATION`) → config secret 확인
- thread starvation (pool 고갈) → thread dump (`jstack <pid>`)
## 4. Mitigation
- 단기: 직전 deploy rollback, OOM pod replacement, traffic 일시 감소(scale-out 또는 rate-limit 강화)
- config 오류면 secret/configmap rollback
- 장기: heap dump 기반 leak 수정, capacity planning 재검토
## 5. Escalation
- 다음 on-call로 page: 10분 내 회복 안 되면 incident commander 호출, severity 1 incident 선언
- 데이터 손상 의심되면 DBA team page
## 6. Recovery / Verification
- 회복 확인 metric: 5xx rate < 0.5% 5분 지속, JVM heap usage < 70%, GC pause p99 < 500ms
- post-incident:
- rollback 원인 RCA 작성 (배포 게이트 강화 필요 여부)
- heap dump 분석 결과 공유
- JVM ergonomics(`-XX:MaxRAMPercentage`) 재검토
- rollback 자동화 절차 점검
## 7. Related
- error-codes.yaml rows: `INTERNAL_ERROR`, `INTERNAL_AUTH_MISCONFIGURATION`, `JVM_OOM`
- metrics.yaml: `jvm_memory_used_bytes`, `jvm_gc_pause_seconds`, `http_server_requests_seconds_count`
- 관련 branch: [[feature-operational-error-observability-foundation]], [[feature-container-runtime-contract]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 deploy 파이프라인·heap dump 보관 경로·rollback 자동화 명령으로 보강 필요.
+67
View File
@@ -0,0 +1,67 @@
---
title: Runbook — background job dead letter
category: INTERNAL
error_codes: [JOB_DEAD_LETTER]
severity: P1
owner: oncall
last_updated: 2026-06-13
status: stub
---
# Runbook: background job dead letter (`runbook://job/dead-letter`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `job_dead_letter`
- alert payload 필수 field: `error.code=JOB_DEAD_LETTER`, `job_name`, `correlation_id`, `runbook_link`
- 임계: `job.dlq.total` > 0 for 5m (p1) — retry 소진 후 DLQ 진입은 자동 회복이 없으므로 점검 대상
## 2. First Response (5분 이내)
### Step 1 — 확인
1. ERROR log에서 `JOB_DEAD_LETTER` 라인 확인: `job_name`, 최종 실패 원인 예외, `correlation_id` 추출
2. `job.retry.total{outcome=EXHAUSTED}` 추이로 DLQ 유입 규모 파악
3. DLQ 적재 위치(향후 retry carrier 확정 시 DB 테이블/큐) 확인 — 현재 skeleton은 vocabulary 단계
### Step 2 — 임시 격리
- DLQ는 max attempts(3) 소진의 최종 상태 — 자동 재시도 없음, 수동 개입 필수
- 비즈니스 크리티컬 job이면 §4의 수동 처분(재처리 또는 폐기)을 우선 수행
## 3. Diagnosis
- log query: `{service="app"} | error.code="JOB_DEAD_LETTER" | stats count by job_name`
- metric panel: `job.dlq.total{job_name}`, `job.retry.total{job_name, outcome}`
- 최종 실패 원인 분류:
- poison input(직렬화/계약 위반) → 입력 결함, 재처리해도 실패 — 수정 후 재처리 또는 폐기
- 외부 의존성 장기 outage 중 attempts 소진 → 의존성 회복 후 재처리로 해결 가능
- non-transient error(권한/도메인/스키마)인데 retry된 경우 → 분류기 보강 필요(WAF-REL05-C3: 즉시 DLQ가 정답)
## 4. Mitigation (수동 처분 — 둘 중 하나)
- **재처리 (기본)**: 원인 해소 후 해당 job을 다시 enqueue. 소비자는 멱등(idempotencyKey dedupe) 의무가 있으므로 중복 처리 안전
- **폐기 (영구)**: 작업이 더 이상 유효하지 않으면 DLQ에서 제거. ⚠ 비즈니스 오너 승인 후에만 수행하고 incident 기록에 남김
- 장기: poison input 재발 방지(입력 계약 테스트 보강), non-transient error는 retry 없이 즉시 DLQ로 분류
## 5. Escalation
- 처분 판단(재처리 vs 폐기)이 불가하면 해당 job의 비즈니스 오너에게 escalate
- DLQ 누적이 특정 `job_name`에 집중되면 해당 job 코드 오너에게 page
## 6. Recovery / Verification
- 회복 확인: `job.dlq.total` 증가 멈춤, 재처리분의 소비자 dedupe 동작 확인
- post-incident: DLQ 원인 분류 기록, 같은 원인의 재발 방지 테스트 추가
## 7. Related
- error-codes.yaml rows: `JOB_DEAD_LETTER` (INTERNAL, 500, retryable=false)
- metrics.yaml: `job.dlq.total{job_name}`, `job.retry.total{job_name, outcome=DLQ}`
- 코드: `app-bootstrap` `async/BackgroundJobMetrics`(retry/DLQ vocabulary 기록 seam — D2/D4)
- 관련 runbook: [[job-executor-rejected]], [[job-timeout]], [[outbox-dead-letter]]
- 관련 branch: [[feature-background-job-async-contract]] (D4 retry/DLQ vocabulary SSOT — outbox/outbound가 consume)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. retry carrier(Spring Retry / Resilience4j / 자체) 확정 후 DLQ 저장소·재처리 절차 보강 필요.
+71
View File
@@ -0,0 +1,71 @@
---
title: Runbook — async executor rejected
category: TRANSIENT_DEPENDENCY
error_codes: [JOB_EXECUTOR_REJECTED]
severity: P1
owner: oncall
last_updated: 2026-06-13
status: stub
---
# Runbook: async executor rejected (`runbook://job/executor-rejected`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `executor_rejected`
- alert payload 필수 field: `error.code=JOB_EXECUTOR_REJECTED`, `executor_name`, `policy`, `runbook_link`
- 임계: `executor.rejected.total` > 0 for 1m (p1) — bounded pool이 saturation으로 task를 거부
- 보조 신호: `executor.saturation` gauge > queue capacity의 80% for 5m (p2)
## 2. First Response (5분 이내)
### Step 1 — 확인
1. ERROR log에서 `JOB_EXECUTOR_REJECTED` 라인 확인: `executor_name`, `policy=AbortPolicy`, `queue_size` 추출
2. `executor.saturation` 패널에서 큐 점유율 추이 확인 — 일시적 burst인지 지속 saturation인지 판별
3. 동시 유입 원인 파악: 신규 배포 / 트래픽 spike / 다운스트림 지연으로 worker가 장기 점유되는지
### Step 2 — 임시 격리
- AbortPolicy 거부는 호출부에 `RejectedExecutionException`으로 surface됨 — fire-and-forget `@Async` 호출이면 호출부의 async-exception 처리(log/metric)로 흡수됐는지 확인
- 지속 saturation이면 유입 측(트래픽/스케줄러 빈도)을 우선 감속
## 3. Diagnosis
- log query: `{service="app"} | error.code="JOB_EXECUTOR_REJECTED" | stats count by executor_name`
- metric panel: `executor.saturation{executor_name}`, `executor.rejected.total{executor_name, policy}`
- 가능한 원인 우선순위:
- 다운스트림 의존성 지연 → worker가 반납되지 않아 큐 포화 (가장 흔함)
- 트래픽 spike → 정상 부하 한계 초과
- pool 과소 설정 (`APP_ASYNC_EXECUTOR_*`)
- non-idempotent 작업이 retry로 누적
## 4. Mitigation
- 단기: 유입 감속(상위 rate-limit / 스케줄러 interval 확대) 또는 다운스트림 의존성 회복
- pool 재조정(restart-only): `APP_ASYNC_EXECUTOR_CORE_SIZE` / `APP_ASYNC_EXECUTOR_MAX_SIZE` / `APP_ASYNC_EXECUTOR_QUEUE_CAPACITY`
— ⚠ queue를 무한정 키우지 말 것(unbounded 금지, D7). 부하테스트로 수치 검증 후 변경
- CallerRunsPolicy로의 전환은 use-case 차원의 명시적 결정 필요(request thread latency 침식 — TPE-JDK21-C6)
## 5. Escalation
- 다운스트림 의존성 장애가 근본 원인이면 해당 의존성 오너에게 escalate
- pool 재조정으로도 saturation이 지속되면 용량 계획(capacity planning) 오너에게 page
## 6. Recovery / Verification
- 회복 확인: `executor.rejected.total` 증가 멈춤, `executor.saturation` < 80% 정상화
- 거부된 작업의 재처리 경로(멱등 retry / 다음 스케줄 cycle) 정상 동작 확인
## 7. Related
- error-codes.yaml rows: `JOB_EXECUTOR_REJECTED` (TRANSIENT_DEPENDENCY, 503, retryable=true, retry_after 5s)
- metrics.yaml: `executor.rejected.total{executor_name, policy}`, `executor.saturation{executor_name}`
- 코드: `app-bootstrap` `async/AsyncExecutorConfig`(bounded executor), `async/LoggingAbortPolicy`(reject log+metric), `async/BackgroundJobMetrics`
- env: `APP_ASYNC_EXECUTOR_CORE_SIZE` / `APP_ASYNC_EXECUTOR_MAX_SIZE` / `APP_ASYNC_EXECUTOR_QUEUE_CAPACITY`
- 관련 runbook: [[job-timeout]], [[job-dead-letter]]
- 관련 branch: [[feature-background-job-async-contract]] (D7 saturation policy)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 부하 프로파일·alert 채널·pool 수치 확정 시 보강 필요.
+69
View File
@@ -0,0 +1,69 @@
---
title: Runbook — background job timeout
category: TRANSIENT_DEPENDENCY
error_codes: [JOB_TIMEOUT]
severity: P2
owner: oncall
last_updated: 2026-06-13
status: stub
---
# Runbook: background job timeout (`runbook://job/timeout`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `job_timeout`
- alert payload 필수 field: `error.code=JOB_TIMEOUT`, `job_name`, `correlation_id`, `runbook_link`
- 임계: `job.retry.total{outcome=RETRY}` 급증 또는 graceful-shutdown 중 in-flight job interrupt 발생
- 연관: shutdown phase에서 19s await 초과로 interrupt된 job (D8)
## 2. First Response (10분 이내)
### Step 1 — 확인
1. ERROR log에서 `JOB_TIMEOUT` 라인 확인: `job_name`, 마지막 단계, 소요 시간 추출
2. timeout이 정상 실행 중 발생인지, graceful-shutdown(배포/스케일다운) 중 interrupt인지 구분
3. 해당 job이 멱등(retry-on-next-cycle 안전)인지 확인 — 비멱등이면 §4에서 신중히 처리
### Step 2 — 임시 격리
- shutdown 중 interrupt면: 다음 기동 시 재시도 대상인지(멱등 전제) 확인, 중복 부작용 여부 점검
- 정상 실행 중 timeout이면: 해당 job의 외부 의존성(DB/HTTP) 지연 여부 확인
## 3. Diagnosis
- log query: `{service="app"} | error.code="JOB_TIMEOUT" | stats count by job_name`
- metric panel: `job.retry.total{job_name, outcome}`
- 가능한 원인 우선순위:
- 외부 의존성(DB lock / 느린 HTTP) 지연으로 job p99 상승
- job 작업량 증가로 단일 cycle이 19s 예산 초과 (D8 — interrupt 노출)
- interrupt 미반응 blocking call(JDBC 등) → awaitTermination 초과 (K8S-POD-LC-C2 SIGKILL 경로)
## 4. Mitigation
- 단기: 의존성 회복 / job 입력 배치 크기 축소
- job p99가 구조적으로 19s를 넘으면: 작업을 분할하거나, grace period 연장 검토(parent project 운영 계약 소유자 승인 필요 — OUT_OF_BRANCH_SCOPE)
- 비멱등 job이 재시도로 중복 부작용을 내면 멱등키/dedupe 도입 우선
## 5. Escalation
- 의존성 지연이 근본 원인이면 해당 의존성 오너에게 escalate
- shutdown 예산(20s) vs k8s `terminationGracePeriodSeconds`(30s) 정합 이슈면 플랫폼/런타임 오너에게 escalate
## 6. Recovery / Verification
- 회복 확인: `JOB_TIMEOUT` 신규 발생 멈춤, `job.retry.total{outcome=SUCCESS}` 정상 비율 회복
- 멱등 재시도분의 부작용 중복 없음 확인
## 7. Related
- error-codes.yaml rows: `JOB_TIMEOUT` (TRANSIENT_DEPENDENCY, 500, retryable=true, retry_after 10s)
- metrics.yaml: `job.retry.total{job_name, outcome}`
- 코드: `app-bootstrap` `async/AsyncExecutorConfig`(awaitTermination 19s — D8 graceful shutdown)
- env: `APP_SERVER_SHUTDOWN_TIMEOUT`(owner: feature-env-driven-runtime-configuration D2)
- 관련 runbook: [[job-executor-rejected]], [[job-dead-letter]]
- 관련 branch: [[feature-background-job-async-contract]] (D4 retry / D8 shutdown)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 retry carrier·job p99·shutdown 예산 확정 시 보강 필요.
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — LOCK_ACQUISITION_TIMEOUT (분산 락 획득 타임아웃)
category: CONFLICT
error_codes: [LOCK_ACQUISITION_TIMEOUT]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: LOCK_ACQUISITION_TIMEOUT (`runbook://lock/acquisition-timeout`)
## Symptoms
- HTTP 409 with `error.code=LOCK_ACQUISITION_TIMEOUT`
- Distributed lock wait exceeded configured timeout; high contention on a resource
## Diagnosis
- Check `lock.acquisition` metric for lock name and duration
- Identify lock holders (check DB `integration_lock` table)
## Action
- Client should retry with backoff (retryable=true)
- Optimize critical section holding time if lock contention is systemic
## Escalation
- Escalate if lock holder appears stuck (potential deadlock in distributed lock)
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — ACTUATOR_FORBIDDEN (Actuator 접근 거부)
category: AUTHZ
error_codes: [ACTUATOR_FORBIDDEN]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: ACTUATOR_FORBIDDEN (`runbook://management/actuator-forbidden`)
## Symptoms
- HTTP 403 with `error.code=ACTUATOR_FORBIDDEN`
- Attempt to access restricted actuator endpoint (env/configprops/heapdump/shutdown)
## Diagnosis
- Identify which actuator endpoint was accessed
- Check caller identity (internal tooling vs external)
## Action
- Verify management port is not exposed externally
- For heapdump/threaddump: follow break-glass runbook procedure
## Escalation
- P1 if forbidden actuator access appears to be external attack
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+96
View File
@@ -0,0 +1,96 @@
---
title: Runbook — MIGRATION_FAILED (DB 마이그레이션 실패)
category: INTERNAL
error_codes: [MIGRATION_FAILED]
severity: P1
owner: oncall
last_updated: 2026-07-29
status: active
---
# Runbook: MIGRATION_FAILED (`runbook://migration/failed`)
## Symptoms
- Container exits with code 70 (migration failure exit)
- Structured log with `error.code=MIGRATION_FAILED`, `startup.phase=migration`
- App refuses to start (fail-fast)
- JPA capability adapter refuses activation because its
`capability_schema_registry.lifecycle_state` is not `ACTIVE`
## Diagnosis
1. Stop rollout and keep the failed revision out of readiness. Do not route traffic to a partially
migrated instance.
2. Identify the exact stream from `src/config/jpa/readiness-cards.yaml`. Each stream has an
independent `location` and `history-table`; do not infer ownership from a broad
`classpath:db/migration` scan.
3. From a privileged migration session, capture the stream state before changing anything:
```sql
select installed_rank, version, description, success
from <owned_history_table>
order by installed_rank;
select capability_id, installation_origin, core_epoch, feature_revision, lifecycle_state
from capability_schema_registry
where capability_id = '<card-id>';
```
4. Check whether any owned relation was created without a successful history entry. Compare only
against the owned tables in the reviewed migration; do not drop unrelated relations.
5. Classify the failure:
- lock/statement timeout: remove the blocker or reduce rollout concurrency, then rerun;
- SQL/data precondition: create a new forward migration that makes the precondition explicit;
- checksum mismatch: compare the deployed artifact with the already applied script before
considering repair;
- connection/TLS failure: fix transport or credentials without changing Flyway history.
## Action
1. Prefer forward recovery. Fix the environmental blocker or add a new immutable migration, then
rerun the same owned stream with its exact history table.
2. For an optional stream that never installed successfully, keep the capability marker absent and
the runtime adapter disabled until migration succeeds.
3. After a successful migration, validate:
- the history contains only successful expected versions;
- `core_epoch` and `feature_revision` match the readiness registry;
- the marker is `INSTALLED_INACTIVE`;
- owned objects and constraints exist.
4. Change the marker to `ACTIVE` only after the compatible application revision is deployed and its
readiness check succeeds. Disabling or rolling back application code changes the marker to
`INSTALLED_INACTIVE`; it does not drop history or owned data.
5. Re-run the candidate evidence task before promoting:
```bash
cd src
./gradlew :adapter:outbound:persistence-jpa:verifyJpaCandidateEvidence --console=plain
```
6. Record the failed revision, stream/history table, root cause, recovery migration, elapsed time
and verification artifact in the incident.
Do not:
- edit an already applied migration;
- delete or rewrite Flyway history to make validation green;
- run `flyway repair` before checksum provenance is proven and reviewed;
- use `clean`, destructive rollback, or schema-wide restore as the first response;
- mark a capability `ACTIVE` before its migration and adapter readiness succeed.
If commit outcome was indeterminate during the failure, reconcile by the application
`OperationId`/idempotency reference before retrying business work. Never blind-retry a commit whose
result is unknown.
## Escalation
- P1 immediate: the required application revision cannot become ready.
- Escalate to the database owner before Flyway history repair, destructive DDL, point-in-time
recovery, or primary failover.
- R3 restore/PITR and failover rehearsal requires a target-like backup topology; local
Testcontainers evidence is not a substitute.
---
This runbook is forward-only. The reviewed migration artifact and the per-card evidence manifest
are the audit sources.
+73
View File
@@ -0,0 +1,73 @@
---
title: Runbook — outbox dead letter
category: INTERNAL
error_codes: [OUTBOX_DEAD_LETTER]
severity: P1
owner: oncall
last_updated: 2026-06-11
status: stub
---
# Runbook: outbox dead letter (`runbook://outbox/dead-letter`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `outbox_dead_letter`
- alert payload 필수 field: `error.code=OUTBOX_DEAD_LETTER`, `event_type`, `event_id`, `correlation_id`, `runbook_link`
- 임계: `outbox.publisher.published.total{outcome=DEAD}` > 0 (DEAD 전이는 자동 회복이 없으므로 단건도 점검 대상)
## 2. First Response (5분 이내)
### Step 1 — 확인
1. ERROR log에서 `OUTBOX_DEAD_LETTER` 라인 확인: `event_id`, `event_type`, `correlation_id`, 마지막 실패 원인 예외 추출
2. DB에서 DEAD row 확인: `SELECT * FROM outbox_event WHERE status = 'DEAD' ORDER BY occurred_at;`
3. **차단 영향 파악 (중요)**: strict per-aggregate FIFO 정책상 DEAD row는 같은 `aggregate_id`의 후행 이벤트를 계속 차단함 —
`SELECT count(*) FROM outbox_event b WHERE b.status <> 'PUBLISHED' AND EXISTS (SELECT 1 FROM outbox_event d WHERE d.status='DEAD' AND d.aggregate_id=b.aggregate_id AND d.occurred_at < b.occurred_at);`
### Step 2 — 임시 격리
- DEAD는 max attempts(3) 소진의 최종 상태 — 자동 재시도 없음, 수동 개입 필수
- 차단된 aggregate가 비즈니스 크리티컬하면 아래 §4의 수동 처분(재발행 또는 skip)을 우선 수행
## 3. Diagnosis
- log query: `{service="app"} | error.code="OUTBOX_DEAD_LETTER" | stats count by event_type`
- 마지막 실패 원인 분류:
- poison event (payload 직렬화/계약 위반) → payload 자체 결함, 재발행해도 실패 — 수정 후 재발행 또는 skip
- broker 장기 outage 중 attempts 소진 → broker 회복 후 재발행으로 해결 가능
- 구성 오류 (Kafka disabled 상태에서 producer 활성) → 구성 수정 후 재발행
- 가능한 원인 우선순위: 구성 오류 > broker outage > poison payload
## 4. Mitigation (수동 처분 — 둘 중 하나)
- **재발행 (기본)**: 원인 해소 후 해당 row를 다시 claim 가능 상태로 되돌림 —
`UPDATE outbox_event SET status = 'PENDING', attempt_count = 0, next_attempt_at = now() WHERE event_id = '<id>' AND status = 'DEAD';`
(consumer는 at-least-once + idempotencyKey dedupe 의무가 있으므로 중복 발행은 안전)
- **skip (영구 폐기)**: 이벤트가 더 이상 유효하지 않으면 PUBLISHED로 마킹해 FIFO 차단을 해제 —
`UPDATE outbox_event SET status = 'PUBLISHED' WHERE event_id = '<id>' AND status = 'DEAD';`
⚠️ skip은 다운스트림에 영구 이벤트 갭을 만든다 — 비즈니스 오너 승인 후에만 수행하고 incident 기록에 남김
- 장기: poison event 재발 방지(payload 계약 테스트 보강), DEAD 빈발 event_type의 producer 검증 강화
## 5. Escalation
- 처분 판단(재발행 vs skip)이 불가하면 해당 이벤트의 비즈니스 오너에게 escalate
- DEAD 누적이 특정 event_type에 집중되면 producer 코드 오너에게 page
## 6. Recovery / Verification
- 회복 확인: `SELECT count(*) FROM outbox_event WHERE status='DEAD';` = 0, 차단됐던 aggregate의 후행 이벤트가 PUBLISHED로 전이
- `outbox.publisher.lag` 정상화(< 60s), 재발행분의 consumer dedupe 동작 확인
- post-incident: DEAD 원인 분류 기록, 같은 원인의 재발 방지 테스트 추가
## 7. Related
- error-codes.yaml rows: `OUTBOX_DEAD_LETTER` (INTERNAL, retryable=false)
- metrics.yaml: `outbox.publisher.published.total{outcome=DEAD}`, `outbox.pending.size{status=DEAD}`, `outbox.publisher.lag`
- 코드: `application-core` `PublishPendingOutboxEventsUseCase`(FAILED→DEAD 전이), `adapter-persistence` `outbox/OutboxEventJpaRepository`(FIFO 게이트 — DEAD가 후행 차단)
- 관련 runbook: [[outbox-publish-failed]]
- 관련 branch: [[feature-domain-event-outbox-contract]], [[feature-background-job-async-contract]] (max attempts/DLQ vocabulary SSOT)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 broker·DLQ 토픽·승인 체계 확정 시 보강 필요.
+77
View File
@@ -0,0 +1,77 @@
---
title: Runbook — outbox publish 일시 실패
category: TRANSIENT_DEPENDENCY
error_codes: [OUTBOX_PUBLISH_FAILED]
severity: P2
owner: oncall
last_updated: 2026-06-11
status: stub
---
# Runbook: outbox publish 일시 실패 (`runbook://outbox/publish-failed`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `outbox_publish_failed_rate` 또는 `outbox_publisher_lag`
- alert payload 필수 field: `error.code=OUTBOX_PUBLISH_FAILED`, `event_type`, `correlation_id`, `runbook_link`
- 임계 (metrics.yaml verbatim):
- P2: `outbox.publisher.published.total{outcome=FAILED}` rate > 1% for 10m
- P2: `outbox.publisher.lag` > 60s for 10m / P1: > 300s for 5m
- P2: `outbox.pending.size{status=PENDING}` growing for 10m
## 2. First Response (5분 이내)
### Step 1 — 확인
1. ERROR log에서 `OUTBOX_PUBLISH_FAILED` 라인 확인: `event_type`, `event_id`, `correlation_id`, `attempt_count` 추출
2. broker(기본 Kafka adapter) 상태 확인: `APP_MESSAGING_KAFKA_ENABLED` 값과 broker endpoint 가용성
- Kafka disabled(default) 상태에서 outbox 이벤트가 append 되고 있으면 publish 경로가 `AdapterDisabledException`으로 전부 실패하는 구성 오류 — 이 경우 producer use case 쪽 활성화/구성을 먼저 의심
3. `outbox.pending.size` status 분포 확인 (FAILED 누적 vs PENDING 누적)
### Step 2 — 임시 격리
- 일시 실패는 자동 backoff 재시도(30s × 2^(attempt-1) + jitter, max attempts 3)가 동작 — 즉시 수동 개입 불필요
- broker 장기 다운이면 DEAD 전이 누적 전에 broker 회복을 우선 (max attempts 소진 시 `runbook://outbox/dead-letter`로 이관)
- relay 자체를 멈춰야 하면 `ca-skeleton.outbox.relay-enabled=false`로 스케줄러 비활성 (이벤트는 outbox 테이블에 안전하게 보존됨 — 유실 없음)
## 3. Diagnosis
- log query: `{service="app"} | error.code="OUTBOX_PUBLISH_FAILED" | stats count by event_type`
- metric panel:
- `outbox.publisher.published.total{outcome}` — FAILED 비율
- `outbox.publisher.lag{event_type}` — 최고령 미발행 이벤트 age
- `outbox.pending.size{status}` — 상태별 분포
- DB 확인: `SELECT status, count(*) FROM outbox_event GROUP BY status;`
- 가능한 원인:
- broker outage/네트워크 → broker 측 회복 대기
- Kafka adapter 미구성(enabled인데 brokers 누락은 기동 시 차단됨) / disabled 상태에서 producer 활성화
- poison event (직렬화 불가/payload 계약 위반) → 재시도 무의미, attempts 소진 후 DEAD로 흘러감 (의도된 동작)
- 동일 aggregate head 실패로 후행 이벤트가 FIFO 게이트에 차단되어 lag 증가 (strict per-aggregate FIFO — 설계 의도)
## 4. Mitigation
- 단기: broker 회복 후 backoff 만료 시 자동 재발행 — `outcome=PUBLISHED` 회복 확인
- IN_FLIGHT orphan(claim 후 crash)은 in-flight-timeout(기본 PT5M) 경과 후 자동 재claim — at-least-once이므로 중복 발행 가능, consumer dedupe(idempotencyKey)가 흡수
- 장기: `ca-skeleton.outbox.poll-interval`/`batch-size` 조정, broker 가용성 SLA 점검, 빈발 event_type의 payload 계약 검토
## 5. Escalation
- P1 lag(>300s 5m) 지속 + broker 회복 불가면 broker/infra 팀에 page
- DEAD 전이가 발생하기 시작하면 `runbook://outbox/dead-letter` 절차로 이관
## 6. Recovery / Verification
- 회복 확인 metric: `outcome=FAILED` rate < 1% 10분 지속, `outbox.publisher.lag` < 60s, `outbox.pending.size{status=FAILED}` 감소 추세
- post-incident: 실패 구간의 DEAD row 유무 확인, consumer 측 중복 처리량 확인(dedupe 동작 검증), backoff/attempts 상수 재평가
## 7. Related
- error-codes.yaml rows: `OUTBOX_PUBLISH_FAILED` (TRANSIENT_DEPENDENCY, retryable=true, retry_after 30s)
- metrics.yaml: `outbox.publisher.published.total`, `outbox.publisher.lag`, `outbox.pending.size`
- 코드: `application-core` `PublishPendingOutboxEventsUseCase`(상태머신), `adapter-persistence` `outbox/OutboxEventJpaRepository`(SKIP LOCKED claim + FIFO 게이트), `adapter-outbound` `messaging/outbox/KafkaOutboxMessagePublishAdapter`(fail-closed)
- 관련 runbook: [[outbox-dead-letter]]
- 관련 branch: [[feature-domain-event-outbox-contract]], [[feature-background-job-async-contract]] (retry/DLQ vocabulary SSOT)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 broker 채택·alert 라우팅·대시보드 링크 확정 시 보강 필요.
+73
View File
@@ -0,0 +1,73 @@
---
title: Runbook — Rate limit 초과 spike
category: RATE_LIMIT
error_codes: [RATE_LIMIT_EXCEEDED, IDEMPOTENT_IN_FLIGHT]
severity: P3
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: Rate limit 초과 spike
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `rate_limit_429_high`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `rate_limit_key_type`(ip|principal|tenant), `runbook_link`
- 임계:
- P3: 429 rate > 1% 10분 지속 (일상적 abuse 차단 효과 정상)
- P2 격상: 정상 client(known principal/tenant)에서 spike 또는 spike와 함께 5xx 동반
## 2. First Response (5분 이내)
### Step 1 — 확인
1. rate-limit key 분포 확인: IP/principal/tenant 중 어디서 spike가 발생했는지
- log query: `error.code=RATE_LIMIT_EXCEEDED | stats count by rate_limit_key_type, rate_limit_key`
2. top-N offending key 추출 (상위 10건)
3. 정상 client 식별 — 알려진 partner/internal service면 P2 격상
### Step 2 — 임시 격리
- abuse traffic 패턴이면 WAF/gateway에서 IP block (geo, ASN 단위)
- IDEMPOTENT_IN_FLIGHT 다발이면 client의 retry-storm 의심 → client에 retry-after 협조 요청
## 3. Diagnosis
- log query: `{service="gateway"} | error.code="RATE_LIMIT_EXCEEDED" | stats count by rate_limit_key`
- metric panel: `gateway_rate_limit_dropped_total`, `gateway_rate_limit_bucket_utilization`
- trace: 429 응답의 `Retry-After` 헤더 값, `rate_limit_remaining` header 확인
- 가능한 원인:
- abuse / bot traffic → IP/ASN block
- 정상 client의 traffic 증가 (캠페인, 신규 feature) → limit 일시 상향
- retry-storm (client backoff 미적용) → client에 idempotency-key + exponential backoff 권고
- limit 설정 오류 (잘못된 정량 threshold) → config rollback
## 4. Mitigation
- 단기: abuse면 IP/ASN block, 정상 client면 해당 key의 limit 일시 상향(예: 2x, 1시간 TTL)
- IDEMPOTENT_IN_FLIGHT 다발: idempotency-key 정책 점검, client 협조 요청
- 장기: limit 정책을 tenant tier별 차등으로 재설계, abuse pattern detection 자동화
## 5. Escalation
- 다음 on-call로 page: 30분 내 정상 client 회복 안 되면 product team 통보
- 정상 client에 SLO 위반 가능성 있으면 CSM/계정담당 통보
## 6. Recovery / Verification
- 회복 확인 metric: 429 rate < 0.5% 10분 지속, 정상 client의 success rate 정상화
- post-incident:
- 일시 상향한 limit 원복 (TTL 만료 확인)
- abuse pattern을 detection rule에 영구 등록
- retry-storm이면 client SDK 가이드 보완
## 7. Related
- error-codes.yaml rows: `RATE_LIMIT_EXCEEDED`, `IDEMPOTENT_IN_FLIGHT`
- metrics.yaml: `gateway_rate_limit_dropped_total`, `gateway_rate_limit_bucket_utilization`
- 관련 branch: [[feature-rate-limit-idempotency-contract]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 gateway 제품(NGINX/Envoy/Kong 등)·tenant tier 정책·WAF 연동 절차로 보강 필요.
+229
View File
@@ -0,0 +1,229 @@
---
title: Runbook — Redis capability incident
category: TRANSIENT_DEPENDENCY
error_codes: []
severity: P1
owner: oncall
last_updated: 2026-07-29
status: active
---
# Runbook: Redis capability incident (`runbook://redis/capability-incident`)
이 runbook은 Redis 전체를 하나의 상태로 취급하지 않는다. 먼저 영향받은 capability와 role을
식별한다.
| Role | Capability | 기본 안전 결정 |
| --- | --- | --- |
| `CACHE` | cache, cache refresh soft lease | source fallback 예산 안에서 degraded serving 허용 |
| `COORDINATION` | edge rate limit, request-replay idempotency, efficiency lease | 새 mutation/claim을 fail closed하고 결과 불확실성을 보존 |
| `SESSION` | Redis session | 인증을 fail open하지 않고 재인증 또는 503으로 전환 |
Redis liveness 실패만으로 pod를 재시작하지 않는다. 재시작 폭주는 reconnect와 source fallback
부하를 키울 수 있다.
## Detection
- readiness detail에서 affected role과 `required` 여부를 확인한다. endpoint, key, token, secret
reference는 detail에 포함되면 안 된다.
- semantic reason을 구분한다: read/write failure, program ACL denial, unsupported server
version, program failure, command saturation, recent command failure, probe-in-progress,
stale observation, closed route, command unavailable. `semanticObservedAt`,
`semanticAgeMillis`, `semanticStale`를 함께 확인한다. PING 성공만으로 role이 ready라는 뜻은
아니다.
- `evictionValidation=CONFIGURED_EXPECTATION_ONLY`
`externalEvictionAttestation=INCOMPLETE`는 effective server policy가 증명되지 않았다는
뜻이다. 이를 정상 attestation으로 해석하지 않는다.
- `redis.capability.operations.total``redis.capability.duration.seconds`에서 affected
capability/role/operation의 실제 반환 outcome을 확인한다. mutation의
`certainty=indeterminate`는 timeout이나 연결 끊김을 미실행 증거로 바꾸지 않는다.
- `redis.capability.admission.rejected.total`에서 `rejected_saturated`
`rejected_closed`를 구분하고, `redis.capability.inflight.total`의 같은 role에 대해 현재 0이
아닌 state를 확인한다. in-flight gauge는 bounded command count이며 byte 수나 queue depth가
아니다.
- `redis.capability.readiness.total`은 현재 상태 gauge가 아니라 exact sanitized
`RoleHealth` 관측 횟수다. 최신 health detail의 state/reason/requirement와 함께 해석한다.
optional cache의 degraded serving과 required coordination/session의 fail-closed 결정을
같은 availability 의미로 합치지 않는다.
- 종료 시 `redis.capability.lifecycle.drain.total`에서 `drained`,
`forced_after_timeout`, `interrupted`를 구분한다. repeated close는 새 drain을 시작하거나
중복 관측을 만들지 않는다.
- reconnect, cache source-load, session repository error 지표의 변화를 함께 본다.
- Redis server 측에서는 memory/eviction, rejected clients, replication link/lag,
persistence error, Cluster coverage를 operator dashboard에서 확인한다.
- `NOSCRIPT`, result-schema mismatch, ACL denial, TLS/auth failure, OOM, timeout을 서로 다른
incident category로 분류한다. timeout은 command 미실행 증거가 아니다.
### Observability and lifecycle boundaries
- 여섯 `redis.capability.*` meter의 tag는 닫힌 enum에서만 생성된다. key, subject, session id,
token, endpoint, exception text, script/SHA, value 같은 identity/wire material을 metric이나
ticket에 복사하지 않는다.
- semantic operation 계측은 logical provider가 실제로 반환한 hit/miss/denied/conflict/
unavailable/indeterminate 결과를 기록한다. cache의 `stale`/`skipped`, session의
`tombstoned`/`absolute_expired`도 정상 hit/miss와 분리한다. meter registry, classifier,
monotonic ticker 장애는 command 결과나 원래 exception instance를 바꾸지 않는다.
- route 응답이 설정된 byte/collection bound를 넘으면 동일 logical operation을
`unavailable`로 종료한다. GET/read-only 응답은 `not_applied`, mutation VALUE/MULTI 응답은
서버 실행 여부를 되돌릴 수 없으므로 `indeterminate`다. 앞선 `success` 표본과 이 실패를 두
operation으로 합산하지 않는다.
- Spring 종료의 dependency order는 invalidation subscription 같은 capability dependent를 먼저
닫고, capability bean을 닫은 다음 canonical registry가 router admission을 닫아 in-flight를
bounded drain하고 마지막에 runtime을 닫는 순서다. 종료 중 새 command를 허용하거나 drain
timeout 뒤 무기한 기다리지 않는다.
- 현재 composition에는 active Redis scheduler나 dormant credential-rotation coordinator가 없다.
존재하지 않는 lifecycle coordinator를 복구 절차에서 찾거나 수동 호출하지 않는다.
- 이 meter와 단일-process lifecycle test는 Sentinel/Cluster failover, TLS/ACL 배포 적합성,
k3s multi-node, L1/L2 분산 일관성, distributed session 동작의 qualification 증거가 아니다.
해당 label은 별도 topology/conformance lane의 실제 증거가 있어야 한다.
## Immediate mitigation
1. 새 배포나 credential/program 전환 직후라면 해당 rollout을 중지한다. 이미 실행된 mutation을
무조건 재시도하지 않는다.
2. optional cache만 영향을 받으면 source bulkhead와 stale/source fallback 예산을 확인한 뒤
degraded serving을 유지한다. source가 포화되면 cache miss를 더 많은 source 요청으로
증폭시키지 않는다.
3. rate limit이 불확실하면 정책에 정의된 fail-closed 또는 bounded local-emergency만 사용한다.
local provider를 조용한 primary fallback으로 바꾸지 않는다.
4. idempotency claim/complete 응답이 유실됐으면 같은 operation token으로 inspect/reconcile한다.
record를 삭제하거나 새 owner를 추측하지 않는다.
5. lease 결과가 불확실하면 소유권이 있다고 가정하지 않는다. fencing 없는 efficiency lease를
correctness lock으로 승격하지 않는다.
6. session repository 장애에서는 기존 요청을 인증된 것으로 간주하지 않는다. fail closed 또는
재인증으로 전환하고 JWT와 Redis Session filter를 동시에 활성화하지 않는다.
## Diagnosis
### Connectivity, TLS, ACL
- 배포 설정이 올바른 role을 참조하고 TLS, hostname verification, explicit trust bundle, named ACL
user를 사용하는지 확인한다.
- runtime identity로 `CONFIG`, `KEYS`, `FLUSH*`, arbitrary program deployment를 시도하지
않는다. Catalog digest로 닫힌 recovery 외 ACL 점검은 별도 operator/deployer identity의
`ACL DRYRUN` 또는 동등한 관리 절차로 수행한다.
- runtime readiness identity에는 bounded probe namespace `~ca-health:*`, SET/GET/DEL,
PING/EVALSHA와 catalog recovery에 필요한 SCRIPT LOAD, 그리고 선택 capability manifest의 exact
command set이 필요하다. broad `~*`/`+@all`로 장애를 우회하지 않는다.
- readiness probe는 5초 TTL의 opaque key만 사용한다. `ca-health:*` key가 5초를 넘겨 남는다면
cleanup/expiry 이상으로 분류하되 key나 value를 ticket/log에 복사하지 않는다.
- 기본 semantic cadence는 minimum interval 5초, maximum staleness 15초다. refresh follower는
blocking하지 않는다. maximum staleness를 넘은 관측을 backend 정상으로 해석하지 말고,
probe 부하를 줄이기 위해 interval을 1초 미만으로 낮추지 않는다.
- optional CACHE의 typed temporary connect/PING outage만 dormant degraded startup과
health-triggered reconnect를 허용한다. reconnect 후보는 full semantic qualification 뒤에만
설치된다. auth/TLS/material/version/ACL/schema mismatch를 transient로 재분류하거나 required
role에 같은 fallback을 적용하지 않는다.
- credential rotation 중이라면 new credential 검증, traffic switch, old connection drain,
old credential revoke 순서를 확인한다. secret 값은 ticket, log, shell history에 복사하지 않는다.
### Program or schema
- checked-in program manifest digest와 배포 artifact digest를 대조한다.
- `semantic-capability-acl-v1` contract와 Redis minimum 7.2를 확인한다. 이 프로그램은
Redis Lua API의 `redis.acl_check_cmd`로 선택 capability의 exact command/key 권한을
비변경 방식으로 검사하고 `redis.REDIS_VERSION_NUM`의 explicit >=7.2 gate를 먼저 적용한다.
두 API는 7.0부터 존재하지만 repository support policy minimum은 7.2다.
- `NOSCRIPT`는 bounded `SCRIPT LOAD -> digest verify -> EVALSHA` recovery가 수행됐는지 확인한다.
arbitrary `EVAL`로 우회하지 않는다.
- result-schema/key/codec future version은 장애가 아니라 호환성 위반으로 분류하고 writer rollout을
중지한다.
- `BUSY` 또는 slow program이면 affected capability admission을 줄이고 isolated environment에서만
재현한다. shared Redis에 장시간 script를 추가 실행하지 않는다.
### Memory and eviction
- `CACHE` 배포와 `COORDINATION`/`SESSION` 배포가 물리적으로 분리됐는지 확인한다.
- correctness role에서 eviction이 관측되면 P1이다. 새 write를 중지하고 record loss를 전제로
idempotency/session reconciliation 또는 재인증 범위를 산정한다.
- noeviction OOM은 성공으로 변환하지 않는다. cache write는 degraded/indeterminate, coordination
mutation은 unavailable/indeterminate로 유지한다.
- big key를 찾을 때 production request path에서 `KEYS`나 unbounded collection read를 사용하지
않는다. 승인된 operator job의 bounded `SCAN`/sampling을 사용한다.
### Topology and persistence
- 현재 구현 후보 card의 promotion topology는 readiness registry의 `selected-topology`가 정본이다.
이는 selection 또는 R2 qualification을 뜻하지 않는다. Sentinel/Cluster evidence가 없는
상태에서 standalone 증거를 HA 증거로 재사용하지 않는다.
- Cluster same-slot semantic probe는 해당 hash slot owner 한 노드만 검증한다. 이를 cluster-wide
또는 failover target version/ACL/program 증거로 해석하지 말고, promotion 전에 모든 target을
별도 conformance lane으로 검증한다.
- failover 뒤에는 in-flight mutation의 certainty, primary role, program availability, replication
offset/lag, persistence status를 각각 확인한다.
- restore 후 session/idempotency/lease record를 자동으로 신뢰하지 않는다. security epoch,
tombstone, durable receipt/fencing high-watermark가 필요한 capability는 별도 reconciliation을
수행한다.
### Sentinel failover
1. affected role의 semantic readiness가 unavailable인지 확인하고 단순 PING success로 정상 판정하지
않는다. required coordination/session은 새 mutation admission을 닫는다.
2. 세 Sentinel 중 응답 수와 같은 master에 동의한 수를 확인한다. 2-of-3 동의 전에는 임의 endpoint,
최초 응답 또는 DNS 추측으로 data runtime을 바꾸지 않는다.
3. Sentinel discovery credential/CA와 Redis data credential/CA가 분리되어 있는지 확인한다.
장애 우회를 위해 trust-all, hostname verification off, plaintext 또는 broad ACL을 열지 않는다.
4. election, discovered primary qualification, new runtime install, old runtime admission close/drain의
순서를 확인한다. old runtime을 강제로 닫아야 했다면 그 시점의 mutation을 성공/미실행으로
추정하지 않는다.
5. response-only cut, timeout, disconnect가 있었던 rate/idempotency/session mutation은
`INDETERMINATE`를 보존한다. rate evaluation replay, 같은 idempotency/session operation token의
inspect/reconcile 또는 재인증을 사용하고 blind retry하지 않는다.
6. semantic readiness 복구 전에는 traffic을 정상화하지 않는다. 복구 뒤 old primary의 replica
재합류, replication lag/acknowledgement, program digest, actor runtime generation을 확인한다.
Sentinel은 asynchronous replication의 zero-data-loss나 strong consistency를 보장하지 않는다.
`min-replicas-to-write`, lag bound, replica acknowledgement가 설정돼도 acknowledgement 결과가
불명확한 mutation은 여전히 `INDETERMINATE`다.
`min-replicas-to-write 1` + `min-replicas-max-lag 1`은 선택이 아니라 **필수**다. 미설정 시
promotion 중 교체된 구 primary가 계속 `+OK`를 반환하고 그 write는 resync에서 폐기된다. 7.4
레인 실측: 승격 후 강등까지 11초, 그 사이 **2,086건이 acknowledge된 뒤 소실**, 실패한 명령은
1건. 클라이언트는 이를 감지할 수단이 없다 — 서버가 응답했으므로 driver·SDK·호출자 모두
정상 성공으로 기록한다. 설정 후 동일 promotion에서 소실 1건, 나머지 2,020건은 `NOREPLICAS`
명시 거부됐다. 근거: `docs/redis/operations.md`, `LiveRedisSentinelPromotionTest`.
### Disposable Multipass k3s qualification safety
qualification lab은 host k3s incident 조치 도구가 아니다. VM exact allowlist는
`ca-redis-lab-server`, `ca-redis-lab-agent-1`, `ca-redis-lab-agent-2`이며 전용 kubeconfig와
`ca-redis-lab` context만 사용한다.
- 시작 전 host context/API/node/CIDR/NodePort와 Multipass inventory fingerprint를 기록한다.
- lab pod/service CIDR `10.52.0.0/16`, `10.53.0.0/16`이 host와 겹치면 생성하지 않는다.
- default kubeconfig를 merge/overwrite하거나 host context에 write command를 실행하지 않는다.
- cleanup은 exact 세 VM만 대상으로 한다. global `multipass purge`, wildcard delete를 사용하지
않는다.
- 성공/실패 뒤 postflight fingerprint와 VM resource 0을 확인한다. local retain-on-failure가
명시적으로 활성화됐으면 보존 이유와 exact inventory를 기록하며 CI에서는 보존하지 않는다.
- 이 한 물리 host의 3 VM 결과를 k3s control-plane HA, physical host/AZ failure 또는
multi-region 증거로 승격하지 않는다.
## Recovery and verification
1. affected role의 connection/auth/TLS와 `ca-health:` SET/GET/cleanup probe가 정상인지
확인한다. probe 잔여 key가 있으면 최대 TTL 5초 뒤 소멸하는지도 확인한다.
2. 선택 capability의 대표 program digest/result schema, semantic ACL contract와 Redis minimum
version 7.2를 재확인한다.
3. capability별 smoke를 수행한다: cache generation guarded write, rate evaluation replay,
idempotency same-operation inspect, lease stale-owner reject, session create/read/logout.
4. queue saturation, indeterminate outcome, source fallback, re-auth 지표가 incident 전 범위로
돌아온 뒤에만 rollout을 재개한다.
5. `CONFIGURED_EXPECTATION_ONLY`인 eviction은 operator/deployer identity의 외부 conformance
job 또는 서명 attestation으로 effective policy를 별도 검증한다. runtime user에 CONFIG/ACL
권한을 추가하지 않는다.
6. production label을 변경하기 전 repository readiness task를 실행한다. Sentinel/Cluster task가
zero-evidence로 실패한다면 topology를 낮춰 표기하거나 실제 evidence를 먼저 추가한다.
7. Sentinel qualification에서는 actual image ID/digest와 fault/election/runtime-swap/readiness
timeline, capability certainty, teardown 결과가 sanitizer/reconciler를 통과했는지 확인한다.
clean committed source와 실제 remote CI가 없으면 `implemented-candidate`,
`releaseQualification=NOT_CLAIMED`를 유지한다.
## Escalation
- `COORDINATION` 또는 `SESSION` required role이 5분 이상 unavailable이면 P1로 Redis/platform,
application on-call을 동시에 호출한다.
- data loss, stale session resurrection, conflicting idempotency completion, duplicate correctness
side effect가 의심되면 security/business owner까지 즉시 확대한다.
- 한 물리 host의 VM 세 개 또는 standalone container 결과를 AZ/host failure 증거로 해석하지
않는다. 그 증거가 필요한 release는 별도 disposable multi-node qualification을 요구한다.
+36
View File
@@ -0,0 +1,36 @@
---
title: Runbook — JVM_OOM (JVM OutOfMemoryError)
category: INTERNAL
error_codes: [JVM_OOM]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: JVM_OOM (`runbook://runtime/jvm-oom`)
## Symptoms
- Container exits with code 137 (ExitOnOutOfMemoryError triggered)
- Structured log entry with `error.code=JVM_OOM` before exit
## Diagnosis
- Check heap dump if `-XX:HeapDumpOnOutOfMemoryError` is configured
- Review memory usage trends before crash
- Check for memory leaks: large cache growth, unbounded lists, session accumulation
## Action
- Restart container immediately (k8s will auto-restart with liveness probe)
- If recurring: increase heap `-Xmx` or fix memory leak
## Escalation
- P1: immediate if multiple pods crashing simultaneously
- Page SRE / infra team for heap analysis
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+35
View File
@@ -0,0 +1,35 @@
---
title: Runbook — PROFILE_MISMATCH (프로파일 불일치)
category: INTERNAL
error_codes: [PROFILE_MISMATCH]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: PROFILE_MISMATCH (`runbook://startup/profile-mismatch`)
## Symptoms
- Container exits with code 71 (profile mismatch exit)
- Structured log with `error.code=PROFILE_MISMATCH`, `startup.phase=profile-check`
- Production profile active with local-only settings enabled
## Diagnosis
- Check active Spring profiles (`spring.profiles.active`)
- Identify which local-only setting is incorrectly enabled in prod profile
## Action
- Remove local-only setting from production deployment config
- Ensure prod profile does not inherit local/dev profile settings
## Escalation
- P1: security risk if local settings expose debug endpoints in production
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — REQUIRED_ADAPTER_DISABLED (필수 어댑터 비활성화)
category: INTERNAL
error_codes: [REQUIRED_ADAPTER_DISABLED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: REQUIRED_ADAPTER_DISABLED (`runbook://startup/required-adapter-disabled`)
## Symptoms
- Container exits with code 72 (required adapter disabled exit)
- Structured log with `error.code=REQUIRED_ADAPTER_DISABLED`, `startup.phase=adapter-check`
## Diagnosis
- Identify which adapter is disabled but required
- Check adapter enable flags in environment config
## Action
- Enable required adapter in deployment configuration
- If adapter is intentionally disabled, update the required/optional designation
## Escalation
- P1: app cannot start; coordinate with deployment team
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — STARTUP_VALIDATION_FAILED (환경 변수 검증 실패)
category: INTERNAL
error_codes: [STARTUP_VALIDATION_FAILED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: STARTUP_VALIDATION_FAILED (`runbook://startup/validation-failed`)
## Symptoms
- Container exits with code 78 (env validation failure exit)
- Structured log with `error.code=STARTUP_VALIDATION_FAILED`, `startup.phase=env-validation`
## Diagnosis
- Check which required env variable is missing or malformed
- Review container environment and secrets injection
## Action
- Supply missing environment variables to deployment
- Verify secrets are correctly mounted / injected
## Escalation
- P1: app cannot start; coordinate with deployment/secrets team
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+44
View File
@@ -0,0 +1,44 @@
---
title: Runbook — <TITLE>
category: <CATEGORY>
error_codes: [<ERROR_CODE_1>, <ERROR_CODE_2>]
severity: <P1|P2|P3>
owner: oncall
last_updated: <YYYY-MM-DD>
status: <stub|active>
---
# Runbook: <TITLE> (`runbook://<area>/<scenario>`)
## Symptoms
- What observable signals trigger this runbook?
- Alert name, metric thresholds, log patterns
## Diagnosis
- Step-by-step diagnostic commands and queries
- Log queries (Loki/CloudWatch)
- Metric panels to check
- Trace investigation approach
## Action
- Immediate mitigation steps
- Configuration changes
- Manual intervention procedures
## Escalation
- Conditions for severity upgrade (e.g., P2 → P1)
- Who to page and when
- Fallback procedures if on-call cannot resolve
---
> **Note**: This is the canonical runbook template.
> Copy this file, rename it to match the `runbook://area/scenario` pattern (→ `area-scenario.md`),
> fill in the frontmatter fields, replace section bodies with operational content,
> then set `status: active`. `LEGACY_STUB_DEBT` in `RunbookCoverageContractTest` is temporary
> containment for existing debt only; do not add a new stub there. Complete the runbook or adopt
> the future owned, expiring debt ledger.