MINERVA Phase C-10 — 운영 인프라 (Capacity·SLO·Incident·Toil·DR)

C37 조직 설계 위에 SRE 관점의 운영 — 부하 관리·SLO·incident·toil 감소·재해 복구의 5축

C37이 사람·팀 구조였다면, C38은 그 팀이 운영하는 인프라 자체. 본 편은 SRE 5축 (Capacity·SLO/SLI·Incident·Toil·Disaster Recovery), 부하 관리(autoscale·rate limit·backpressure), Incident 4 등급(P1~P4) 대응 절차, Post-mortem 패턴, Toil 감소 자동화, Chaos Engineering, RTO·RPO·DR drill, 24/7 on-call 운영 디테일, MINERVA 적용을 정리한다.

Agent
저자

Kwangmin Kim

공개

2026년 05월 06일

1 SRE 5축

1. Capacity Planning   - 미래 부하·peak·burst 대비
2. SLO·SLI            - 목표·측정·burn rate
3. Incident Response   - 등급별 대응 절차·escalation
4. Toil Reduction      - 반복 수작업 자동화
5. Disaster Recovery   - 큰 장애·데이터 손실 복구

각 축이 다른 시간 척도와 다른 사람 부담: - Capacity·DR: 분기 단위·드물게 큰 작업 - SLO·Toil: 매일·매주 점진 개선 - Incident: 즉각 대응·후 회고

C25 실행 제어·C34 관측성이 toolkit 측면. 본 편은 그것을 운영 절차로 묶는 측면.

2 SLO vs SLA vs SLI

용어 혼용이 많아 명확히:

용어 정의
SLI (Indicator) 측정 지표 p95 latency·success rate·thumbs_up rate
SLO (Objective) 내부 목표 p95 latency ≤ 3s (99% of 30d)
SLA (Agreement) 외부 계약·법적 의무 “99.9% uptime, 위반 시 환불”
SLI < SLO ≤ SLA
↑      ↑     ↑
실측   내부   외부 계약

내부 SLO가 외부 SLA보다 strict — 외부 계약 위반 전에 내부 알림 시간 확보.

# config/slo.yaml
sli:
  - p95_latency_ms
  - success_rate
  - thumbs_up_rate

slos:
  - sli: p95_latency_ms
    target: 3000
    window: 30d

  - sli: success_rate
    target: 99.9             # SLA가 99.5라면 SLO를 더 strict로
    window: 30d

slas_external:
  - "99.5% availability monthly"
  - "p95 < 5s"

3 Capacity Planning

미래 부하 예측 → 인프라 사이즈 결정.

# scripts/capacity_plan.py
def project_load(growth_rate: float = 1.5, months: int = 6) -> dict:
    current = current_metrics()
    return {
        f"month_{i}": {
            "qps": current["qps"] * (growth_rate ** (i/12)),
            "tokens_per_sec": current["tokens"] * (growth_rate ** (i/12)),
            "concurrent_users": current["users"] * (growth_rate ** (i/12)),
        }
        for i in range(1, months + 1)
    }

3.1 Peak·Burst 분리

Average Load    : 100 qps (일반)
Peak Hour       : 300 qps (매일 오후 2~4시)
Daily Burst     : 800 qps (분기 발표 후 30분)
Disaster Burst  : 3000 qps (외부 사건 — incident 보고)

각 시나리오별 다른 인프라 전략: - Average: 평균 사이즈 - Peak: 시간별 auto-scale - Daily Burst: queue + backpressure 우선 - Disaster Burst: degraded 모드 (LLM 호출 줄이고 cache 응답 우선)

3.2 한도 결정

# config/capacity.yaml
infrastructure:
  llm_concurrent:
    soft_limit: 100         # 일반 운영
    hard_limit: 200         # 비상
    hard_limit_alert: warn  # 100% 가까이면 알림

  vector_search_qps:
    soft: 500
    hard: 1500

  postgres_connections:
    pool: 50
    max: 100

scaling_targets:
  cpu_utilization: 70       # 평균 CPU 70%면 scale-up
  request_queue_depth: 100

C25 Bulkhead 패턴이 인프라 차원 — 의존별 pool로 격리.

4 부하 관리

4.1 Auto-scaling

# Kubernetes HPA 예시
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  minReplicas: 3
  maxReplicas: 30
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: llm_request_queue_depth
        target:
          type: AverageValue
          averageValue: "10"

CPU만 보면 LLM-heavy workload에서 부정확 — request queue depth 같은 application-level 메트릭 추가.

4.2 Rate Limiting

# app/ops/rate_limit.py
import redis

class TokenBucket:
    """per-user·per-tenant rate limit."""

    def __init__(self, redis_client, key: str, capacity: int, refill_per_sec: float):
        self.redis = redis_client
        self.key = key
        self.capacity = capacity
        self.refill = refill_per_sec

    async def consume(self, n: int = 1) -> bool:
        # Lua script for atomic operation
        result = await self.redis.eval(LUA_SCRIPT, 1, self.key, self.capacity, self.refill, n)
        return result == 1


# 사용
async def handle_query(query: Query):
    user_bucket = TokenBucket(redis, f"rl:user:{query.user_id}", capacity=60, refill_per_sec=1)
    if not await user_bucket.consume():
        raise RateLimitExceeded()
    ...

4.3 Backpressure

# 큐 깊이가 임계 초과면 새 요청 거부
async def check_backpressure():
    queue_depth = await redis.llen("query_queue")
    if queue_depth > MAX_QUEUE_DEPTH:
        return False
    return True


@app.post("/run")
async def run(query: Query):
    if not await check_backpressure():
        raise HTTPException(503, "system overloaded — try again")
    ...

빠른 503보다 무한 대기가 사용자 경험 더 나쁨. Backpressure는 빠른 실패 원칙.

5 Incident Response

5.1 4 등급

등급 정의 응답 시간 알림
P1 전체 down·데이터 누출·SLA 직접 위반 즉시 (< 15분) 모든 on-call page + lead 호출
P2 한 도메인 down·심각한 품질 회귀 1시간 내 Domain on-call + Platform
P3 부분 영향·SLA 임박 4시간 내 Slack channel
P4 운영 부담·미세 회귀 영업일 Ticket·daily 회의

5.2 Incident Lifecycle

[탐지]
  ├── 자동 alert (SLO burn rate)
  ├── 사용자 신고 (support ticket)
  └── 사람 운영 (점검 중 발견)
       ↓
[Acknowledge] (on-call이 받음)
       ↓
[Triage]
  ├── 등급 결정 (P1~P4)
  ├── Incident commander 지정
  └── war room (P1·P2)
       ↓
[Mitigate] (사용자 영향 줄이기)
  ├── Kill Switch (C25)
  ├── Rollback (C26)
  ├── Traffic shift (Bandit·canary 강제)
  └── Degraded mode
       ↓
[Resolve] (근본 원인 해결)
       ↓
[Post-mortem] (5 영업일 내 작성)
       ↓
[Action Items] (회귀 방지)

5.3 Incident Commander

P1·P2 incident마다 한 사람이 IC — 결정 속도 + 명확한 책임:

# Slack에 자동 IC 지정
@incident_handler.on_p1
def assign_ic(incident):
    on_call = get_current_on_call()
    notify(channel="#incident",
           message=f"P1 incident — IC: @{on_call.name}, war room: {incident.url}")

IC 책임: - 결정 (rollback·kill switch·escalate) - 의사소통 (status update 30분마다) - 사후 — post-mortem 리드

6 Post-mortem

# Post-mortem — Incident I-2026-04-23

## 요약
2026-04-23 14:30~15:42, 모든 사용자에 응답 latency p95 > 30s. 약 3,200 query 영향.

## 시간선
- 14:32 SLO burn rate alert (p95 latency)
- 14:35 IC @kim 지정, war room 시작
- 14:40 root cause 추정: vector store CPU saturation
- 14:45 Mitigate — vector store auto-scale max 강제
- 15:10 latency 정상 복귀
- 15:42 incident close

## Root Cause
Vector store가 5x 트래픽 spike에 auto-scale 따라가지 못함. 새 collection 도입 후 인덱스가 4× 커진 것이 underlying.

## What Went Well
- Alert가 4분 만에 트리거
- IC 결정 빠름 (15분 내 mitigate)

## What Went Wrong
- Auto-scale max가 너무 낮음 (사전 capacity planning 누락)
- 새 collection 영향 사전 추정 안 함

## Action Items
- [ ] Vector store auto-scale max 4×로 (kim, 2 days)
- [ ] 새 collection 도입 절차에 capacity 영향 추정 추가 (han, 1 week)
- [ ] Vector store CPU saturation alert 별도 (lee, 3 days)

## Blameless 원칙
사람이 아닌 시스템·절차 문제로 분석. "왜 이 사람이 안 했는가" 대신 "왜 시스템이 알람을 울리지 않았는가".

Blameless culture가 핵심 — 비난하면 다음 incident 시 정보 숨김. 사실 발견·시스템 개선만 집중.

7 Toil Reduction

Toil = 반복적·자동화 가능한 수작업. 운영팀의 50%+가 toil이면 위험 신호.

# scripts/toil_audit.py
TOIL_INDICATORS = [
    "수동 service restart",
    "수동 cache invalidation",
    "수동 user permission 변경",
    "수동 stale data cleanup",
    "수동 SSL cert 갱신",
    ...
]


def quarterly_toil_audit():
    on_call_logs = load_oncall_logs(period="quarter")
    toil_pct = count_matching(on_call_logs, TOIL_INDICATORS) / len(on_call_logs)
    if toil_pct > 0.5:
        alert("toil ratio > 50% — 자동화 우선순위")
    return toil_pct

7.1 Toil → 자동화 사례

수작업 자동화
수동 stale doc cleanup C31 retention cron
수동 user permission 변경 RBAC + IdP sync
수동 cache invalidation event-driven (source webhook)
수동 service restart health check + auto restart
수동 cost spike 점검 cost burn rate alert
수동 incident IC 지정 rotation + auto Slack assign

분기마다 — 가장 부담 큰 toil 1~3개 자동화.

8 Chaos Engineering

운영 중 의도적 실패 주입 — 시스템이 회복하는지 검증.

# scripts/chaos.py — staging 환경
async def chaos_experiments():
    # 1. LLM provider 5분 down
    await inject_failure(target="openai_api", duration_sec=300)
    assert system_falls_back_to_anthropic()

    # 2. Vector store 50% latency 추가
    await inject_latency(target="vector_store", added_ms=2000, duration_sec=600)
    assert circuit_breaker_trips()

    # 3. 한 collection 전체 down
    await disable_collection("finance_internal")
    assert search_returns_fallback()

분기마다 staging에서 — 운영 전 회귀 catch. Production은 careful (사용자 영향 큼).

9 Disaster Recovery

9.1 RTO·RPO

RTO (Recovery Time Objective)   - 얼마나 빨리 복구해야 하는가
RPO (Recovery Point Objective)  - 얼마나 많은 데이터 손실을 허용하는가
# config/dr.yaml
rto:
  primary_service: 1h         # 1시간 내 복구
  analytics: 24h
  audit_log: 0                 # 손실 X (영구)

rpo:
  user_data: 1h               # 1시간 내 데이터까지 복구
  embeddings: 24h              # 1일치 손실 허용 (재인덱싱 가능)
  audit_log: 0                 # 손실 X

9.2 Backup 전략

데이터 빈도 보존
Postgres 매시간 + 매일 full 30일
Vector store 매일 30일
Audit log 실시간 (S3 versioning) 영구
Config git (영구) 영구
Secrets encrypted backup 30일

9.3 DR Drill

분기마다 — 백업에서 새 환경으로 복원 시도:

# scripts/dr_drill.py
def quarterly_dr_drill():
    # 1. 별도 region·account에 새 인프라 provisioning
    new_env = provision_new_environment()

    # 2. 최근 backup으로 복원
    restore_from_backup(new_env, snapshot=latest_backup())

    # 3. 핵심 기능 smoke test
    assert run_smoke_tests(new_env)

    # 4. RTO 측정·RPO 검증
    log_drill_metrics(rto=measure_rto(), rpo=measure_rpo())

Drill 안 하면 — 실제 disaster 시 backup이 손상됐는지 모름.

10 24/7 On-call 디테일

10.1 Rotation Pattern

Follow-the-sun (글로벌)
  US: 09:00-17:00 PT
  EU: 09:00-17:00 CET
  KR: 09:00-17:00 KST
→ 24/7 자연 커버, 야간 page X
Single-region (소규모)
  주중 day shift (팀 내 rotation)
  주말·야간 on-call (1주 한 번 page 가능 1~2회)
→ 비용 적음, 번아웃 위험

10.2 Compensation·Wellness

  • On-call 시간 보상 (overtime·comp time)
  • Page 발생 시 다음날 휴식
  • 분기 on-call 회고 (피로도·문제 패턴)
  • Mental health support

10.3 Handoff

[Daily handoff] (글로벌 rotation 전환 시)
  - 진행 중 incident 인수
  - 알림 임계 변경 사항
  - 주의 영역 (최근 변경된 곳)

[Weekly review]
  - 한 주의 incident 정리
  - false positive alert tuning
  - toil 패턴 식별

11 C25·C34와 결합

본 편 영역 C25·C34에서 토대
Capacity Planning C34 metrics 시계열
SLO·burn rate C34 burn rate 계산
Incident — Mitigate C25 Kill Switch·Circuit Breaker·Rollback
Toil Reduction C34 dashboard 자동화
Chaos Engineering C25 데코레이터로 inject
DR C20 retention·C36 audit log

Phase C-9가 toolkit, Phase C-10이 운영 절차로 묶음.

12 MINERVA 적용

app/ops/
├── capacity.py             # 부하 예측·임계 결정
├── rate_limit.py            # token bucket·per-user
├── backpressure.py          # 큐 깊이·503 빠른 실패
├── incident.py              # commander·triage·status
├── post_mortem.py           # template·5일 내 작성 강제
├── toil.py                  # 운영 작업 분류·자동화 우선순위
├── chaos.py                  # staging chaos experiments
└── dr.py                    # 분기 drill·RTO·RPO 측정

scripts/
├── slo_burn.py              # SLO 계산·alert 발화
├── oncall_handoff.py        # daily handoff 보조
├── incident_war_room.py     # P1·P2 자동 Slack channel·IC
└── dr_drill_runbook.py      # 분기 DR drill 실행

infra/
├── runbooks/                # 절차 문서 (incident별·서비스별)
├── alerts/                   # alert 정의
└── dashboards/               # SRE dashboard

C25·C34·C36 audit 모두 의존.

13 자주 발생하는 함정

13.1 Alert Fatigue

너무 많은 alert → 운영 무시 → P1 놓침.

해법: - alert 개수 < 사람 처리 가능 - 분기마다 alert 정리 — false positive·중복 제거 - burn rate 기반 (메트릭 임계 X)

13.2 Toil 누적

분기 자동화 우선순위 결정 안 함 → toil 80% → 새 기능 0.

해법: - 분기 toil audit 의무 - 매 sprint 자동화 작업 1개 강제 - toil > 50% 시 인력 충원 또는 우선순위 재조정

13.3 MTTR 측정 안 함

incident 발생만 보고 회복 시간 추적 안 함 → 개선 가시화 X.

해법: - 매 incident에 detect·ack·mitigate·resolve 시간 기록 - 분기 MTTR 추세 review - 등급별 MTTR target (P1 < 1h, P2 < 4h)

13.4 Blame Culture

post-mortem이 비난 — 다음 incident 시 정보 숨김.

해법: - 명시적 blameless 정책 - post-mortem template에 “사람” 항목 X (시스템·절차만) - lead가 모범 — 자기 실수도 공개적으로 분석

13.5 DR Drill 미수행

backup 자동화는 됐지만 복원은 한 번도 안 함 → 실제 disaster 시 발견.

해법: - 분기 DR drill 의무 (운영 KPI) - 결과 (RTO·RPO 측정)·문제점 review - 외부 audit (SOC 2)이 강제하는 경우 활용

13.6 On-call 번아웃

작은 팀 24/7 → 야간 page 자주 → 사람 잃음.

해법: - 최소 4명 (1주에 1번 이내) - Follow-the-sun - Page 빈도 → 자동화 우선순위 - 보상·휴식·wellness 제도

13.7 Capacity 사후 대응

부하 spike 후 사후 scaling → 사용자 영향 발생 후.

해법: - 사전 capacity 예측 (분기마다) - 부하 테스트 분기 (load test) - 새 기능 도입 시 영향 추정 의무 (C19 spec)

13.8 Runbook Stale

incident runbook이 1년 전 — 인프라 바뀐 후 안 맞음.

해법: - post-mortem action item에 runbook 갱신 - 분기 runbook audit - 새 incident 시 runbook 따라가는데 안 맞으면 즉시 수정

14 정리

영역 핵심
5축 Capacity·SLO/SLI·Incident·Toil·DR
용어 SLI(측정)·SLO(목표)·SLA(외부 계약)
Capacity average·peak·burst·disaster 시나리오별 전략
Incident P1~P4 등급·IC·blameless post-mortem·5일 내
Toil 분기 audit·자동화 우선순위·50% 임계
Chaos staging·분기·핵심 의존 시뮬레이션
DR RTO·RPO·분기 drill·외부 audit 결합
On-call follow-the-sun·보상·번아웃 방지
함정 alert fatigue·toil 누적·MTTR·blame·drill 미수행·번아웃·capacity 사후·runbook stale

15 응용 분야

시나리오 핵심
신규 기능 도입 시 capacity 영향 C19 spec에 capacity 추정
외부 LLM provider 장애 Circuit Breaker + 자동 fallback (C25)
분기 발표 후 트래픽 spike auto-scale + queue + degraded mode
데이터 누출 의심 P1 + Kill Switch + audit replay
회귀 발견 rollback (C26) + post-mortem
분기 DR 검증 별도 region 복원 + smoke test
Toil 초과 분기 자동화 sprint

16 관련 주제

선행 학습 (선수)

후속 (Phase C-10)

Cross-reference

Subscribe

Enjoy this blog? Get notified of new posts by email: