// 이 파일이 하는 일: "무중단 배포와 롤백" 코스 — 재시작 몇 초가 왜 장애인지에서 출발해,
// 롤링·블루-그린·카나리아 전략 비교, 헬스체크와 트래픽 전환, DB 확장→이행→수축 마이그레이션,
// 롤백 설계와 기능 플래그, 카나리아 자동 관측, 배포 파이프라인 규율까지 7개 섹션으로 안내하는
// 정적 학습 페이지. 프레이밍은 "배포가 무섭지 않아야 자주 배포한다" — 서비스를 만들어 본 수습이
// '내는 것'을 넘어 '되돌릴 수 있게 내는 것'을 설계하도록, 우리 compose 운영 구조에 발을 딛고 간다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

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

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

const CODE_DOWNTIME = `# "잠깐 껐다 켜면 되죠" — 그 잠깐 동안 벌어지는 일 (타임라인)

14:00:00  docker compose up -d --build 실행
14:00:01  구(舊) 컨테이너 중지 → 이 순간부터 요청은 갈 곳이 없다
14:00:01  사용자 A: 주문 버튼 클릭 → 502 Bad Gateway
14:00:03  사용자 B: 대시보드 새로고침 → 연결 실패, SSE 시세 스트림 끊김
14:00:25  새 컨테이너 기동 중 (JVM 워밍업, DB 커넥션 풀 채우는 중)
14:00:40  헬스체크 통과 → 정상 응답 재개

체감: 개발자에겐 "40초 재시작", 사용자에겐 "장 중에 앱이 죽었다".
그리고 이 공포가 쌓이면 → 배포를 미루고 → 한 번에 큰 덩어리를 내고
→ 실패 확률과 원인 추적 난도가 함께 오르는 악순환이 시작됩니다.`;

const CODE_STRATEGIES = `전략        방식                          비용     위험      되돌림 속도
──────────────────────────────────────────────────────────────────
롤링        인스턴스를 하나씩 교체         낮음     중간      느림(다시 롤링)
            (구·신 버전이 잠시 공존)

블루-그린   똑같은 환경 2벌(blue/green)   높음     낮음      매우 빠름
            신버전을 green에 띄우고        (2배)              (스위치만
            검증 후 트래픽 스위치                              되돌리면 끝)

카나리아    신버전에 트래픽 일부(5%)만     중간     매우낮음  빠름
            흘려 지표 관찰 → 점차 확대                        (5%만 영향)

공통 전제: 어떤 전략이든 "구버전과 신버전이 동시에 살아 있는 구간"이
생깁니다. 그래서 4섹션의 앞뒤 호환(DB·API)이 모든 전략의 기초 체력이에요.`;

const CODE_BLUEGREEN = `# 블루-그린을 compose로 흉내 내기 — 같은 이미지 계열의 서비스 2벌
# docker-compose.prod.yml 오버레이에 색깔만 다른 서비스를 나란히 둔다

services:
  web-blue:                # 현재 운영 중 (v1.4)
    image: tdatalab/web:1.4
    ports:
      - "3000:3000"
  web-green:               # 새 버전 후보 (v1.5) — 아직 트래픽 없음
    image: tdatalab/web:1.5
    ports:
      - "3010:3000"

# 절차:
# ① green 기동 → ② green에 직접 접속해 검증(3010 포트, 내부에서만)
# ③ Caddy 업스트림을 blue→green으로 전환(리로드는 무중단)
# ④ 이상 없으면 blue 중지 / 이상 있으면 업스트림만 blue로 되돌림
# 롤백 = "설정 한 줄 되돌리기". 재빌드도 재기동도 필요 없다.`;

const CODE_HEALTH = `# Spring Boot Actuator — readiness와 liveness는 다른 질문이다
# application.yml
management:
  endpoint:
    health:
      probes:
        enabled: true      # /actuator/health/readiness · /liveness 활성화

# liveness  = "살아 있나?"        → 아니오면: 컨테이너를 재시작하라
# readiness = "손님 받을 준비 됐나?" → 아니오면: 트래픽을 보내지 마라
#
# JVM은 떴지만 DB 커넥션 풀이 아직 안 채워졌다면?
#   liveness: UP / readiness: DOWN  ← 재시작할 일도, 트래픽 줄 일도 아님
#
# compose 헬스체크에는 readiness를 물어봐야 한다:
healthcheck:
  test: curl -fs http://localhost:8080/actuator/health/readiness
  interval: 5s
  retries: 12
  start_period: 30s        # JVM 워밍업 시간은 실패로 세지 않기`;

const CODE_EXPAND = `-- 확장 → 이행 → 수축: 컬럼 이름을 바꾸는 '무중단' 3단계
-- 목표: users.name 을 users.display_name 으로 바꾸고 싶다
-- 금지: ALTER TABLE users RENAME COLUMN ... 을 배포와 동시에 한 방에
--       (구버전 코드가 name을 읽는 순간 전부 깨진다)

-- [1단계: 확장(expand)] 새 컬럼을 '추가만' 한다 — 구·신 코드 모두 무사
ALTER TABLE users ADD COLUMN display_name VARCHAR(80);

-- [2단계: 이행(migrate)] 새 코드는 양쪽에 쓰고 새 컬럼을 먼저 읽는다.
--  기존 데이터는 배포와 '분리된' 배치로 천천히 채운다
UPDATE users SET display_name = name WHERE display_name IS NULL;

-- [3단계: 수축(contract)] 모든 코드가 새 컬럼만 쓰는 걸 확인한 '다음 릴리스'에서
ALTER TABLE users DROP COLUMN name;

-- 핵심: 1~3단계는 최소 세 번의 배포로 나눠 나간다.
-- 각 단계 사이에서 언제든 멈추거나 되돌릴 수 있는 게 이 방식의 값어치.`;

const CODE_FLYWAY = `# ddl-auto: update 의 문제 — "앱이 스키마를 마음대로 만진다"
# · 무엇이 바뀔지 배포 전에 알 수 없다 (리뷰 불가)
# · 컬럼 삭제·이름 변경은 아예 못 한다 (추가만 흉내냄)
# · 롤백 개념이 없다 — 되돌릴 스크립트 자체가 존재하지 않는다

# Flyway 도입 — 마이그레이션을 '번호 달린 파일'로 코드처럼 관리
src/main/resources/db/migration/
  V1__init_users.sql
  V2__add_display_name.sql        # ← 확장 단계
  V3__drop_legacy_name.sql        # ← 수축 단계 (몇 릴리스 뒤)

# application.yml
spring:
  jpa:
    hibernate:
      ddl-auto: validate   # 앱은 검증만! 스키마 변경 권한을 회수한다
  flyway:
    enabled: true

# 이제 스키마 변경도 PR 리뷰를 거치고, 어떤 버전의 DB인지 이력이 남는다.`;

const CODE_FLAG = `# 기능 플래그 — "코드 배포"와 "기능 공개"를 분리하는 스위치
# 새 화면 코드는 이미 운영에 나가 있지만, 플래그가 꺼져 있어 아무도 못 본다

# 설정(예: 환경변수 또는 설정 테이블)
FEATURE_NEW_SCREENER=false     # 배포는 어제 했고, 공개는 오늘 오후에

# 서비스 코드는 플래그를 읽어 갈림길만 태운다 (의사코드)
#   플래그 켜짐  → 새 스크리너 화면으로
#   플래그 꺼짐  → 기존 화면 그대로

# 무엇이 좋아지나:
# · 문제가 생기면 '재배포 없이' 플래그만 끈다 → 가장 빠른 롤백
# · 특정 그룹(사내 계정)에만 먼저 켜서 운영 데이터로 검증
# · 큰 기능을 작은 배포 여러 번으로 쪼개 숨겨 두고, 마지막에 켠다
# 주의: 다 켜고 안정화된 플래그는 정리(삭제)까지가 한 사이클 —
#       안 지운 플래그가 쌓이면 갈림길 미로가 된다.`;

const CODE_CANARY = `# 카나리아 롤아웃 — 지표가 게이트를 통과해야 다음 단계로
단계    트래픽    관찰 시간    통과 조건(게이트)
─────────────────────────────────────────────────────
1       5%        15분        5xx 비율 < 0.5% · p95 지연 < 800ms
2       25%       30분        위와 동일 + 로그인 성공률 기존 대비 -1%p 이내
3       100%      -           안정화 확인 후 구버전 정리

# 게이트를 '자동'으로 만드는 게 핵심:
# 지표가 조건을 벗어나면 → 사람을 기다리지 말고 → 즉시 트래픽을 0%로.
# 새벽 3시의 배포 담당자는 판단력이 흐리다. 조건은 '대낮에' 문서로 정해 둔다.
#
# 폭발 반경(blast radius) 감각:
# 100% 배포에서 터지면 전 사용자 장애, 5%에서 터지면 '지표에 남은 흔적'.
# 같은 버그라도 언제 발견하느냐가 사고의 크기를 정한다.`;

const CODE_CHECKLIST = `# 배포 체크리스트 — '기억'이 아니라 '목록'이 지키게 한다

[배포 전]
  □ CI 통과 확인 (빌드 · 테스트 · 타입체크 — 게이트를 우회하지 않았나)
  □ 이번 변경에 DB 마이그레이션이 있는가? → 있다면 확장 단계인지 확인
  □ 롤백 방법을 한 줄로 말할 수 있는가? (없으면 배포 보류)
  □ 백업 확인 — 최근 백업이 존재하고 복원 절차를 알고 있는가

[배포 중]
  □ readiness 통과 후에만 트래픽 전환
  □ 에러율 · 지연 대시보드를 옆에 띄워 두고 진행

[배포 후 15분]
  □ 5xx 비율 · p95 지연 · 로그인 성공률 정상 범위인가
  □ 신규 에러 로그 급증 없는가
  □ 이상 시: 사전에 적어 둔 롤백 트리거 조건에 따라 '즉시' 되돌린다
    (원인 분석은 되돌린 다음에 — 순서를 바꾸지 않는다)`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. 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: '왜 무중단인가' },
  { n: 2, label: '배포 전략 비교' },
  { n: 3, label: '헬스체크와 전환' },
  { n: 4, label: 'DB 마이그레이션' },
  { n: 5, label: '롤백 설계' },
  { n: 6, label: '안전장치' },
  { n: 7, label: '파이프라인 규율' },
];

export default function Lv3ZeroDowntimeDeployPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 고급 과정</div>
        <h1>🚦 무중단 배포와 롤백</h1>
        <p>
          배포 버튼 앞에서 심장이 빨라진다면, 문제는 담력이 아니라 <strong>설계</strong>예요.
          배포가 무섭지 않아야 자주 배포하고, 자주 배포해야 한 번의 변경이 작아져 더 안전해집니다.
          이 코스에서는 재시작 몇 초가 왜 장애인지에서 출발해, <strong>되돌릴 수 있게 내보내는
          법</strong> — 전략 선택, 헬스체크, DB 호환성, 롤백과 안전장치까지를 익힙니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 70분</span>
          <span className="chip">실습: 블루-그린 절차 + 롤백 조건 문서 설계</span>
          <span className="chip">선수 지식: Docker·compose 운영, HTTP, DB 기초</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="왜 무중단인가" sub="재시작 몇 초가 사용자에겐 장애다">
        <p>
          "잠깐 껐다 켜는 건데요, 30초면 돼요." — 개발자의 30초와 사용자의 30초는 다른
          시간입니다. 우리처럼 <strong>장 중 실시간 시세</strong>를 다루는 서비스라면 더더욱요.
          재시작하는 그 순간에도 누군가는 주문 직전이고, 누군가는 SSE로 시세 스트림을 받고
          있어요. 그들에게 재시작은 '점검'이 아니라 <strong>장애</strong>입니다.
        </p>
        <Code>{CODE_DOWNTIME}</Code>
        <p>
          더 무서운 건 2차 효과예요. 배포가 아프면 사람은 배포를 <strong>미룹니다</strong>.
          미루면 변경이 쌓이고, 쌓인 변경을 한 번에 내면 실패 확률이 오르고, 실패하면 원인
          후보가 수십 개라 추적도 오래 걸리죠. 반대로 배포가 아무렇지 않으면{' '}
          <strong>작게, 자주</strong> 내게 되고, 작은 배포는 실패해도 원인이 뻔히 보입니다.
          무중단 배포는 기술 과제이기 전에 <strong>배포 빈도를 끌어올리는 심리적 장치</strong>예요.
        </p>
        <div className="tip">
          <b>이 코스의 한 문장</b> 좋은 배포의 기준은 "안 터지는 배포"가 아니라{' '}
          <strong>"터져도 몇 분 안에 되돌릴 수 있는 배포"</strong>입니다. 롤백 가능성이 곧
          안전벨트예요. 안전벨트가 있어야 속도를 낼 수 있고요. 이후 모든 섹션은 이 한 문장을
          기술로 구현하는 방법들입니다.
        </div>
      </Section>

      <Section n={2} title="배포 전략 비교" sub="롤링 · 블루-그린 · 카나리아 — 비용과 되돌림 속도의 거래">
        <p>
          무중단을 이루는 전략은 크게 셋이에요. 공통 아이디어는 하나 — <strong>새 버전이
          준비를 마치기 전까지 구버전을 죽이지 않는다</strong>. 차이는 "몇 벌의 환경을 유지하고,
          트래픽을 어떻게 옮기느냐"입니다.
        </p>
        <Code>{CODE_STRATEGIES}</Code>
        <p>
          <strong>롤링</strong>은 인스턴스를 하나씩 갈아 끼우는 방식이라 추가 서버가 거의 필요
          없지만, 문제 발견 시 되돌리는 것도 다시 한 바퀴 롤링이라 느립니다.{' '}
          <strong>블루-그린</strong>은 운영(blue)과 똑같은 환경(green)을 한 벌 더 두고, 신버전을
          green에 올려 검증한 뒤 트래픽 스위치만 딸깍 넘겨요. 비용은 2배지만{' '}
          <strong>롤백이 스위치 한 번</strong>이라는 게 압도적 장점이죠. <strong>카나리아</strong>는
          광산의 카나리아처럼 신버전에 트래픽 일부만 먼저 흘려 보고, 지표가 멀쩡하면 비중을
          늘립니다.
        </p>
        <p>
          우리 운영 구조 — 단일 호스트에서 <span className="icode">docker-compose.yml</span>에{' '}
          <span className="icode">docker-compose.prod.yml</span> 오버레이를 겹쳐 올리는 방식 —
          에서는 쿠버네티스식 자동 롤링은 없어요. 하지만 <strong>서비스를 색깔별로 2벌 정의하고
          앞단(Caddy)의 업스트림만 바꾸는 수동 블루-그린</strong>은 지금 구조로도 충분히
          가능합니다. 실습에서 이 절차를 문서로 설계할 거예요.
        </p>
        <div className="tip">
          <b>전략 선택의 감각</b> 정답은 규모가 정해요. 인스턴스가 수십 개면 롤링이 자연스럽고,
          한두 개면 블루-그린이 단순하고 확실합니다. 카나리아는 "지표를 볼 눈(관측)"이 갖춰진
          다음에야 의미가 있어요 — 5%에게 나간 버그를 아무도 감지 못 하면 그냥 5% 사용자를
          실험대에 올린 것뿐이니까요(6섹션).
        </div>
      </Section>

      <Section n={3} title="헬스체크와 트래픽 전환" sub="준비되기 전엔 손님을 받지 않는다">
        <p>
          모든 무중단 전략의 공통 부품은 <strong>"새 버전이 준비됐는지 아는 방법"</strong>,
          즉 헬스체크입니다. 여기서 많이들 뭉개는 구분이 하나 있어요 —{' '}
          <strong>liveness</strong>(살아 있나)와 <strong>readiness</strong>(손님 받을 준비가
          됐나)는 <strong>다른 질문</strong>이라는 것.
        </p>
        <Code>{CODE_HEALTH}</Code>
        <p>
          이 구분이 왜 중요할까요? JVM 프로세스는 떴지만 DB 커넥션 풀이 아직 안 채워진 서비스에
          트래픽을 넣으면, 사용자는 에러를 받아요. 그렇다고 그 서비스를 재시작하면? 영원히
          준비를 못 마칩니다. <strong>liveness 실패의 처방은 재시작, readiness 실패의 처방은
          "트래픽 주지 말고 기다리기"</strong> — 처방이 다르니 진단도 나눠야 하는 거예요.
        </p>
        <p>
          트래픽 전환은 <strong>앞단</strong>에서 일어납니다. 우리 구조에선 두 겹이 있어요 —
          맨 앞의 <strong>Caddy</strong>(HTTPS 종단)와 그 뒤의 <strong>게이트웨이</strong>(8080,
          서비스 라우팅). 블루-그린 전환점은 이 앞단의 업스트림 설정이고, Caddy는 설정 리로드가
          무중단이라 전환 자체가 끊김을 만들지 않습니다.
        </p>
        <Code>{CODE_BLUEGREEN}</Code>
        <div className="warn">
          <b>흔한 함정: 얕은 헬스체크</b> <span className="icode">/actuator/health</span>가
          200을 준다고 "준비 완료"가 아닐 수 있어요. DB 연결·필수 캐시 연결이 빠진 채 HTTP
          서버만 뜬 상태도 200을 줄 수 있거든요. readiness에는 <strong>그 서비스가 일하는 데
          꼭 필요한 의존성</strong>(DB, Valkey 등)의 상태가 포함돼야 합니다. 반대로 너무 깊게
          — 옆 서비스의 상태까지 — 물면, 남의 장애에 내가 재시작당하는 연쇄가 생겨요.
        </div>
      </Section>

      <Section n={4} title="DB 마이그레이션과 호환성" sub="확장 → 이행 → 수축, 세 걸음으로 나눠 딛기">
        <p>
          무중단 배포의 최종 보스는 코드가 아니라 <strong>데이터베이스</strong>입니다. 이유는
          2섹션에서 봤듯 어떤 전략이든 <strong>구버전과 신버전 코드가 동시에 살아 있는 구간</strong>이
          생기기 때문이에요. 그 구간 동안 DB는 <strong>양쪽 모두를</strong> 만족시켜야 합니다.
          구버전이 읽는 컬럼을 신버전 배포와 동시에 지워 버리면, 전환이 끝나기 전의 구버전이
          즉사하죠. 그래서 스키마 변경은 <strong>뒤로도(backward) 앞으로도(forward) 호환</strong>되게,
          세 걸음으로 나눠 딛습니다.
        </p>
        <Code>{CODE_EXPAND}</Code>
        <p>
          <strong>확장(expand)</strong> 단계에선 추가만 해요 — 컬럼 추가, 테이블 추가는 아무도
          해치지 않습니다. <strong>이행(migrate)</strong> 단계에서 새 코드가 새 구조를 쓰기
          시작하고, 데이터를 옮깁니다. <strong>수축(contract)</strong>은 모두가 새 구조로 넘어간
          걸 확인한 <strong>다음 릴리스</strong>에서야 옛것을 지워요. 성급한 수축이 사고의
          단골 원인입니다.
        </p>
        <p>
          그런데 우리에겐 그보다 앞선 숙제가 있어요. 지금 백엔드는 JPA{' '}
          <span className="icode">ddl-auto: update</span> — <strong>앱이 뜰 때 스키마를 알아서
          바꾸는</strong> 골격 단계 설정을 쓰고 있죠. 편하지만, 무엇이 바뀔지 배포 전에 리뷰할
          수 없고, 되돌릴 스크립트도 없습니다. 무중단·롤백을 진지하게 하려면 스키마 변경을{' '}
          <strong>Flyway 같은 마이그레이션 도구의 버전 파일</strong>로 옮겨, 코드처럼 리뷰하고
          이력을 남겨야 해요.
        </p>
        <Code>{CODE_FLYWAY}</Code>
        <div className="tip">
          <b>비유</b> 확장→이행→수축은 <strong>다리를 옆에 새로 놓고 건너는 것</strong>과
          같아요. 옛 다리를 부수기 전에 새 다리를 먼저 놓고(확장), 차들이 새 다리로 옮겨 타는
          걸 지켜본 뒤(이행), 아무도 안 다니는 걸 확인하고 옛 다리를 철거합니다(수축).
          한 방에 갈아끼우는 건 차가 달리는 중에 다리를 부수는 일이고요.
        </div>
      </Section>

      <Section n={5} title="롤백 설계" sub="되돌릴 수 있는 변경과 그렇지 않은 변경을 미리 가른다">
        <p>
          롤백은 배포가 터진 뒤에 고민하는 게 아니라 <strong>배포 전에 설계하는 것</strong>입니다.
          첫걸음은 이번 변경이 어느 쪽인지 가르는 거예요 — <strong>되돌릴 수 있는 변경</strong>(코드
          로직, 화면, 설정값)과 <strong>불가역이거나 비싼 변경</strong>(컬럼 삭제, 데이터 변환·삭제,
          외부 시스템에 이미 나간 요청). 앞쪽은 이전 이미지로 갈아타면 그만이지만, 뒤쪽은 이미지가
          돌아가도 <strong>데이터는 돌아오지 않습니다</strong>.
        </p>
        <p>
          그래서 규율은 이렇게 세웁니다. <strong>① 마이그레이션과 롤백을 한 세트로 계획한다</strong> —
          "이 변경을 되돌리려면 어떤 순서로 무엇을 하나"가 배포 문서에 함께 적혀 있어야 해요.{' '}
          <strong>② 코드 롤백이 DB 롤백을 요구하지 않게 만든다</strong> — 4섹션의 확장 단계만
          나간 상태라면, 코드를 이전 버전으로 되돌려도 (새 컬럼은 무시될 뿐) DB는 그대로 둬도
          됩니다. 이게 확장→수축을 릴리스 여러 개로 쪼개는 진짜 이유예요.{' '}
          <strong>③ 불가역 작업은 최대한 뒤로 미루고, 백업을 먼저 확인한다.</strong>
        </p>
        <p>
          그리고 롤백을 아예 <strong>배포 없이</strong> 해내는 도구가 있습니다 —{' '}
          <strong>기능 플래그(feature flag)</strong>. 코드를 내보내는 일과 기능을 사용자에게
          공개하는 일을 분리하는 스위치예요.
        </p>
        <Code>{CODE_FLAG}</Code>
        <div className="warn">
          <b>롤백 트리거는 '조건'으로 적는다</b> "문제가 심각하면 롤백한다"는 문장은 새벽 3시에
          아무도 실행하지 못해요. 심각함을 판정하느라 30분을 태우거든요.{' '}
          <strong>"5xx 비율이 1%를 10분 넘게 유지하면 롤백"</strong>처럼 숫자와 시간이 있는
          조건이어야, 판단이 아니라 <strong>실행</strong>만 남습니다. 이번 실습의 핵심 산출물이
          바로 이 트리거 조건 목록이에요.
        </div>
      </Section>

      <Section n={6} title="안전장치 — 관측과 자동 롤백" sub="폭발 반경을 줄이고, 지표가 사람보다 먼저 알게 한다">
        <p>
          카나리아 전략(2섹션)이 진짜 힘을 내는 건 <strong>지표 관측과 결합될 때</strong>입니다.
          신버전에 5% 트래픽을 흘리면서 에러율·지연·핵심 전환율을 보고, 조건을 벗어나는 순간{' '}
          <strong>사람의 승인을 기다리지 않고</strong> 트래픽을 회수하는 것 — 이게 자동 롤백이에요.
        </p>
        <Code>{CODE_CANARY}</Code>
        <p>
          여기서 앞 강(에러 추적과 모니터링)이 그대로 이어집니다. 배포 직후 몇 분은{' '}
          <strong>에러 로그가 가장 정직한 시간</strong>이에요. 평소에 없던 예외가 갑자기 분당
          수십 건 찍히면, 사용자 신고가 오기 전에 지표가 먼저 알려 줍니다. 그래서 배포 절차에
          "배포 후 15분간 에러 대시보드 관찰"이 <strong>절차의 일부로</strong> 들어가야 하고,
          더 나아가면 "배포 직후 신규 에러 급증 → 알림 → 자동 롤백"까지 묶을 수 있어요.
        </p>
        <p>
          이 모든 장치를 관통하는 개념이 <strong>폭발 반경(blast radius)</strong>입니다.
          같은 버그라도 100%에게 나가면 전면 장애, 5%에게 나가면 지표의 흔적이에요. 점진적
          롤아웃, 기능 플래그의 그룹별 공개, 사내 계정 선공개 — 전부 "터질 수 있다는 전제
          아래, 터졌을 때의 반경을 줄이는" 같은 철학의 변주입니다.
        </p>
        <div className="tip">
          <b>완벽한 자동화가 없어도</b> 우리 규모에서 카나리아 트래픽 분배 자동화는 아직 과할
          수 있어요. 하지만 축소판은 오늘부터 가능합니다 — 블루-그린의 green을 전환 전에{' '}
          <strong>내부에서 먼저 두들겨 보는 것</strong>이 트래픽 0%짜리 카나리아고, 배포 후
          15분 지표 관찰이 수동 게이트예요. 도구보다 <strong>순서와 조건</strong>이 먼저입니다.
        </div>
      </Section>

      <Section n={7} title="배포 파이프라인 규율" sub="사람의 기억이 아니라 절차가 지키게 한다">
        <p>
          마지막 조각은 기술이 아니라 <strong>규율</strong>입니다. 무중단 장치를 다 갖춰도,
          테스트를 건너뛴 빌드가 운영에 나가면 소용없으니까요. 파이프라인의 각 단계 —{' '}
          <strong>빌드 → 테스트 → 배포</strong> — 를 <strong>게이트</strong>로 세우고, 게이트를
          통과하지 못한 산출물은 다음 단계로 못 가게 하는 것. "급하니까 이번만"이 규율의
          가장 큰 적이에요. 급할수록 실수 확률이 높고, 실수했을 때 기댈 건 절차뿐입니다.
        </p>
        <p>
          그리고 최후의 안전망 — <strong>백업</strong>. 우리 플랫폼에는 Postgres와 Valkey를
          하루 한 번 백업하고 30일 보관하는 백업 데몬이 이미 돌고 있죠. 롤백 설계가 아무리
          좋아도 데이터가 깨지는 최악의 시나리오는 남고, 그때 복원할 백업이 있느냐가 서비스의
          생사를 가릅니다. 중요한 건 <strong>"백업이 돌고 있다"가 아니라 "복원해 본 적이
          있다"</strong>예요. 복원 안 해 본 백업은 있다고 믿는 파일일 뿐입니다.
        </p>
        <Code>{CODE_CHECKLIST}</Code>
        <p>
          체크리스트가 촌스러워 보일 수 있지만, 항공기 조종사도 수천 번 한 이륙 앞에서 목록을
          읽어요. 긴장한 사람의 기억은 구멍이 나고, 목록은 나지 않으니까요. 배포 후 확인까지
          끝나야 배포가 끝난 겁니다 — <strong>머지가 아니라 "운영에서 멀쩡히 도는 것"이
          완료의 정의</strong>예요.
        </p>
        <div className="tip">
          <b>실습 과제</b> 우리 서비스 중 하나(예: web 프론트)를 골라 문서 한 장을 쓰세요 —{' '}
          <strong>① 블루-그린 배포 절차</strong>(green 기동 → 검증 방법 → 전환 명령 → blue 정리)
          단계별로, <strong>② 롤백 트리거 조건</strong>(숫자·시간이 있는 조건 3개 이상),{' '}
          <strong>③ 롤백 실행 절차</strong>(누가, 무엇을, 몇 분 안에). 완성 기준은 하나 —{' '}
          <strong>이 문서만 들고 처음 보는 사람이 새벽 3시에 실행할 수 있는가.</strong>
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🚦 여기까지 왔다면</h3>
          <p className="muted">
            이제 배포는 "숨 참고 누르는 버튼"이 아니라 <strong>설계된 절차</strong>가 됐어요.
            준비된 것만 트래픽을 받고(헬스체크), DB는 세 걸음으로 나눠 딛고(확장→이행→수축),
            터지면 조건에 따라 몇 분 안에 되돌립니다(롤백 트리거·기능 플래그). 되돌릴 수 있다는
            확신이 생기면 자연히 <strong>작게, 자주</strong> 내게 되죠. 그런데 작게 자주 내는
            팀의 코드는 또 다른 근육을 요구해요 — 매일 흐르는 변경을 서로 읽고 다듬는 힘이요.
            다음 <Link to="/learn/lv3-code-review-refactoring"><strong>코드 리뷰와 리팩터링
            전략</strong></Link> 코스에서, 변경의 품질을 지키는 팀의 기술을 이어서 익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
