// 이 파일이 하는 일: "분산 시스템 첫걸음 — 왜 서버 한 대로는 안 될까" 코스 — 서버가 여러 대가
// 되는 순간 새로 생기는 근본 문제들(부분 실패, CAP, 일관성 모델, 시간)을 7개 섹션으로 안내하는
// 정적 학습 페이지. 프레이밍은 "특급 과정 — 대규모 트래픽과 분산 환경을 설계하는 시야" —
// 코드 한 줄보다 '어떤 데이터에 어떤 일관성이 필요한가'를 스스로 판단하는 감각을 기른다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

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

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

const CODE_SCALE = `수직 확장(스케일업)                 수평 확장(스케일아웃)
"더 좋은 서버 한 대"                "평범한 서버 여러 대"

비용 |          *                  비용 |
     |        *                        |            ****
     |      *                          |        ****
     |   *                             |    ****
     | *                               | ***
     +------------ 성능                +------------ 성능
  성능이 오를수록 가격이 '기하급수'      대체로 '선형'으로 늘어난다
  그리고 물리적 상한이 있다             대신 소프트웨어가 복잡해진다

핵심: 스케일업은 언젠가 벽을 만나요. 그 벽 앞에서 서버를 2대로
늘리는 순간 — 분산 시스템의 모든 문제가 한꺼번에 시작됩니다.
상태를 어디에 둘지, 두 서버의 시계가 다르면 어쩔지, 한 대만
죽으면 어떻게 알아챌지. 이 코스는 그 문제들의 지도입니다.`;

const CODE_FALLACIES = `분산 컴퓨팅의 8가지 오류 (fallacies of distributed computing)
— 신입 설계자가 '참'이라고 믿어 버리기 쉬운 8가지 거짓말

1. 네트워크는 신뢰할 수 있다        → 아니다. 패킷은 유실된다.
2. 지연 시간(latency)은 0이다       → 아니다. 서울-버지니아는 왕복 ~180ms.
3. 대역폭은 무한하다                → 아니다. 시세 틱을 다 쏘면 터진다.
4. 네트워크는 안전하다              → 아니다. 그래서 내부 호출도 키를 검증한다.
5. 토폴로지는 변하지 않는다         → 아니다. 컨테이너는 수시로 죽고 뜬다.
6. 관리자는 한 명이다               → 아니다. 팀·클라우드·통신사가 얽힌다.
7. 전송 비용은 0이다                → 아니다. 직렬화·복사·과금이 붙는다.
8. 네트워크는 균질하다              → 아니다. 구간마다 속도·정책이 다르다.

이 8줄을 뒤집으면 그대로 분산 설계 체크리스트가 됩니다.`;

const CODE_TIMEOUT = `// "응답이 안 온다"의 세 가지 가능성 — 호출자는 구분할 수 없다
//
//  클라이언트 ──요청──> ???
//
//  경우 A: 요청이 서버에 도착하지 못함        (처리 안 됨)
//  경우 B: 서버가 받아서 처리 중 / 처리 완료   (처리 됨!)
//  경우 C: 처리는 끝났는데 응답만 유실됨       (처리 됨!)
//
// 타임아웃이 터졌을 때 보이는 건 셋 다 똑같이 '침묵'이에요.
// 그래서 재시도는 "이미 처리됐을 수도 있다"를 전제해야 합니다.

// 멱등성(idempotency): 같은 요청을 몇 번 보내도 결과가 한 번과 같게
POST /api/orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

// 서버는 이 키를 본 적 있으면 '저장해 둔 그때의 응답'을 그대로 돌려준다.
// 잔고 차감이 두 번 일어나는 사고는 이 한 줄이 막는다.`;

const CODE_BACKOFF = `// 재시도 폭풍(retry storm): 서버가 느려짐 → 전원이 타임아웃
// → 전원이 '동시에' 재시도 → 부하 2배 → 더 느려짐 → 또 재시도...
// 도우려던 재시도가 장애를 눈덩이처럼 키우는 악순환이에요.

// 처방 ①: 지수 백오프 — 간격을 1s, 2s, 4s, 8s... 로 늘린다
// 처방 ②: 지터(jitter) — 간격에 무작위를 섞어 '동시에'를 깨뜨린다
long delayMs = baseMs * (1L << attempt);            // 지수 백오프
delayMs = ThreadLocalRandom.current()
            .nextLong(0, delayMs + 1);               // full jitter
Thread.sleep(Math.min(delayMs, maxDelayMs));        // 상한도 필수

// 처방 ③: 재시도 횟수 상한 + 회로 차단(circuit breaker).
// "포기할 줄 아는 클라이언트"가 시스템 전체를 살립니다.`;

const CODE_CAP = `CAP 정리 — 흔한 오해와 정확한 문장

[오해] "C, A, P 셋 중 마음에 드는 두 개를 고르세요"
[정확] "네트워크 분할(P)이 '일어났을 때', C와 A 중 하나를 포기해야 한다"

  C (Consistency)          모든 노드가 같은 순간, 같은 데이터를 보인다
  A (Availability)         모든 요청이 (오래된 값일지라도) 응답을 받는다
  P (Partition tolerance)  노드 사이 통신이 끊겨도 시스템이 동작한다

네트워크는 반드시 언젠가 끊깁니다(8가지 오류 1번). 그래서 P는
'선택지'가 아니라 '전제'예요. 실제 선택은 이것뿐입니다:

  분할이 일어난 순간 →  CP: 일관성을 지키려 일부 요청을 거절한다
                        AP: 응답은 계속 주되 잠시 어긋남을 허용한다

  CP 예: 결제 잔고 — 틀린 잔고를 보여줄 바엔 잠깐 멈추는 게 낫다
  AP 예: 좋아요 수 — 숫자가 3초 어긋나도 서비스는 계속 돌아야 한다`;

const CODE_PACELC = `PACELC — CAP의 확장판: "분할이 없을 때도 공짜는 없다"

  if (Partition)  →  A(가용성) vs C(일관성)     ... 여기까지가 CAP
  Else            →  L(지연)   vs C(일관성)     ... CAP이 놓친 절반

평상시(분할 없음)에도, 강한 일관성을 원하면 여러 노드의 확인을
기다려야 하므로 응답이 '느려'집니다. 빠른 응답을 원하면 가까운
복제본 하나만 읽으므로 '오래된 값'일 수 있고요.

  PA/EL 성향: DynamoDB, Cassandra   — 늘 빠르게, 어긋남 허용
  PC/EC 성향: 전통 RDB + 동기 복제   — 늘 정확하게, 지연 감수

즉 일관성의 값은 장애 때만 내는 게 아니라, 매 요청의
지연 시간으로 '평소에도 조금씩' 지불하고 있는 셈입니다.`;

const CODE_CONSISTENCY = `일관성 모델 스펙트럼 — 강할수록 느리고, 약할수록 빠르다

강함 ◀──────────────────────────────────────────▶ 약함

선형성(linearizability)
  "쓰기가 끝난 '실제 시각' 이후의 모든 읽기는 새 값을 본다"
  단일 서버처럼 행동. 가장 직관적, 가장 비싸다.

순차 일관성(sequential)
  "모두가 '같은 순서'로 사건을 본다 — 그 순서가 실제 시각과
   일치한다는 보장은 없다"

인과적 일관성(causal)
  "원인과 결과의 순서만 지킨다 — 댓글이 원글보다 먼저 보이는
   일은 없다. 서로 무관한 사건의 순서는 어긋나도 된다"

최종 일관성(eventual)
  "쓰기가 멈추면 '언젠가는' 모두 같은 값에 수렴한다"
  언제인지는 약속하지 않는다. 가장 싸고, 가장 조심해야 한다.`;

const CODE_CLIENT_CENTRIC = `클라이언트 중심 보장 — 사용자 한 명의 눈높이에서 본 일관성

read-your-writes   "내가 쓴 건 내가 바로 읽을 수 있다"
  → 프로필을 수정했는데 새로고침하니 옛 사진이 보인다? 이 보장이
    없어서예요. 내 쓰기가 간 노드에서 내 읽기도 처리하면 해결.

monotonic reads    "한번 본 값보다 과거로 돌아가지 않는다"
  → 새 댓글 3개를 봤는데 새로고침하니 1개로 줄었다? 요청이
    복제 지연이 다른 노드로 번갈아 가서 그래요. 같은 사용자를
    같은 복제본에 붙이면(sticky session) 해결.

시스템 전체는 최종 일관성이어도, 이 두 가지만 지켜 주면
사용자가 체감하는 '이상함'의 대부분이 사라집니다.`;

const CODE_QUORUM = `쿼럼(quorum) — 복제본 N개에서 몇 개의 동의를 받을 것인가

  N = 복제본 수,  W = 쓰기 성공에 필요한 응답 수,  R = 읽기 시 확인하는 수

  규칙:  W + R > N  이면 읽기와 쓰기가 반드시 '한 노드 이상 겹친다'
         → 읽기가 최소 하나는 최신 값을 만난다

  예) N=3, W=2, R=2  →  2+2 > 3  ✓  균형형 (흔한 기본값)
      N=3, W=3, R=1  →  쓰기는 느리지만 읽기가 매우 빠르다
      N=3, W=1, R=1  →  1+1 < 3  ✗  빠르지만 낡은 값을 읽을 수 있다

다수결(과반)이 필요한 이유: 노드가 5대일 때 3대의 동의를 받으면,
어떤 다른 결정도 3대를 못 모은다(3+3 > 5, 반드시 겹침).
'서로 모순되는 두 결정'이 동시에 성립하는 걸 수학적으로 막는 장치.`;

const CODE_RAFT = `Raft 합의 알고리즘 — 큰 그림 세 단계 (암기 말고 흐름만)

① 리더 선출 (leader election)
   팔로워는 리더의 심장박동(heartbeat)을 기다린다.
   일정 시간 못 받으면 → "리더가 죽었나?" → 후보로 나서서 투표 요청.
   과반 득표한 노드가 새 리더. (과반이므로 리더는 동시에 둘일 수 없다)

② 로그 복제 (log replication)
   모든 쓰기는 리더로만 들어온다. 리더는 자기 로그에 적고,
   팔로워들에게 복사본을 보낸다.

③ 커밋 (commit)
   과반의 팔로워가 "받았다"고 답하면 그때 확정(commit)하고
   클라이언트에 성공을 알린다. 과반에 새겨진 기록은
   리더가 죽어도 다음 리더에게 반드시 살아남는다.

etcd(쿠버네티스의 두뇌), Consul 등이 Raft로 굴러갑니다.`;

const CODE_LAMPORT = `// 램포트 타임스탬프 — 물리 시계 없이 사건의 '순서' 정하기
// 규칙은 딱 두 개:
//   ① 내 사건이 하나 생길 때마다 내 카운터를 +1
//   ② 메시지를 받으면: 내 카운터 = max(내 것, 받은 것) + 1

서버A: 카운터 1 → 2 → 3(메시지 발송, 3을 실어 보냄)
서버B: 카운터 1 → 수신! max(1, 3)+1 = 4

이제 "발송(3) → 수신(4)"의 인과 순서가 숫자로 남았어요.
서버 A와 B의 벽시계가 몇 초씩 어긋나 있어도 상관없습니다.

한계: 카운터가 5와 7이라고 5가 '먼저 일어났다'는 보장은 없어요.
(서로 무관한 사건일 수 있음) — 인과가 있으면 숫자가 크다,
의 역은 성립하지 않는다는 것. 이 한계를 메우려고 벡터 시계가 나옵니다.`;

const CODE_LWW = `LWW(Last-Write-Wins) — "타임스탬프가 늦은 쓰기가 이긴다"

  서버A (시계가 30초 빠름)          서버B (시계 정상)
  10:00:30  잔고 = 5000 저장         10:00:10  잔고 = 8000 저장
                                     (실제로는 B가 '나중' 쓰기!)

  충돌 해소: LWW → 타임스탬프 비교 → 10:00:30 > 10:00:10
  → A의 5000이 승리 → B의 정당한 쓰기가 '조용히 증발'

무서운 점: 에러도 로그도 없이 데이터가 사라진다는 것.
NTP로 시계를 맞춰도 수십 ms 오차는 남고, 가상머신은 초 단위로
튀기도 해요. 그래서 '무엇이 이길지'가 돈과 관련되면 LWW 대신
버전 번호, 벡터 시계, 혹은 아예 단일 리더 직렬화를 씁니다.`;

const CODE_TRADERBOX = `traderbox의 실제 선택 — 같은 제품, 다른 일관성

┌──────────────┬──────────────────────┬─────────────────────┐
│ 데이터        │ 시세 틱 (실시간 가격) │ 인증·회원·잔고성     │
├──────────────┼──────────────────────┼─────────────────────┤
│ 저장/전달     │ Valkey Pub/Sub        │ 단일 PostgreSQL      │
│              │ (market.ticks 채널)   │ (스키마-퍼-서비스)   │
│ 성향          │ AP 쪽                 │ CP 쪽 (강한 일관성)  │
│ 유실되면?     │ 다음 틱이 곧 덮는다   │ 사고 (복구 불가)     │
│ 지연되면?     │ 치명적 (가치 소멸)    │ 수백 ms는 감수 가능  │
│ 재전송?       │ 안 한다 (fire&forget) │ 트랜잭션으로 보장    │
└──────────────┴──────────────────────┴─────────────────────┘

시세: "1초 전 가격"은 이미 낡은 데이터 — 유실 복구에 시간을
쓰느니 최신 틱을 빨리 흘리는 게 낫다. 그래서 Pub/Sub.
인증·잔고: 한 건이라도 어긋나면 안 된다 — 그래서 트랜잭션이
있는 단일 Postgres. 속도는 일관성 값으로 지불한다.`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. 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: 'CAP 정리' },
  { n: 4, label: '일관성 모델' },
  { n: 5, label: '복제와 합의' },
  { n: 6, label: '분산 시스템의 시간' },
  { n: 7, label: 'traderbox로 보기' },
];

export default function Lv4DistributedSystemsBasicsPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 특급 과정</div>
        <h1>분산 시스템 첫걸음<br />— 왜 서버 한 대로는 안 될까</h1>
        <p>
          서버가 <strong>한 대</strong>일 때 세상은 단순해요. 트랜잭션은 원자적이고, 시계는
          하나고, 죽으면 그냥 다 죽습니다. 그런데 트래픽이 자라 서버가 <strong>두 대</strong>가
          되는 순간 — 상태 공유, 부분 실패, 시계 불일치라는 전혀 새로운 종류의 문제가
          한꺼번에 태어납니다. 이 코스에서는 <strong>CAP, 일관성 모델, 부분 실패</strong>를
          몸으로 이해하고, 우리 traderbox의 실제 설계 결정으로 그 감각을 검증해 봅니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 80분</span>
          <span className="chip">실습: 데이터 10가지 CP/AP 분류표</span>
          <span className="chip">선수 지식: 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>
          트래픽이 늘면 처음엔 <strong>수직 확장(스케일업)</strong>을 해요. CPU를 더 좋은 걸로,
          메모리를 더 크게 — 코드는 한 줄도 안 바꿔도 되니까요. 문제는 비용 곡선입니다.
          성능이 2배인 서버는 값이 2배가 아니라 <strong>4배, 8배</strong>로 뛰고, 어느 지점부터는
          돈이 있어도 <strong>살 수 없는 사양</strong>이 돼요. 그래서 결국{' '}
          <strong>수평 확장(스케일아웃)</strong> — 평범한 서버를 여러 대 놓는 길로 갑니다.
        </p>
        <Code>{CODE_SCALE}</Code>
        <p>
          서버가 2대가 되는 순간, 한 대일 땐 존재하지 않던 문제 세 가지가 태어납니다.{' '}
          <strong>① 상태 공유</strong> — 사용자의 세션이 1번 서버에 있는데 다음 요청이 2번
          서버로 가면? <strong>② 시계 불일치</strong> — 두 서버의 시계가 몇 초 어긋나 있으면
          "어느 쓰기가 먼저인가"를 누가 정하나요? <strong>③ 부분 실패</strong> — 한 대일 땐
          죽으면 다 죽어서 차라리 깔끔했는데, 이젠 <strong>절반만 죽은 상태</strong>가
          생깁니다. 분산 시스템의 어려움은 대부분 이 세 번째, 부분 실패에서 나와요.
        </p>
        <p>
          이런 함정을 선배들이 여덟 줄로 정리해 둔 게{' '}
          <strong>분산 컴퓨팅의 8가지 오류</strong>입니다. 1990년대 Sun Microsystems의
          엔지니어들이 남긴 목록인데, 30년이 지난 지금도 한 줄도 낡지 않았어요.
        </p>
        <Code>{CODE_FALLACIES}</Code>
        <div className="tip">
          <b>읽는 법</b> 이 8가지는 "외워야 할 지식"이 아니라 <strong>설계 리뷰 때 던지는
          질문</strong>이에요. 새 기능이 서비스 간 호출을 추가한다면 — "이 호출이 유실되면?
          느려지면? 상대가 죽어 있으면?" 을 습관처럼 묻는 것. 그 질문이 이 코스 전체의
          출발점입니다.
        </div>
      </Section>

      <Section n={2} title="네트워크는 믿을 수 없다 — 부분 실패와 타임아웃" sub="응답 없음의 세 얼굴, 그리고 멱등성이라는 보험">
        <p>
          단일 서버 안의 함수 호출은 <strong>성공 아니면 예외</strong>, 둘 중 하나예요. 그런데
          네트워크 건너편으로의 호출엔 제3의 상태가 있습니다 — <strong>침묵</strong>.
          타임아웃이 터졌을 때, 요청이 아예 도착을 못 한 건지, 서버가 지금 처리 중인 건지,
          처리는 끝났는데 <strong>응답만 유실</strong>된 건지 호출자는 절대 구분할 수 없어요.
          이게 분산 시스템에서 가장 근본적인 불확실성입니다.
        </p>
        <Code>{CODE_TIMEOUT}</Code>
        <p>
          그래서 <strong>타임아웃 + 재시도</strong>를 설계할 땐 반드시{' '}
          <strong>멱등성(idempotency)</strong>이 세트예요. "이미 처리됐을지도 모르는 요청을
          한 번 더 보내도 안전한가?" — 조회(GET)는 원래 안전하지만, 주문 생성이나 잔고 차감
          같은 쓰기는 <span className="icode">Idempotency-Key</span> 같은 장치로 "같은 요청 두 번 =
          한 번"을 서버가 보장해 줘야 재시도를 마음 놓고 걸 수 있습니다. 멱등성 없는 재시도는
          <strong> 중복 결제 사고</strong>의 지름길이에요.
        </p>
        <p>
          재시도엔 함정이 하나 더 있어요. 서버가 살짝 느려진 순간, 수천 개의 클라이언트가{' '}
          <strong>동시에</strong> 타임아웃을 맞고 <strong>동시에</strong> 재시도하면 — 부하가
          두 배가 되고, 서버는 더 느려지고, 또 전원이 재시도하는 악순환,{' '}
          <strong>재시도 폭풍(retry storm)</strong>이 붑니다.
        </p>
        <Code>{CODE_BACKOFF}</Code>
        <div className="tip">
          <b>지터가 핵심인 이유</b> 지수 백오프만 쓰면 "1초 뒤, 2초 뒤, 4초 뒤"에 여전히{' '}
          <strong>모두가 동시에</strong> 몰려옵니다. 파도가 늦게 올 뿐 파도인 건 같아요.
          무작위(지터)를 섞어야 파도가 <strong>이슬비</strong>로 흩어집니다. "백오프는 간격을
          벌리고, 지터는 발맞춤을 깨뜨린다" — 둘은 항상 한 쌍입니다.
        </div>
        <div className="warn">
          <b>타임아웃 값은 공짜가 아니다</b> 타임아웃을 짧게 잡으면 멀쩡히 처리 중인 요청을
          실패로 오판하고(→ 중복 위험), 길게 잡으면 죽은 서버를 붙들고 스레드가 고갈됩니다.
          정답 값은 없고, <strong>그 호출의 정상 응답 분포</strong>(예: p99 지연)를 보고
          정하는 게 실무의 방법이에요.
        </div>
      </Section>

      <Section n={3} title="CAP 정리 — 세 개 중 두 개가 아니라 '분할 시 선택'" sub="P는 전제다. 진짜 선택지는 C vs A">
        <p>
          분산 시스템에서 가장 유명하고, 가장 자주 <strong>틀리게 인용되는</strong> 정리가
          CAP입니다. "일관성·가용성·분할내성 중 두 개를 고르세요"라는 문장은 시험엔 나올지
          몰라도 설계엔 못 씁니다. 정확한 문장은 이거예요 — <strong>"네트워크 분할이 일어난
          동안, 일관성과 가용성을 동시에 가질 수 없다."</strong>
        </p>
        <Code>{CODE_CAP}</Code>
        <p>
          왜 P가 전제냐면 — 섹션 1의 8가지 오류 1번, "네트워크는 신뢰할 수 있다"가 거짓이기
          때문이에요. 스위치 고장, 케이블 절단, GC 멈춤으로 인한 오탐까지, 분할은{' '}
          <strong>반드시 언젠가</strong> 일어납니다. "P를 포기한다"는 건 "네트워크가 절대 안
          끊긴다고 가정한다"는 뜻인데, 그건 설계가 아니라 기도죠. 그래서 실전 질문은 늘
          하나입니다 — <strong>"분할이 일어난 그 순간, 우리는 멈출 것인가(CP), 어긋남을
          허용할 것인가(AP)?"</strong>
        </p>
        <p>
          그리고 이 선택은 시스템 전체가 아니라 <strong>데이터마다</strong> 다르게 내릴 수
          있어요. 결제 잔고는 CP — 틀린 잔고로 출금을 허용하느니 "잠시 후 다시 시도해
          주세요"가 낫습니다. 좋아요 수는 AP — 숫자가 3초쯤 어긋나도 서비스가 멈추는 것보단
          백 배 낫죠. 섹션 7에서 우리 traderbox가 정확히 이 논리로 갈라지는 걸 보게 됩니다.
        </p>
        <Code>{CODE_PACELC}</Code>
        <div className="tip">
          <b>PACELC로 한 단계 더</b> CAP은 "장애가 났을 때" 이야기만 해요. PACELC는 거기에
          "평상시(Else)에도 <strong>지연(L) vs 일관성(C)</strong> 트레이드오프가 있다"를
          덧붙입니다. 강한 일관성은 매 요청마다 여러 노드의 확인을 기다리는 <strong>지연
          비용</strong>을 항상 내고 있다는 것 — 일관성은 장애 보험료가 아니라{' '}
          <strong>월 구독료</strong>에 가깝습니다.
        </div>
      </Section>

      <Section n={4} title="일관성 모델 스펙트럼 — 강한 일관성부터 최종 일관성까지" sub="어긋남을 '얼마나, 어떤 방향으로' 허용할 것인가">
        <p>
          CP냐 AP냐는 시작일 뿐이에요. "일관성을 얼마나 느슨하게 할 것인가"엔 훨씬 고운
          눈금이 있습니다. 이 눈금을 <strong>일관성 모델</strong>이라 불러요. 강할수록
          직관적이고 느리며, 약할수록 빠르고 위험합니다.
        </p>
        <Code>{CODE_CONSISTENCY}</Code>
        <p>
          가장 강한 <strong>선형성</strong>은 "분산돼 있다는 사실 자체를 숨기는" 보장이에요.
          쓰기가 완료된 실제 시각 이후의 읽기는 어느 노드로 가든 새 값을 봅니다. 대신 매
          연산마다 노드 간 조율이 필요해 비쌉니다. <strong>인과적 일관성</strong>은 실무에서
          가성비가 좋은 중간 지점이에요 — "댓글이 원글보다 먼저 보이는" 종류의 어색함만 막고,
          서로 무관한 사건의 순서는 놓아줍니다. <strong>최종 일관성</strong>은 "언젠가는
          맞는다"만 약속해요. 그 "언젠가"가 오기 전의 창(window)에서 무슨 일이 벌어져도
          되는지를 <strong>설계자가</strong> 판단해야 합니다.
        </p>
        <p>
          시스템 전체의 보장과 별개로, <strong>사용자 한 명의 눈높이</strong>에서 지켜 줘야 할
          최소한이 있어요. 이걸 클라이언트 중심 보장이라 부릅니다.
        </p>
        <Code>{CODE_CLIENT_CENTRIC}</Code>
        <p>
          기준을 하나로 정리하면 이렇습니다. <strong>SNS 팔로워 수</strong>는 잠깐 안 맞아도
          돼요 — 어긋난 값을 봐도 사용자가 내리는 결정이 달라지지 않고, 곧 수렴하며, 틀린
          기간의 피해가 0에 가깝기 때문이에요. <strong>계좌 잔고</strong>는 안 됩니다 — 어긋난
          값이 <strong>다음 행동(출금·주문)의 입력</strong>이 되고, 그 행동은 되돌릴 수 없기
          때문이에요. "이 값이 잠깐 틀리면, 그걸 보고 누가 무엇을 결정하는가?"가 일관성
          수준을 정하는 질문입니다.
        </p>
        <div className="tip">
          <b>실습 예고</b> 이 질문을 이 코스의 실습에서 그대로 씁니다 — 우리 서비스의 데이터
          10가지를 골라 CP/AP 성향과 필요한 일관성 수준을 표로 분류하고, 근거를 한 줄씩
          적어 보세요. "왜"를 적을 수 없다면 아직 그 데이터를 이해 못 한 거예요.
        </div>
      </Section>

      <Section n={5} title="복제와 합의 맛보기 — 리더는 누가 정하나" sub="스플릿 브레인, 쿼럼, 그리고 Raft의 큰 그림">
        <p>
          가용성을 위해 데이터를 여러 노드에 <strong>복제</strong>하면, 이번엔 "누구 말이
          진짜인가"라는 문제가 생깁니다. 가장 흔한 답은 <strong>리더-팔로워 복제</strong> —
          쓰기는 리더 한 곳으로만 받고, 팔로워들은 그걸 복사합니다. 쓰기 충돌이 원천적으로
          없어서 단순하죠. 그런데 <strong>리더가 죽으면?</strong>
        </p>
        <p>
          팔로워 중 하나를 새 리더로 승격시키면 되는데, 여기 무서운 함정이 있어요. 옛 리더가
          진짜 죽은 게 아니라 <strong>네트워크가 잠깐 끊겼던 것뿐</strong>이라면? 잠시 후
          돌아온 옛 리더는 자기가 아직 리더인 줄 알고 쓰기를 계속 받습니다. 이제 리더가{' '}
          <strong>둘</strong> — 서로 모르는 채 각자 다른 데이터를 쌓아 가는 이 상태를{' '}
          <strong>스플릿 브레인(split brain)</strong>이라 부릅니다. 나중에 발견해도 두 갈래의
          데이터를 합칠 방법이 없는, 분산 시스템 최악의 사고 중 하나예요.
        </p>
        <p>
          해독제가 <strong>다수결</strong>입니다. "과반의 동의 없이는 아무도 리더가 될 수
          없다"고 정해 두면, 과반은 정의상 하나뿐이므로 리더도 하나뿐이에요. 이 다수결을
          읽기·쓰기에 일반화한 게 <strong>쿼럼</strong>입니다.
        </p>
        <Code>{CODE_QUORUM}</Code>
        <p>
          그리고 "리더 선출과 로그 복제를 다수결로 안전하게 해내는 절차"를 통째로 알고리즘으로
          만든 것이 <strong>합의(consensus) 알고리즘</strong>이에요. 원조는 Paxos지만
          악명 높게 어렵고, 요즘의 표준은 <strong>이해 가능성을 목표로 설계된 Raft</strong>입니다.
        </p>
        <Code>{CODE_RAFT}</Code>
        <div className="warn">
          <b>직접 구현하지 마세요</b> 합의 알고리즘은 "큰 그림을 이해하고, 검증된 구현(etcd,
          ZooKeeper, 각 DB 내장 합의)을 <strong>쓰는</strong>" 영역이에요. 논문 그대로 구현해도
          엣지 케이스(리더 교체 중의 중복 커밋 등)에서 수년 묵은 버그가 나오는 게 이 동네입니다.
          여러분이 가져갈 것은 구현이 아니라 <strong>"과반이 왜 안전한가"라는 직관</strong>이에요.
        </div>
      </Section>

      <Section n={6} title="분산 시스템의 시간 — '동시에'는 없다" sub="물리 시계의 배신, 논리적 시계, 그리고 LWW의 함정">
        <p>
          단일 서버에선 "A가 B보다 먼저 일어났다"를 시계 한 번 보면 알아요. 분산 환경에선
          이 당연한 것이 무너집니다. 각 서버의 <strong>물리 시계</strong>는 수정 진동자의
          미세한 편차 때문에 계속 어긋나고(drift), <strong>NTP</strong>로 주기적으로 맞춰도
          네트워크 지연 때문에 수 ms~수백 ms의 오차가 남아요. 심지어 NTP가 시계를 뒤로{' '}
          <strong>되감는</strong> 순간도 있습니다 — "시간은 단조 증가한다"는 가정마저 깨지는
          거죠.
        </p>
        <p>
          그래서 분산 시스템은 발상을 바꿉니다 — "몇 시에 일어났나"를 포기하고{' '}
          <strong>"어느 것이 먼저인가"만 지키자.</strong> 이게 <strong>논리적 시계</strong>고,
          가장 단순한 형태가 램포트 타임스탬프예요.
        </p>
        <Code>{CODE_LAMPORT}</Code>
        <p>
          이 원리를 모르면 밟게 되는 지뢰가 <strong>LWW(Last-Write-Wins)</strong>입니다.
          "충돌하면 타임스탬프가 늦은 쓰기가 이긴다" — 문장은 합리적으로 들리지만, 그
          타임스탬프가 <strong>어긋난 물리 시계</strong>에서 나온다는 게 함정이에요.
        </p>
        <Code>{CODE_LWW}</Code>
        <div className="tip">
          <b>그래서 언제 LWW를 써도 되나</b> "마지막 값만 의미 있고, 중간 값이 증발해도 아무도
          다치지 않는" 데이터 — 예를 들어 사용자의 <strong>마지막 접속 시각</strong>, 캐시된
          프로필 사진 URL 같은 것들이에요. 반대로 <strong>잔고·재고·주문 상태</strong>처럼 모든
          쓰기가 의미 있는 데이터에 LWW를 쓰는 건, 시계 오차만큼의 확률로 돈을 지우는
          장치를 설치하는 것과 같습니다.
        </div>
      </Section>

      <Section n={7} title="traderbox로 보는 분산 설계 결정" sub="같은 제품 안에서 갈라지는 두 개의 일관성">
        <p>
          이제 배운 렌즈로 <strong>우리 제품</strong>을 봅시다. traderbox에는 성격이 정반대인
          데이터 두 부류가 공존해요 — <strong>시세 틱</strong>과 <strong>인증·잔고성
          데이터</strong>. 그리고 실제 아키텍처가 이 둘을 정확히 다르게 다룹니다.
        </p>
        <Code>{CODE_TRADERBOX}</Code>
        <p>
          <strong>시세는 왜 AP 성향인가.</strong> 시세 데이터의 가치는 <strong>신선도</strong>가
          전부예요. 1초 전 가격은 이미 낡은 데이터고, 유실된 틱을 재전송받는 사이에 새 틱이
          두 개 더 도착합니다. 그래서 Valkey Pub/Sub(<span className="icode">market.ticks</span>{' '}
          채널)로 <strong>흘려보내고 잊는</strong> 구조를 씁니다 — 구독자가 잠깐 끊겨 몇 틱을
          놓쳐도, 다음 틱이 곧 도착해 화면을 최신으로 되돌려요. 일종의 자기 치유죠. 대신
          "모든 틱이 반드시 전달된다"는 보장은 <strong>일부러</strong> 포기했습니다.
        </p>
        <p>
          <strong>인증·잔고성 데이터는 왜 CP 성향인가.</strong> 회원 정보, 토큰, 포트폴리오
          기록은 정반대예요 — 한 건의 유실이나 어긋남이 그대로 <strong>사고</strong>이고,
          "다음 값이 곧 덮어 준다"는 자기 치유도 없습니다. 그래서 트랜잭션이 보장되는{' '}
          <strong>단일 PostgreSQL</strong>(스키마-퍼-서비스)에 두고, 강한 일관성을 유지해요.
          수백 ms의 지연은 감수합니다 — 로그인이 0.2초 느린 건 아무도 기억 못 하지만, 잔고가
          틀린 건 모두가 기억하니까요.
        </p>
        <p>
          여기서 가져갈 관점은 하나입니다 — <strong>"우리 시스템은 CP인가 AP인가"는 잘못된
          질문</strong>이라는 것. 옳은 질문은 <strong>"이 데이터는 CP인가 AP인가"</strong>예요.
          같은 제품 안에서도 데이터마다 일관성 요구가 다르고, 좋은 아키텍처는 그 차이를
          채널부터(Pub/Sub vs 트랜잭션 DB) 다르게 설계한 아키텍처입니다.
        </p>
        <div className="tip">
          <b>실습 — 데이터 10가지 분류표</b> 우리 서비스의 데이터 10가지를 골라(예: 시세 틱,
          회원 정보, 관심종목, 알림 발송 이력, 라이브 채팅 메시지, 공시 아티클, 감사 로그...)
          각각 <strong>CP/AP 성향</strong>과 <strong>필요한 일관성 수준</strong>(선형성 /
          인과적 / read-your-writes면 충분 / 최종 일관성)을 표로 분류하고, <strong>근거를 한
          줄씩</strong> 적어 보세요. 근거의 형식은 섹션 4의 질문 그대로 — "이 값이 잠깐
          틀리면, 그걸 보고 누가 무엇을 결정하는가?" 팀원과 표를 비교해 보면, 같은 데이터를
          다르게 분류한 지점에서 가장 좋은 토론이 벌어질 거예요.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🌐 여기까지 왔다면</h3>
          <p className="muted">
            이제 "서버를 늘리면 되지"라는 말 뒤에 숨은 대가를 읽을 수 있게 됐어요. 부분
            실패라는 제3의 상태, 분할 시 C와 A 사이의 선택, 강함과 빠름을 맞바꾸는 일관성
            스펙트럼, 과반이 지켜 주는 리더의 유일성, 그리고 믿을 수 없는 시계까지 — 분산
            설계의 기본 어휘를 손에 쥐었습니다. 그런데 서비스 사이를 동기 호출로만 이으면,
            한 서비스의 장애가 사슬처럼 번지는 문제가 남아요. 호출을 <strong>메시지</strong>로
            바꿔 시스템을 느슨하게 잇는 법 — 다음{' '}
            <Link to="/learn/lv4-message-queue-event-driven"><strong>메시지 큐와 이벤트
            드리븐 — 시스템을 느슨하게 잇는 법</strong></Link> 코스에서 이어서 익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
