// 이 파일이 하는 일: "시스템 설계 훈련 3 — 뉴스피드와 타임라인" 코스 — 읽기 폭발형 시스템의
// 대표 문제인 뉴스피드를 재료로, 푸시 vs 풀, 팬아웃 파이프라인, 셀럽 문제와 하이브리드 전략,
// 랭킹·카운터 같은 부속 시스템까지 7개 섹션으로 안내하는 정적 학습 페이지. 특급 과정의
// 시스템 설계 3부작을 마무리하며, 개별 문제 풀이를 넘어 '문제 유형별 첫수'로 일반화한다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

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

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

const CODE_REQUIREMENTS = `[요구사항을 숫자로 고정한다]
- 피드 로드: p95 200ms 이내
- 게시 반영: 게시 후 수 초 내 팔로워 피드에 노출
- 규모: 일 게시물 수억 건, 활성 유저 수천만

[증폭 계산 — 쓰기 1건이 만드는 읽기 요구]
평균 팔로워를 200명이라 하면,
  게시 1건 → 200명의 피드에 '나타나야' 한다
일 게시물 2억 건 × 200 = 일 400억 개의 피드 항목 노출 요구

읽기:쓰기가 수백 대 일을 넘는 전형적인 '읽기 폭발형' 시스템.
그래서 설계의 중심 질문은 하나로 좁혀집니다:
"N개 소스의 병합 정렬 비용을 — 읽을 때 낼 것인가, 쓸 때 낼 것인가?"`;

const CODE_PULL = `-- 풀(pull) 모델: 요청이 올 때마다 그 자리에서 조립한다
-- ① 내가 팔로우하는 사람 목록 (N명)
SELECT followee_id FROM follow WHERE follower_id = :me;

-- ② 그들의 최신 글을 모아 온다
SELECT * FROM post
 WHERE author_id IN ( ...N명... )
 ORDER BY created_at DESC
 LIMIT 50;

-- ③ 애플리케이션에서 시간순 병합 후 상위 50건 반환

읽기 비용: 팔로잉 수 N에 비례 — N=1,000이면 1,000개 소스의 병합
쓰기 비용: INSERT 1건이 전부 — 세상에서 제일 저렴
저장 비용: 글은 원본 한 벌만 — 중복 없음`;

const CODE_FANOUT = `// 푸시(push) 모델: 쓸 때 뿌려 둔다 — fan-out on write
// ① 게시 API — 저장하고 이벤트만 발행, 사용자는 즉시 응답을 받는다
postRepository.save(post);
kafka.send("feed.post.created", new PostCreated(post.id(), authorId));

// ② 팬아웃 워커 — 팔로워 목록을 청크로 나눠 각자의 피드함에 적재
List<Long> followers = followRepository.findFollowerIds(authorId);
for (List<Long> chunk : partition(followers, 1000)) {
    for (Long uid : chunk) {
        // 유저별 피드함 = Valkey sorted set (score=게시 시각, member=글 id)
        valkey.zadd("feed:" + uid, post.createdAt(), post.id());
        valkey.zremrangebyrank("feed:" + uid, 0, -801); // 최근 800건만 유지
    }
}

// ③ 피드 조회 — 이미 정렬돼 있으니 그냥 읽는다
List<Long> ids = valkey.zrevrange("feed:" + me, 0, 49);

// 읽기 비용: 팔로잉 수와 무관하게 사실상 상수 — 200ms 목표는 이쪽이 지킨다
// 쓰기 비용: 팔로워 수만큼의 zadd — 청구서가 쓰기 쪽으로 이동했다`;

const CODE_CELEB = `[팬아웃 폭발 — 봉투 뒤 계산]
팔로워 1,000만 계정이 글 1건을 올리면
  → zadd 1,000만 번
  → 워커가 초당 1만 건씩 처리해도 약 17분
  → 그동안 워커는 이 글에 붙잡혀 다른 유저의 팬아웃까지 밀린다
  → "게시 후 수 초 내 반영" 요구가 무너진다

[하이브리드 전략]
일반 유저(팔로워 < 임계값): 푸시 — 피드함에 미리 적재
셀럽(팔로워 >= 임계값):     풀  — 팬아웃하지 않고, 읽는 쪽이 가져간다

피드 조회 = 내 피드함(푸시분)
          + 내가 팔로우한 셀럽들의 최신 글(풀분)
          → 두 목록을 시간순으로 병합해 반환`;

const CODE_THRESHOLD = `[경계값을 '데이터로' 정하는 법 — 감이 아니라 측정]
① 팔로워 수 분포를 뽑는다 (p50 / p99 / p99.9 — 대개 극단적 롱테일)
② 팬아웃 지연 SLO를 정한다 — 예: 게시 후 5초 내 95% 적재 완료
③ 워커 처리량으로 역산한다
   초당 2만 zadd 처리 가능 × 5초 = 팔로워 10만 명까지는 푸시로 감당
④ 임계값 10만으로 시작 → 팬아웃 지연 대시보드를 보며 조정

포인트: 임계값은 한 번 정하는 상수가 아니라,
워커 증설·트래픽 변화에 따라 움직이는 '운영 파라미터'입니다.`;

const CODE_RANKING = `// 점수 기반 랭킹의 기본 골격 — 시간순을 넘어서
score(post, viewer) =
    freshness(post.age)               // 신선도: 시간 감쇠(오래될수록 점수 하락)
  × affinity(viewer, post.author)     // 친밀도: 이 작성자에게 얼마나 반응해 왔나
  × engagement(post)                  // 참여도: 좋아요·댓글·체류 시간

// 랭킹을 '어디서' 계산하나 — 지연 vs 신선도 트레이드오프
미리 계산(팬아웃 때 점수까지):  조회는 빠르다 / 점수가 금방 낡는다
요청 시 계산(조회 때 재랭킹):   항상 신선하다 / 후보 수백 건 스코어링 지연

// 흔한 절충
팬아웃은 시간순으로 적재해 두고, 조회 때 상위 후보 200건만 재랭킹

// ML 랭킹은 인터페이스 뒤로 분리한다
rank(candidates, viewer) -> sortedList
피드 팀은 '후보 생성'까지, 랭킹 팀은 이 함수 안쪽만 —
모델이 룰 기반이든 딥러닝이든 피드 시스템은 몰라도 되게.`;

const CODE_COUNTER = `-- 좋아요 카운터의 핫키 문제
UPDATE post SET like_count = like_count + 1 WHERE id = :hot;
-- 인기 글 하나에 초당 수만 번 → 같은 행(row)의 잠금 경합 → DB가 먼저 죽는다

// 패턴: 캐시에서 집계하고, 주기적으로 DB에 플러시
valkey.incr("like:" + postId);          // 메모리 연산 — 초당 수만 건도 거뜬

// 별도 플러셔가 5초마다 한 번:
long delta = valkey.getdel("like:" + postId);
jdbc.update("UPDATE post SET like_count = like_count + ? WHERE id = ?",
            delta, postId);

// 미디어는 피드에 싣지 않는다
게시물 레코드에는 image_url(오브젝트 스토리지 키)만 저장,
실제 바이트는 S3류 스토리지 + CDN이 서빙 —
피드 API는 끝까지 '작은 JSON'으로 유지한다.`;

const CODE_PLAYBOOK = `[3부작의 공통 뼈대 — 어떤 문제든 이 순서로]
① 요구사항 고정   : 지연·신선도·규모를 '숫자'로 못 박는다
② 봉투 뒤 산정    : QPS·저장량·증폭 계수를 어림 계산한다
③ 저장/전달 모델  : 비용을 읽기 쪽에 낼지 쓰기 쪽에 낼지 정한다
④ 예외 격리      : 핫키·셀럽·폭주 — 평균이 아니라 '꼬리'를 따로 설계한다

[문제 유형별 첫수]
읽기 편중형 (뉴스피드·상품 목록)     → 캐시와 사전 계산(팬아웃)부터 본다
쓰기 편중형 (로그 수집·시세 적재)    → 파티셔닝과 배치 적재부터 본다
연결 유지형 (채팅·알림·실시간 시세)  → 커넥션 관리와 fan-out 채널부터 본다

낯선 문제는 대부분 이 셋의 '조합'입니다.
"이 문제는 어느 유형이 지배적인가?" — 이것이 언제나 첫 질문.`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. 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: '셀럽 문제' },
  { n: 5, label: '랭킹과 개인화' },
  { n: 6, label: '미디어와 카운터' },
  { n: 7, label: '3부작 총정리' },
];

export default function Lv4SystemDesignNewsfeedPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 특급 과정</div>
        <h1>시스템 설계 훈련 3<br />— 뉴스피드와 타임라인</h1>
        <p>
          📰 트위터·인스타그램의 첫 화면, 그 <strong>피드</strong>는 어떻게 200ms 만에
          뜰까요? 쓰기 1건이 팔로워 수만큼의 읽기 요구로 증폭되는{' '}
          <strong>읽기 폭발형 시스템의 끝판왕</strong>이 바로 뉴스피드입니다. 푸시 vs 풀,
          팬아웃, 셀럽 문제, 랭킹까지 — 이 문제 하나를 끝까지 밀어붙이며{' '}
          <strong>시스템 설계 3부작</strong>을 완성합니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 85분</span>
          <span className="chip">실습: 푸시·풀 피드 API 실측 비교</span>
          <span className="chip">선수 지식: 시스템 설계 훈련 1·2</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="쓰기 1건이 팔로워 수만큼의 읽기로 증폭된다">
        <p>
          피드의 정의부터 정확히 해 둡시다. 내가 <strong>N명을 팔로우</strong>하고 있다면,
          내 피드는 <strong>N개의 글 소스를 시간순으로 병합 정렬한 결과</strong>예요.
          N이 3명이면 데이터베이스 쿼리 한 방이면 됩니다. 그런데 N이 1,000명이고,
          그런 유저가 수천만 명이고, 모두가 하루에도 수십 번 새로고침을 한다면요?
          단순한 문제가 규모 때문에 어려워지는 — 시스템 설계의 전형적인 순간입니다.
        </p>
        <p>
          이 문제의 성격을 결정짓는 건 <strong>증폭(amplification)</strong>이에요.
          글쓰기는 1건인데, 그 글은 팔로워 <strong>모두의 피드에 나타나야</strong> 하죠.
          쓰기 1건이 읽기 수백 건의 요구로 부풀어 오르는 구조 — 훈련 1·2에서처럼
          먼저 요구사항을 숫자로 고정하고 봉투 뒤 계산을 해 봅시다.
        </p>
        <Code>{CODE_REQUIREMENTS}</Code>
        <p>
          마지막 줄의 질문이 이 코스 전체를 관통합니다. 병합 정렬이라는 비용은
          사라지지 않아요. 다만 <strong>읽을 때 낼지, 쓸 때 낼지</strong> 선택할 수
          있을 뿐이죠. 읽을 때 내는 방식이 섹션 2의 <strong>풀 모델</strong>,
          쓸 때 내는 방식이 섹션 3의 <strong>푸시 모델</strong>입니다.
        </p>
        <div className="tip">
          {/* 학습 포인트: 설계 문제는 '비용을 없애는' 게 아니라 '비용을 옮기는' 게임임을 먼저 심는다 */}
          <b>관점 하나</b> 시스템 설계에서 공짜는 없습니다. 캐시도, 팬아웃도, 복제도
          전부 "비용을 <strong>언제·어디서</strong> 낼 것인가"를 옮기는 장치예요.
          이 관점을 쥐고 있으면 처음 보는 아키텍처도 "아, 이건 비용을 저쪽으로 옮겼구나"로
          읽어 낼 수 있습니다.
        </div>
      </Section>

      <Section n={2} title="풀 모델 — 읽을 때 조립하기" sub="요청 시 팔로잉 목록 → 최신 글 → 병합">
        <p>
          가장 정직한 방법부터요. <strong>풀(pull) 모델</strong>은 피드 요청이 올 때마다
          그 자리에서 조립합니다. 내 팔로잉 목록을 조회하고 → 각자의 최신 글을 가져와 →
          시간순으로 병합해 돌려주는 거예요. 저장된 피드라는 건 어디에도 없고,
          매번 즉석에서 만들어집니다.
        </p>
        <Code>{CODE_PULL}</Code>
        <p>
          장점은 분명해요. <strong>쓰기가 저렴하고</strong>(글 INSERT 한 번이 전부),
          글이 원본 한 벌만 존재하니 <strong>저장 중복이 없습니다</strong>. 수정·삭제도
          원본만 고치면 모두의 피드에 즉시 반영되죠. 문제는 읽기입니다.{' '}
          <strong>읽기 지연이 팔로잉 수 N에 비례</strong>해요. 팔로잉이 열 명인 유저는
          빠르지만, 천 명인 유저의 요청은 천 개 소스를 긁어 병합해야 하니
          p95 200ms 목표가 위태로워집니다.
        </p>
        <div className="tip">
          {/* 학습 포인트: "무조건 큰 회사 아키텍처"가 아니라 규모에 맞는 선택을 가르친다 */}
          <b>그래도 풀부터</b> 소규모 서비스라면 <strong>풀 모델 + 캐시</strong>로
          꽤 오래 버틸 수 있어요. 유저가 수만 명 수준이면 IN 쿼리 한 방 + 결과 캐시
          몇 초로 충분합니다. "트위터가 하니까 우리도 팬아웃"은 설계가 아니라 모방이에요.
          지금 규모의 숫자를 먼저 계산하고, 그 숫자가 시키는 걸 하세요.
        </div>
      </Section>

      <Section n={3} title="푸시 모델 — 쓸 때 뿌려두기" sub="fan-out on write: 게시 시점에 팔로워의 피드함으로">
        <p>
          읽기가 병목이라면 비용을 쓰기 쪽으로 옮깁니다. <strong>푸시(push) 모델</strong>은
          글이 게시되는 순간, <strong>팔로워 각자의 피드함(inbox)에 글을 밀어 넣어</strong>{' '}
          둬요. 이걸 <strong>fan-out on write</strong>(쓰기 시점 팬아웃)라고 부릅니다.
          조회 시점엔 이미 정렬된 내 피드함을 읽기만 하면 되니, 팔로잉이 몇 명이든
          응답 시간이 일정해지죠.
        </p>
        <p>
          피드함 저장소로는 <strong>유저별 정렬 리스트</strong>가 필요한데,{' '}
          <strong>Valkey의 sorted set</strong>이 정확히 이 모양이에요. 게시 시각을
          score로 쓰면 삽입만으로 시간순이 유지되고, 조회는{' '}
          <span className="icode">zrevrange</span> 한 번입니다. 피드함은 어차피 최근
          글만 보여 주면 되니 유저당 수백 건으로 잘라 메모리를 아끼고요.
        </p>
        <p>
          중요한 건 팬아웃을 <strong>비동기로</strong> 돌린다는 점이에요. 게시 API가
          팔로워 수만 명의 피드함을 직접 다 쓰고 응답한다면 글 하나 올리는 데 수십 초가
          걸리겠죠. 그래서 게시 API는 글만 저장하고 <strong>이벤트를 발행</strong>한 뒤
          즉시 응답하고, 뒤에서 <strong>팬아웃 워커</strong>가 이벤트를 받아 피드함을
          채웁니다. 우리 스택으로 말하면 <strong>Kafka</strong>가 그 이벤트 통로예요.
        </p>
        <Code>{CODE_FANOUT}</Code>
        <div className="tip">
          {/* 학습 포인트: 요구사항의 "수 초 내 반영"이 왜 '비동기 허용'의 근거인지 연결한다 */}
          <b>요구사항 다시 보기</b> 섹션 1에서 게시 반영 목표를 "수 초 내"로 잡았죠.
          "즉시"가 아니라요. 이 느슨함이 비동기 팬아웃을 허용하는 근거입니다.
          요구사항의 한 줄이 아키텍처의 큰 갈래를 결정하는 장면 — 요구사항을 숫자로
          못 박는 습관이 왜 중요한지 여기서 드러나요.
        </div>
        <div className="warn">
          <b>공짜가 아니다</b> 푸시 모델의 청구서는 <strong>저장 중복</strong>(같은 글 id가
          팔로워 수만큼 복사됨)과 <strong>팬아웃 쓰기량</strong>입니다. 팔로워 200명까진
          귀엽지만, 200만 명이라면요? 그 계산이 다음 섹션의 주인공, 셀럽 문제입니다.
        </div>
      </Section>

      <Section n={4} title="셀럽 문제 — 하이브리드 전략" sub="팔로워 1천만 계정의 글 하나가 만드는 폭발">
        <p>
          푸시 모델의 아킬레스건은 <strong>팔로워 분포의 롱테일</strong>이에요.
          대부분의 유저는 팔로워가 수백 명이지만, 꼬리 끝엔 팔로워 수백만·수천만의{' '}
          <strong>셀럽</strong>이 있습니다. 그 계정의 글 <strong>하나</strong>가
          팬아웃 파이프라인에 어떤 일을 벌이는지 계산해 봅시다.
        </p>
        <Code>{CODE_CELEB}</Code>
        <p>
          그래서 실전 답은 <strong>하이브리드</strong>입니다. <strong>일반 유저는
          푸시</strong>로 피드함에 미리 적재하고, <strong>셀럽은 팬아웃하지 않아요</strong>.
          대신 피드를 <strong>읽는 시점에</strong>, 내 피드함(푸시분)과 내가 팔로우한
          셀럽들의 최신 글(풀분)을 병합합니다. 셀럽은 전체 유저의 극소수이므로
          풀 쪽 병합 대상은 몇 개 소스에 불과하고, 셀럽 최신 글은 어차피 수백만 명이
          읽으니 <strong>캐시 적중률이 극도로 높습니다</strong>. 푸시의 읽기 성능과
          풀의 쓰기 절약을 각자 잘하는 자리에 배치한 거예요.
        </p>
        <p>
          남는 질문은 <strong>경계값</strong>이죠. 팔로워 몇 명부터 셀럽으로 취급할
          것인가? 이걸 감으로 정하면 설계가 아니라 점술입니다. 데이터로 정하는
          절차는 이렇습니다.
        </p>
        <Code>{CODE_THRESHOLD}</Code>
        <div className="tip">
          {/* 학습 포인트: 훈련 1·2에서 반복해 온 '꼬리를 따로 설계한다' 원칙의 재등장을 짚는다 */}
          <b>패턴의 재등장</b> "평균은 문제없는데 극단값이 시스템을 부순다"는 구조,
          낯익지 않나요? 캐시를 부수는 핫키, 파티션을 기울이는 인기 상품 — 앞선
          훈련들에서 만난 그 패턴의 뉴스피드 버전이 셀럽 문제입니다. 대규모 시스템
          설계란 결국 <strong>평균이 아니라 꼬리를 설계하는 일</strong>이에요.
        </div>
      </Section>

      <Section n={5} title="랭킹과 개인화 — 시간순을 넘어서" sub="신선도 × 친밀도 × 참여도, 그리고 계산 위치">
        <p>
          여기까지의 피드는 <strong>시간순</strong>이었어요. 구현이 단순하고 예측
          가능하지만 한계가 있습니다. 새벽에 올라온 절친의 소식이, 방금 올라온
          광고성 글에 밀려 화면 밖으로 사라지죠. 팔로잉이 많을수록 피드는 "최신이지만
          나와 무관한 글"로 채워집니다. 그래서 큰 피드 서비스들은{' '}
          <strong>점수 기반 랭킹</strong>으로 넘어가요. 기본 구조는 세 축의 곱입니다.
        </p>
        <Code>{CODE_RANKING}</Code>
        <p>
          설계자가 정해야 할 건 수식 자체보다 <strong>랭킹을 어디서 계산하느냐</strong>예요.
          팬아웃 때 <strong>미리 계산</strong>해 두면 조회는 빠르지만, 참여도는 계속
          변하니 점수가 금방 낡습니다. <strong>요청 시 계산</strong>하면 항상 신선하지만
          후보 수백 건을 매 요청마다 스코어링하는 지연을 떠안죠. 흔한 절충은 피드함
          적재는 시간순으로 하되, <strong>조회 시 상위 후보 일부만 재랭킹</strong>하는
          것 — 지연과 신선도 사이의 비용 재배치가 여기서도 반복됩니다.
        </p>
        <div className="tip">
          {/* 학습 포인트: 시스템 경계 설계 = 조직 경계 설계라는 특급 과정다운 시야를 심는다 */}
          <b>인터페이스로 팀을 나눈다</b> ML 랭킹 고도화는 끝이 없는 세계라, 잘하는
          조직은 이걸 <strong>다른 팀의 문제로 분리</strong>합니다. 피드 시스템은
          "후보 목록을 넣으면 정렬된 목록이 나온다"는 <span className="icode">rank()</span>{' '}
          인터페이스만 알고, 그 안쪽이 룰 기반인지 딥러닝인지는 몰라도 되게요.
          시스템의 경계를 긋는 일이 곧 조직의 경계를 긋는 일 — 특급 과정에서
          계속 만나게 될 시야입니다.
        </div>
      </Section>

      <Section n={6} title="미디어와 카운터 — 부속 시스템들" sub="피드 본체를 가볍게 지키는 주변 설계">
        <p>
          피드 본체가 서도 부속 시스템이 무너지면 소용없어요. 첫째,{' '}
          <strong>이미지·영상</strong>. 미디어 바이트를 피드 응답에 실으면 API가
          순식간에 무거워집니다. 원칙은 단순해요 —{' '}
          <strong>오브젝트 스토리지 + CDN이 바이트를 서빙</strong>하고, 피드에는{' '}
          <strong>URL만</strong> 흘립니다. 둘째, <strong>좋아요 카운터</strong>.
          인기 글 하나에 좋아요가 초당 수만 건 몰리면, 같은 행을 갱신하려는 트랜잭션이
          잠금 앞에 줄을 서는 <strong>핫키 문제</strong>가 됩니다.
        </p>
        <Code>{CODE_COUNTER}</Code>
        <p>
          카운터 패턴의 교환 조건도 정확히 알아 둡시다. 캐시 집계 + 주기 플러시는
          쓰기 폭주를 흡수하는 대신, <strong>플러시 주기만큼의 오차</strong>를
          허용합니다. 좋아요 수가 5초쯤 덜 보여도 아무도 다치지 않으니 괜찮은
          거래예요. 반대로 결제 잔액이라면 절대 이 패턴을 쓰면 안 되겠죠 —{' '}
          <strong>정확성 요구가 패턴을 고릅니다</strong>.
        </p>
        <div className="tip">
          {/* 학습 포인트: 배운 패턴을 자사 프로덕트(traderbox)에 투영해 보는 전이 훈련 */}
          <b>우리 프로덕트에 대입해 보면</b> traderbox의 아티클·공시 피드를
          떠올려 보세요. 종목을 '팔로우'하는 유저에게 새 공시를 흘린다면 —
          삼성전자 공시 한 건은 수십만 관심 유저에게 가야 하는 <strong>셀럽 글</strong>이고,
          조회수·북마크 수는 <strong>핫키 카운터</strong>이며, 첨부 문서는{' '}
          <strong>URL만 피드에 싣는 미디어</strong>입니다. 이 코스의 설계가 SNS만의
          얘기가 아니라, 구독·알림이 있는 모든 서비스의 뼈대라는 뜻이에요.
        </div>
      </Section>

      <Section n={7} title="3부작 총정리 — 설계 감각의 일반화" sub="문제 유형별 첫수, 그리고 환원 훈련">
        <p>
          세 번의 훈련이 끝났습니다. 문제는 매번 달랐지만, 풀이의 뼈대는 매번
          같았어요. 요구사항을 숫자로 고정하고 → 봉투 뒤 계산으로 규모를 산정하고 →
          비용을 읽기와 쓰기 중 어느 쪽에 낼지 저장/전달 모델을 고르고 → 마지막으로
          평균이 아닌 <strong>꼬리</strong>(핫키·셀럽·폭주)를 따로 격리했죠.
          이 뼈대를 문제 유형별 '첫수'와 함께 한 장으로 정리합니다.
        </p>
        <Code>{CODE_PLAYBOOK}</Code>
        <p>
          면접장에서든 실무 회의에서든, 낯선 설계 문제를 받았을 때 백지에서 시작하지
          마세요. <strong>"이 문제는 내가 아는 어떤 문제와 닮았지?"</strong> —
          읽기가 폭발하는가(피드형), 쓰기가 폭주하는가(수집형), 연결을 오래 쥐고
          있어야 하는가(실시간형). 지배적인 유형을 찾으면 첫수가 나오고, 첫수가
          나오면 나머지는 이 3부작에서 연습한 트레이드오프 협상입니다.
          낯선 문제를 <strong>기존 패턴으로 환원하는 힘</strong>, 그게 3부작이
          남기려던 근육이에요.
        </p>
        <div className="warn">
          <b>패턴 맹신 금지</b> 환원은 출발점이지 결론이 아닙니다. "뉴스피드니까
          무조건 팬아웃"이 아니라, <strong>지금 이 서비스의 숫자</strong>를 계산한
          뒤에 패턴을 고르세요. 섹션 2에서 봤듯, 작은 규모에선 풀 + 캐시가
          정답인 경우가 훨씬 많습니다. 패턴은 지도이지 목적지가 아니에요.
        </div>
        <div className="tip">
          <b>실습 안내</b> 팔로우 관계 <strong>1만 건</strong>을 시드 데이터로 넣고,{' '}
          <strong>푸시·풀 두 방식의 피드 API를 모두</strong> 구현해 보세요. 그리고
          팔로워 수 구간별로 <strong>게시 지연</strong>(글 저장 → 피드함 반영 완료)과{' '}
          <strong>조회 지연</strong>(피드 요청 → 응답)을 실측해 표로 비교합니다.
          "풀은 읽기가, 푸시는 쓰기가 팔로워 수에 비례해 늘어난다"를 그래프로
          직접 확인하는 순간, 이 코스의 모든 문장이 자기 경험이 됩니다.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>📰 3부작을 완주했다면</h3>
          <p className="muted">
            요구사항 → 산정 → 모델 선택 → 꼬리 격리. 이제 낯선 시스템 문제 앞에서
            무엇부터 계산해야 하는지 아는 개발자가 됐어요. 그런데 설계 실력이 커질수록
            새로운 문제가 생깁니다. 내 머릿속의 트레이드오프를 <strong>팀은 어떻게
            아나요?</strong> 6개월 뒤에 합류한 동료는 "왜 여기만 하이브리드지?"라는
            질문의 답을 어디서 찾죠? 좋은 설계는 결정을 내리는 것에서 끝나지 않고,{' '}
            <strong>결정을 기록하고 설득하는 것</strong>까지입니다. 다음{' '}
            <Link to="/learn/lv4-tech-leadership-adr"><strong>기술 리더십과 ADR —
            결정을 설계하고 기록하는 사람</strong></Link> 코스에서, 설계자의 마지막
            역량을 이어서 익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
