// 이 파일이 하는 일: "로깅·모니터링·알림 설계" 코스 — 서비스를 이미 만들어 본 사람이
// "무엇이 느린가"와 "왜 느린가"를 나눠 답하는 관측 가능성(observability)을 설계하도록,
// 로그·메트릭·트레이스 3기둥에서 출발해 대시보드·알림·SLO까지 7개 섹션으로 안내하는
// 정적 학습 페이지. 프레이밍은 "돌아가는 서비스"에서 "지켜볼 수 있는 서비스"로의 승격이다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 예제 코드 상수들 ──

const CODE_PILLARS = `장애가 났다. 사용자가 "느려요"라고 한다. 이제 세 가지 질문이 생긴다.

메트릭(Metrics)      "무엇이, 얼마나 이상한가?"
                     숫자의 시계열. p99 지연이 2초를 넘었다 — 사실을 알려 준다.
                     싸고 빠르다. 대신 '왜'는 모른다.

트레이스(Traces)     "그 요청은 어디서 시간을 썼는가?"
                     요청 하나의 여정. 게이트웨이 30ms → auth 40ms → DB 1800ms.
                     느린 '구간'을 짚어 준다. 대신 전체 추세는 못 본다.

로그(Logs)           "그 순간, 정확히 무슨 일이 있었는가?"
                     사건의 기록. "커넥션 풀 고갈, 대기 큐 47건" — 맥락과 원인.
                     가장 풍부하다. 대신 가장 비싸고, 검색 없이는 바다다.

셋은 경쟁자가 아니라 역할 분담입니다.
메트릭이 "이상하다"고 알리면 → 트레이스로 "어느 구간"인지 좁히고
→ 로그로 "왜"를 확정한다. 이 흐름이 관측의 기본 동선이에요.`;

const CODE_LOG_LEVELS = `// 로그 레벨 — "누가, 언제 이 로그를 읽게 되는가"로 정한다
ERROR  지금 사람이 봐야 하는 실패. 요청이 죽었거나 데이터가 어긋났다.
       → 알림과 연결될 수 있는 레벨. 남발하면 알림이 무뎌진다.
WARN   아직 실패는 아니지만 곧 문제가 될 조짐.
       재시도로 살아난 호출, 임계치 80%에 닿은 커넥션 풀.
INFO   서비스의 '큰 사건' 서사. 기동/종료, 주문 체결, 로그인 성공.
       운영 중 기본으로 켜 두는 레벨.
DEBUG  개발자가 원인을 좁힐 때만 켜는 현미경. 파라미터, 분기 판단.
       운영에서 상시로 켜면 비용과 소음만 남는다.

흔한 실패: 모든 catch에서 ERROR를 찍는 것.
사용자가 비밀번호를 틀린 건 '시스템의 에러'가 아니라 '정상 동작'이다.
그건 INFO(혹은 WARN)이고, ERROR는 "코드가 예상 못 한 상황"에 아껴 쓴다.`;

const CODE_JSON_LOG = `// 구조화 로그 — grep이 아니라 '질의'가 가능한 로그
// 나쁜 예: 사람은 읽어도 기계가 못 자른다
2026-07-20 14:02:11 ERROR 주문 실패 user=482 order=9912 결제사 타임아웃

// 좋은 예: JSON 한 줄 — 필드별로 검색·집계·필터가 된다
{
  "ts": "2026-07-20T14:02:11.482+09:00",
  "level": "ERROR",
  "service": "service-order",
  "traceId": "b3f1a9c2e4d85f70",     // ← 상관관계 ID: 이 값 하나로
  "userId": 482,                      //    게이트웨이·주문·결제 로그를
  "orderId": 9912,                    //    한 사건으로 꿴다
  "event": "payment.timeout",
  "elapsedMs": 3012,
  "message": "결제사 응답 3초 초과로 주문 중단"
}

// traceId가 없으면: 서비스 5개의 로그를 시각으로 어림잡아 이어 붙인다(고통).
// traceId가 있으면: traceId=b3f1... 검색 한 번으로 요청의 전 생애가 나온다.`;

const CODE_MICROMETER = `// Micrometer — 스프링 코드에 심는 '계측기'. Prometheus가 주기적으로 걷어 간다
// build.gradle / pom.xml에 micrometer-registry-prometheus 추가 후:

@RestController
public class OrderController {
  private final MeterRegistry registry;

  @PostMapping("/api/orders")
  public OrderResponse create(@RequestBody OrderRequest req) {
    return Timer.builder("http.order.create")     // 메트릭 이름
        .tag("result", "success")                  // 라벨(주의: 섹션 본문 참고)
        .register(registry)
        .record(() -> orderService.create(req));   // 소요 시간을 자동 기록
  }
}

// 스프링 액추에이터를 켜면 /actuator/prometheus 엔드포인트가 열리고,
// Prometheus 서버가 15초마다 이 페이지를 '긁어(scrape)' 시계열 DB에 쌓는다.
// 우리가 푸시하는 게 아니라 Prometheus가 당겨 가는(pull) 구조라는 점이 핵심.`;

const CODE_RED_USE = `RED — '요청을 받는 것들'(API, 서비스)을 볼 때
  Rate      초당 요청 수          "트래픽이 얼마나 오나"
  Errors    실패한 요청 수(비율)   "그중 몇이 죽나"
  Duration  요청 처리 시간 분포    "얼마나 걸리나" (평균 말고 p95·p99!)

USE — '자원'(CPU, 디스크, 커넥션 풀)을 볼 때
  Utilization  사용률              "얼마나 바쁜가"
  Saturation   포화도(대기 큐)      "일이 얼마나 밀려 있나"
  Errors       자원 수준의 에러     "디스크 I/O 에러 같은 것"

왜 평균이 아니라 p99인가?
요청 100개 중 99개가 10ms, 1개가 5초라면 평균은 60ms — 멀쩡해 보인다.
그러나 그 1%의 사용자는 5초를 기다렸다. p99는 "가장 불행한 1%가
겪는 경험"을 숫자로 드러낸다. 사용자 체감은 평균이 아니라 꼬리에 산다.`;

const CODE_TRACE = `// 분산 추적 — 헤더로 '이야기의 실'을 넘겨주는 원리
// 게이트웨이가 요청을 받으면 traceId를 만들어 붙이고, 하류로 전파한다.

클라이언트 → 게이트웨이         traceparent: 00-b3f1a9c2...-01  (새로 발급)
게이트웨이 → service-auth       traceparent: 00-b3f1a9c2...-02  (같은 traceId,
게이트웨이 → service-order      traceparent: 00-b3f1a9c2...-03   새 spanId)
service-order → PostgreSQL      (DB 쿼리도 하나의 스팬으로 기록)

수집기(예: Tempo, Zipkin)가 스팬들을 traceId로 모아 붙이면:

[게이트웨이 12ms]
  └─ [auth: 토큰 검증 8ms]
  └─ [order: 주문 생성 1840ms]        ← 범인은 여기
       └─ [DB: INSERT 대기 1790ms]    ← 그중에서도 여기
       └─ [Kafka: 발행 6ms]

스프링에선 Micrometer Tracing이 이 전파를 대신해 준다 —
RestClient·Kafka 헤더에 traceparent를 자동으로 실어 보낸다.
직접 스레드를 만들거나 헤더를 손으로 조립할 때만 전파가 '끊긴다'.`;

const CODE_ALERT_RULE = `# Prometheus 알림 규칙 초안 — "p99가 임계를 '지속해서' 넘을 때만"
groups:
  - name: order-api
    rules:
      - alert: OrderApiP99High
        # 최근 5분 창에서 주문 API p99가 1.5초 초과
        expr: |
          histogram_quantile(0.99,
            rate(http_server_requests_seconds_bucket[5m])
          ) > 1.5
        for: 10m          # ← 10분간 '계속' 참이어야 발화. 스파이크 무시
        labels:
          severity: page   # 사람을 깨우는 등급
        annotations:
          summary: "주문 API p99 1.5s 초과 10분 지속"
          runbook: "wiki/runbook/order-latency"  # 받은 사람이 볼 첫 문서

# 포인트 세 가지:
# 1) 임계치(1.5s)는 SLO에서 역산한다. 감이 아니라 약속에서 나온 숫자.
# 2) for: 10m — '지속시간' 조건이 노이즈의 절반을 걸러 낸다.
# 3) runbook 링크 — 새벽 3시의 나는 낮의 나보다 훨씬 멍청하다.`;

const CODE_SLO = `SLI (Indicator)  측정하는 지표 그 자체
                 예: "주문 API 요청 중 500ms 안에 성공한 비율"

SLO (Objective)  그 지표에 대한 우리의 약속
                 예: "30일 창에서 99.9% 이상"

에러 버짓        100% - SLO = 우리가 '써도 되는' 실패량
                 99.9%라면 30일 중 약 43분의 장애가 허용치.

에러 버짓의 진짜 쓸모는 의사결정입니다.
  버짓이 넉넉하다  → 배포 자주 하고, 실험하고, 기능에 투자
  버짓이 바닥이다  → 신규 배포를 늦추고, 안정화 작업에 투자
"더 안정적으로"라는 구호가 아니라, 숫자가 우선순위를 정해 준다.

100%를 목표로 하지 않는 이유: 마지막 9 하나를 더할 때마다
비용은 10배가 되는데, 사용자는 그 차이를 대부분 느끼지 못한다.`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: '3기둥' },
  { n: 2, label: '로그 설계' },
  { n: 3, label: '메트릭' },
  { n: 4, label: '분산 추적' },
  { n: 5, label: '대시보드·감사' },
  { n: 6, label: '알림 설계' },
  { n: 7, label: 'SLO와 배움' },
];

export default function Lv3ObservabilityPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 고급 과정</div>
        <h1>로깅·모니터링·알림 설계</h1>
        <p>
          서비스는 만들었어요. 그런데 새벽 2시에 "느려요"라는 제보가 오면, 여러분은
          어디부터 봅니까? <strong>관측 가능성(observability)</strong>은 그 순간을 위해
          미리 깔아 두는 감각기관이에요. <strong>'무엇이 느린가'</strong>(메트릭)와{' '}
          <strong>'왜 느린가'</strong>(트레이스·로그)를 나눠 답할 수 있게 — 로그·메트릭·
          트레이스 3기둥부터 알림과 SLO까지, 지켜볼 수 있는 서비스의 설계를 익혀 봅시다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 60분</span>
          <span className="chip">실습: RED 메트릭 + p99 알림 규칙 초안</span>
          <span className="chip">선수 지식: HTTP·서비스 구조·운영 경험</span>
        </div>
      </div>

      {/* 섹션 바로가기 */}
      <nav className="pill-nav">
        {SECTIONS.map((s) => (
          <a key={s.n} href={`#sec-${s.n}`}>
            <b>{s.n}</b>
            {s.label}
          </a>
        ))}
      </nav>

      <Section n={1} title="관측 가능성 3기둥" sub="로그·메트릭·트레이스의 역할 분담">
        <p>
          "모니터링 붙였어요"라고 말할 때, 그 안에는 사실 성격이 전혀 다른 세 가지 데이터가
          섞여 있어요. <strong>로그</strong>(사건의 기록), <strong>메트릭</strong>(숫자의
          시계열), <strong>트레이스</strong>(요청 하나의 여정). 이 셋을 뭉뚱그리면 "다 쌓고는
          있는데 장애 때 아무것도 못 찾는" 상태가 됩니다. 먼저 역할부터 갈라 봅시다.
        </p>
        <Code>{CODE_PILLARS}</Code>
        <p>
          핵심 구도는 이거예요. <strong>메트릭은 '무엇이 느린가'에 답하고, 트레이스와 로그는
          '왜 느린가'에 답합니다.</strong> 메트릭만 있으면 이상한 건 아는데 원인을 못 찾고,
          로그만 있으면 원인은 있는데 이상한 줄을 늦게 알아요. 장애 대응의 동선 —
          알림(메트릭) → 구간 좁히기(트레이스) → 원인 확정(로그) — 이 세 기둥을 순서대로
          타고 내려가는 길입니다.
        </p>
        <div className="tip">
          <b>관측은 기능이지, 나중 일이 아니다</b> "일단 기능부터 만들고 모니터링은 나중에"는
          "일단 배부터 만들고 계기판은 나중에"와 같은 말이에요. 코드를 쓰는 그 순간이
          "이 함수가 실패하면 뭘 알고 싶지?"를 가장 잘 아는 순간입니다. 관측 코드는
          기능 코드와 <strong>같은 PR</strong>에 들어가는 게 원칙이에요.
        </div>
      </Section>

      <Section n={2} title="로그를 설계하다" sub="레벨·구조화·상관관계 ID — 그리고 남기면 안 되는 것">
        <p>
          로그는 아무 데나 <span className="icode">log.info("여기 옴")</span>을 뿌리는 게
          아니라 <strong>설계 대상</strong>이에요. 첫 번째 설계는 <strong>레벨</strong>입니다.
          기준은 간단해요 — "누가, 언제 이 로그를 읽게 되는가."
        </p>
        <Code>{CODE_LOG_LEVELS}</Code>
        <p>
          두 번째 설계는 <strong>구조화</strong>예요. 사람이 읽을 문장 대신{' '}
          <strong>기계가 자를 수 있는 JSON 한 줄</strong>로 남기면, 로그가 '읽는 것'에서
          '질의하는 것'으로 바뀝니다. 그리고 그 JSON에 반드시 실어야 할 필드가{' '}
          <strong>상관관계 ID(traceId)</strong> — 여러 서비스에 흩어진 로그를 한 사건으로
          꿰는 실이에요(섹션 4에서 이 실이 어떻게 전파되는지 봅니다).
        </p>
        <Code>{CODE_JSON_LOG}</Code>
        <div className="tip">
          <b>절대 남기면 안 되는 것</b> 비밀번호, 액세스/리프레시 <strong>토큰</strong>,
          카드번호, 주민번호, API 키. 로그는 DB보다 접근 통제가 느슨한 곳(수집기, 대시보드,
          복사된 텍스트)으로 흘러 다녀요. <span className="icode">Authorization</span> 헤더나
          요청 바디를 <strong>통째로 찍는 습관</strong>이 가장 흔한 유출 경로입니다.
          남겨야 한다면 마스킹(<span className="icode">user****@...</span>)하거나 식별자
          (userId)만 남기세요. "로그에 찍는 순간, 그 값은 더 이상 비밀이 아니다"가 기준이에요.
        </div>
      </Section>

      <Section n={3} title="메트릭 수집" sub="Micrometer → Prometheus, 그리고 RED·USE">
        <p>
          메트릭은 <strong>숫자의 시계열</strong>이에요. "지금 초당 요청이 몇인가", "p99가
          몇 ms인가"를 15초 간격 같은 주기로 계속 쌓습니다. 스프링 생태계의 표준 조합은{' '}
          <strong>Micrometer</strong>(코드에 심는 계측 API)와 <strong>Prometheus</strong>
          (긁어 가서 쌓고 질의하는 시계열 DB)예요.
        </p>
        <Code>{CODE_MICROMETER}</Code>
        <p>
          그럼 <strong>무엇을</strong> 잴 것인가? 막막할 때 꺼내 쓰는 두 개의 방법론이
          있습니다. 요청을 받는 것들엔 <strong>RED</strong>, 자원엔 <strong>USE</strong>.
        </p>
        <Code>{CODE_RED_USE}</Code>
        <div className="tip">
          <b>라벨 카디널리티 폭발</b> 메트릭의 라벨(tag)은 값의 종류마다 <strong>별도의
          시계열</strong>을 만들어요. <span className="icode">tag("route", "/api/orders")</span>
          처럼 종류가 유한한 값은 좋지만, <span className="icode">tag("userId", ...)</span>처럼
          <strong> 무한히 늘어나는 값</strong>을 라벨로 쓰면 사용자 수만큼 시계열이 생겨
          Prometheus 메모리가 터집니다. 규칙: 라벨엔 '분류'(라우트, 상태코드, 결과)를,
          '개체'(사용자 ID, 주문 ID)는 로그와 트레이스에. 개체를 좇는 건 메트릭의 일이 아니에요.
        </div>
      </Section>

      <Section n={4} title="분산 추적" sub="요청 하나가 여러 서비스를 흐를 때, 그 흐름을 잇기">
        <p>
          마이크로서비스에서 요청 하나는 <strong>게이트웨이 → 인증 → 도메인 서비스 → DB</strong>
          처럼 여러 프로세스를 건너다녀요. 각 서비스의 로그는 자기 구간만 알죠. "전체에서
          2초가 걸렸는데, 그 2초는 누구 몫인가?"에 답하는 게 <strong>분산 추적</strong>입니다.
        </p>
        <p>
          원리는 소박해요. 요청이 처음 들어올 때 <strong>traceId</strong>를 하나 발급하고,
          하류 서비스를 부를 때마다 <strong>HTTP 헤더로 그 ID를 넘겨줍니다</strong>. 각
          서비스는 자기가 쓴 시간을 <strong>스팬(span)</strong>이라는 단위로 기록하면서 같은
          traceId를 달아 두고요. 나중에 수집기가 같은 traceId의 스팬들을 모아 붙이면, 요청
          하나의 여정이 폭포수 그림으로 복원됩니다.
        </p>
        <Code>{CODE_TRACE}</Code>
        <p>
          이 그림의 힘은 <strong>"느린 구간을 짚는"</strong> 데 있어요. 위 예시에서 전체
          1.8초의 범인은 auth도 Kafka도 아니고 <strong>DB INSERT 대기</strong>라는 게 한눈에
          보이죠. 메트릭이 "주문 API가 느리다"까지 데려다주면, 트레이스가 "그중 DB 구간이다"
          까지 데려다줍니다. 남은 '왜'(커넥션 풀? 락 경합?)는 그 시각·그 traceId의 로그가
          마무리하고요.
        </p>
        <div className="tip">
          <b>전파가 끊기는 자리</b> 프레임워크가 헤더를 자동으로 실어 주는 경로(RestClient,
          Kafka 템플릿)는 공짜지만, <strong>직접 만든 스레드</strong>나 수동으로 조립한 HTTP
          호출에선 traceId가 조용히 끊겨요. "트레이스가 게이트웨이에서 뚝 끊긴다"면 십중팔구
          누군가 손으로 비동기 처리를 짠 자리입니다. 끊긴 곳이 곧 계측의 사각지대예요.
        </div>
      </Section>

      <Section n={5} title="대시보드와 감사 이벤트" sub="한 화면의 계기판, 그리고 비즈니스도 관측 대상">
        <p>
          쌓기만 한 메트릭은 아직 정보가 아니에요. <strong>Grafana</strong> 같은 도구로
          "서비스 하나당 한 화면"의 계기판을 만듭니다. 구성은 섹션 3의 방법론을 그대로
          옮기면 됩니다 — 맨 윗줄에 <strong>RED</strong>(요청·에러율·p99), 그 아래{' '}
          <strong>USE</strong>(CPU·메모리·커넥션 풀 포화도), 마지막 줄에 그 서비스 고유의
          지표. 장애 때 이 화면 하나로 "무엇이 이상한가"의 1차 판단이 끝나는 게 목표예요.
        </p>
        <p>
          여기서 시야를 한 칸 넓힙시다. 관측 대상은 시스템만이 아니라{' '}
          <strong>비즈니스</strong>이기도 해요. "분당 주문 수", "로그인 성공률", "결제 전환율"
          같은 지표는 시스템 메트릭이 멀쩡해도 <strong>비즈니스가 죽어 있는</strong> 장애
          (예: 결제 버튼이 프론트에서 안 보이는 버그)를 잡아 줍니다. CPU는 한가한데 매출이
          0인 새벽 — 시스템 대시보드만 보던 팀은 아침까지 모릅니다.
        </p>
        <p>
          우리 트레이딩 플랫폼에도 이미 이 구조의 씨앗이 있어요. 각 서비스의{' '}
          <span className="icode">AuditEventPublisher</span>가 "누가 언제 무엇을 했나"를
          Kafka의 <span className="icode">audit.events</span> 토픽으로 발행하면, log 서비스의{' '}
          <span className="icode">AuditEventConsumer</span>가 받아서 쌓아 두죠. 이건 감사
          (audit)용 기록이지만, 관점을 바꾸면 <strong>비즈니스 이벤트의 스트림</strong>이기도
          합니다 — "시간당 로그인 이벤트 수"를 세면 그대로 비즈니스 지표가 돼요.
        </p>
        <div className="tip">
          <b>감사 로그와 앱 로그는 다르다</b> 앱 로그는 디버깅용이라 유실돼도 아프고 말지만,
          감사 이벤트는 "누가 무엇을 했는가"의 <strong>증거</strong>라 유실되면 안 돼요.
          그래서 우리도 파일 로그가 아니라 <strong>Kafka라는 내구성 있는 통로</strong>로
          따로 흘립니다. 같은 '기록'이어도 요구사항이 다르면 배관을 달리 깐다 — 이게 설계예요.
        </div>
      </Section>

      <Section n={6} title="알림 설계" sub="새벽에 사람을 깨울 자격이 있는 신호만">
        <p>
          알림은 관측의 꽃이자, 가장 망치기 쉬운 부분이에요. 첫 번째 원칙:{' '}
          <strong>증상 기반(symptom-based)으로 깨우고, 원인 기반(cause-based)은 참고로
          돌린다.</strong> 증상은 "사용자가 겪는 것"(에러율 상승, p99 초과)이고, 원인은
          "그럴 수도 있는 내부 상태"(CPU 80%, 디스크 70%)예요. CPU가 90%여도 사용자가
          멀쩡하면 새벽에 깨울 일이 아니고, CPU가 20%여도 에러율이 10%면 당장 깨워야 합니다.
        </p>
        <p>
          두 번째 원칙: <strong>임계치에 지속시간을 곱해라.</strong> 순간 스파이크는 대부분
          스스로 지나갑니다. "p99가 1.5초를 <strong>10분간 계속</strong> 넘을 때"처럼
          지속 조건을 달면, 알림의 절반을 차지하는 노이즈가 걸러져요.
        </p>
        <Code>{CODE_ALERT_RULE}</Code>
        <p>
          그리고 <strong>안티패턴</strong>들 — 온콜을 갈아 넣는 알림의 전형이에요.{' '}
          <strong>① 조치 없는 알림</strong>: 받아도 할 일이 없는 알림은 다음번에 무시되고,
          그 무시가 진짜 장애를 삼킵니다. <strong>② 중복 폭풍</strong>: DB 하나가 죽었는데
          그 위의 서비스 6개가 각자 울면, 정작 원인(DB) 알림이 묻혀요. <strong>③ 임계치
          방치</strong>: 트래픽이 두 배가 됐는데 알림 기준이 작년 그대로면, 매일 울리는
          양치기 소년이 됩니다.
        </p>
        <div className="tip">
          <b>알림의 품질 지표</b> "울린 알림 중 실제로 조치가 필요했던 비율"을 주기적으로
          세어 보세요. 이 비율이 낮다는 건 팀이 알림을 <strong>불신하도록 훈련되고 있다</strong>는
          뜻이에요. 알림 하나를 지우는 일은 하나를 추가하는 일만큼 가치 있습니다.
        </div>
      </Section>

      <Section n={7} title="장애를 배움으로" sub="SLI·SLO·에러 버짓, 그리고 포스트모템 루프">
        <p>
          마지막 기둥은 문화예요. "얼마나 안정적이어야 하는가"를 감이 아니라 숫자로 합의하는
          도구가 <strong>SLI·SLO·에러 버짓</strong>입니다.
        </p>
        <Code>{CODE_SLO}</Code>
        <p>
          SLO가 있으면 섹션 6의 임계치가 <strong>역산</strong>됩니다. "30일 99.9%"를
          약속했다면, 에러 버짓이 얼마나 빠르게 타고 있는지(burn rate)가 알림 기준이 되죠.
          감으로 정한 "p99 1.5초"가 아니라, <strong>약속에서 도출된 숫자</strong>가 새벽에
          사람을 깨우는 겁니다.
        </p>
        <p>
          그리고 장애가 났다면 — 그건 관측 체계를 업그레이드할 기회예요.{' '}
          <strong>포스트모템(사후 분석)</strong>은 "누구 잘못인가"가 아니라{' '}
          <strong>"시스템의 어디가 약했고, 다음엔 어떻게 더 빨리 알아챌 것인가"</strong>를
          묻는 문서입니다. 비난 없는(blameless) 회고여야 사람들이 사실을 숨기지 않아요.
        </p>
        <p>
          포스트모템의 고정 질문 하나를 추천할게요 — <strong>"우리는 무엇을 관측하지 않고
          있었나?"</strong> 알림이 늦게 온 장애라면 지속시간·임계치를 튜닝하고, 알림 자체가
          없던 장애라면 새 SLI를 추가하고, 로그가 없어 원인을 못 찾았다면 그 자리에 로그를
          심습니다. 이렇게 <strong>장애 → 관측 보강 → 다음 장애를 더 빨리 감지</strong>하는
          루프가 돌기 시작하면, 팀의 관측 체계는 장애를 겪을수록 강해져요.
        </p>
        <div className="tip">
          <b>실습 과제</b> 여러분 서비스의 핵심 API 하나를 골라 ① Micrometer로{' '}
          <strong>RED 세 지표</strong>를 심고, ② "p99가 몇 초를 몇 분간 넘으면, 누구에게,
          어떤 런북 링크와 함께" 알림이 가는지 <strong>알림 규칙 초안</strong>을 섹션 6의
          YAML 형태로 써 보세요. 임계치의 근거(왜 그 숫자인가)를 한 줄 주석으로 달 수
          있다면, 이 코스는 통과입니다.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🔭 여기까지 왔다면</h3>
          <p className="muted">
            이제 "모니터링 있어요?"라는 질문에 층위를 나눠 답할 수 있게 됐어요. 메트릭으로{' '}
            <strong>무엇이 느린지</strong> 알아채고, 트레이스로 구간을 좁히고, 로그로{' '}
            <strong>왜 느린지</strong>를 확정한다 — 그리고 그 위에 증상 기반 알림과 SLO라는
            약속을 얹는다. 그런데 관측이 잘 될수록 눈에 밟히는 순간이 하나 있죠. 바로{' '}
            <strong>배포하는 순간</strong>이에요. 새 버전을 내보낼 때마다 에러율 그래프가
            움찔한다면, 다음 과제는 배포 자체를 안전하게 만드는 것. 다음{' '}
            <Link to="/learn/lv3-zero-downtime-deploy"><strong>무중단 배포와 롤백</strong></Link>{' '}
            코스에서, 오늘 만든 계기판을 보며 트래픽을 옮기는 법을 이어서 익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
