// 이 파일이 하는 일: "캐시와 NoSQL 맛보기" 코스 — 자주 쓰는 데이터를 가까이 두는
// 캐시의 원리(브라우저 캐시 → Redis)에서 시작해, 표가 아닌 문서로 저장하는
// NoSQL(MongoDB)까지 8개 섹션으로 안내하는 정적 학습 페이지.
// (캐시란 → 히트/미스 → Redis → 캐시 무효화 → NoSQL vs RDB → MongoDB → 선택 기준 → 304 실습)
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
// 텍스트 다이어그램은 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

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

// ── 텍스트 다이어그램 · 표 상수들 ──

const CODE_CACHE_LAYERS = `캐시 = "자주 쓰는 물건은 책상 위에" 전략

데이터를 가지러 가는 거리 (가까울수록 빠름):

[브라우저 캐시]   내 책상 서랍        ← 0.001초, 네트워크도 안 탐
[서버 캐시]       교무실 앞 사물함     ← 0.01초, 메모리에서 바로
[DB(PostgreSQL)]  도서관 서고         ← 0.1초~, 디스크를 뒤져서 찾음

같은 질문("인기 코스 목록 줘")이 1초에 100번 온다면?
→ 100번 다 서고까지 다녀올 필요 없죠.
  한 번 가져와서 사물함에 넣어 두고 꺼내 주면 됩니다.`;

const CODE_HIT_MISS = `캐시 히트(Hit) vs 캐시 미스(Miss)

요청: "인기 코스 목록 주세요"

  ① 캐시 확인 ──── 있다! ────> 바로 응답        = 캐시 히트 🎯
  │
  └─ 없다... ──> DB에 다녀옴 ──> 응답하면서
                                캐시에도 복사해 둠  = 캐시 미스

미스가 한 번 나야 그다음부터 히트가 나요.
"첫 손님만 오래 기다리고, 뒷손님은 전부 즉시" 구조.

히트율(hit rate) = 히트 횟수 ÷ 전체 요청
→ 히트율 90%면 DB 부담이 1/10로 줄어든 셈이에요.`;

const CODE_REDIS_KV = `Redis = 메모리에 사는 초고속 키-값(Key-Value) 저장소

포스트잇 벽이라고 생각하세요:

  키(제목)                값(내용)
  ─────────────────────────────────────────
  "course:popular"   →   "[네트워크, 코딩기초, ...]"
  "user:42:name"     →   "김미림"
  "visit:today"      →   "1024"

명령도 그만큼 단순해요:
  SET user:42:name "김미림"     ← 붙이기
  GET user:42:name              ← 떼서 읽기  → "김미림"
  EXPIRE user:42:name 60        ← 60초 뒤 자동 폐기(유통기한!)
  INCR visit:today              ← 숫자 1 올리기 (방문자 카운터)

왜 빠른가? 두 가지 이유:
1) 메모리(RAM)에 삼 — 디스크보다 수백~수천 배 빠른 동네
2) 해시로 찾음 — 키를 계산 한 번으로 위치를 바로 알아냄.
   책을 처음부터 뒤지는 게 아니라 "그 포스트잇은 항상 저 자리"인 것.`;

const CODE_INVALIDATION = `캐시 무효화(Invalidation) — 컴퓨터 과학의 2대 난제 중 하나

문제 상황:
  10:00  캐시에 저장: "인기 1위 = 네트워크의 이해"
  10:05  실제 DB 변경: 인기 1위가 "코딩 기초"로 바뀜
  10:06  사용자 접속 → 캐시가 답함: "1위는 네트워크의 이해" ← 낡은 정보!

우유 유통기한 비유:
  캐시는 냉장고 속 우유예요. 문제는 —
  "언제까지 신선한가?"를 미리 알 수 없다는 것.

  유통기한 짧게(10초)  → 신선하지만 DB에 자주 다녀옴 (캐시 효과 ↓)
  유통기한 길게(1시간) → 빠르지만 낡은 정보를 보여줄 위험 ↑

대표 전략 두 가지:
  TTL(Time To Live)  "60초 지나면 무조건 버려" — 단순, 최대 60초 낡음 감수
  즉시 삭제           DB를 바꾸는 순간 캐시도 지움 — 정확, 지울 곳을
                     하나라도 빼먹으면 유령 데이터가 남음(이게 어려움!)`;

const CODE_RDB_VS_NOSQL = `RDB(관계형 DB) vs NoSQL — 표 vs 자유 문서

RDB (PostgreSQL) = 엑셀 표
  ┌────┬─────────┬────────┐
  │ id │ name    │ email  │   ← 열(컬럼)이 미리 고정됨
  ├────┼─────────┼────────┤
  │ 1  │ 김미림  │ a@...  │   ← 모든 행이 같은 모양
  │ 2  │ 이수습  │ b@...  │
  └────┴─────────┴────────┘
  · 표끼리 관계(JOIN) 맺기가 강점
  · 스키마(모양)를 바꾸려면 표 전체를 손봐야 함

NoSQL (MongoDB) = 자유 양식 문서 뭉치
  { "name": "김미림", "email": "a@...",
    "badges": ["네트워크 수료", "Git 입문"] }
  { "name": "이수습", "phone": "010-...",
    "mentor": { "name": "박선배" } }        ← 문서마다 모양이 달라도 OK!
  · 중첩·배열이 자연스러움 (JSON 그대로)
  · 대신 문서끼리 엮는(JOIN 같은) 일은 약함`;

const CODE_MONGO_TASTE = `MongoDB 한 입 — SQL과 나란히 놓고 보기

같은 일                SQL (PostgreSQL)                MongoDB
─────────────────────────────────────────────────────────────
저장     INSERT INTO users (name)        db.users.insertOne(
           VALUES ('김미림');               { name: "김미림" })

조회     SELECT * FROM users             db.users.find(
           WHERE name = '김미림';           { name: "김미림" })

용어도 짝이 맞아요:
  테이블(table)  ↔  컬렉션(collection)
  행(row)        ↔  문서(document)
  열(column)     ↔  필드(field)

MongoDB의 문서는 사실상 JSON — 프론트엔드에서 늘 다루는
그 모양 그대로 저장하고 그대로 꺼냅니다.`;

const CODE_DECISION = `언제 뭘 쓰나 — 결정 기준 치트시트

질문 1. 데이터가 사라지면 큰일 나나?
  YES → PostgreSQL (수강 기록, 계정, 성적 — 원본은 RDB에)
  NO  → Redis 후보 (다시 계산하면 되는 것: 인기 목록, 세션, 카운터)

질문 2. 모양(스키마)이 자주 바뀌거나 문서마다 다른가?
  YES → MongoDB 후보 (설문 응답, 활동 로그, 형태가 들쑥날쑥한 것)
  NO  → PostgreSQL (모양이 고정이면 표가 최고)

질문 3. 관계·집계가 복잡한가? (JOIN, 트랜잭션)
  YES → PostgreSQL (은행 이체처럼 "전부 성공 or 전부 취소"가 필요할 때)

우리 학습 플랫폼이라면:
  회원·코스·수강기록  → PostgreSQL (원본, 관계 중요)
  로그인 세션·조회수   → Redis     (빠르고, 날아가도 재생성 가능)
  자유 양식 학습일지   → MongoDB   (학생마다 형식이 다르니까)

핵심: 셋은 경쟁자가 아니라 역할 분담. 원본은 RDB,
속도는 캐시, 자유 형식은 문서 DB — 같이 씁니다.`;

const CODE_304_LAB = `실습: 브라우저 캐시를 눈으로 확인하기 (상태 코드 304)

① 우리 플랫폼(edu.awesomedevapp.com)을 열고 F12 → Network 탭
② "Disable cache" 체크박스가 꺼져 있는지 확인
③ F5 새로고침 → 요청 목록에서 Status 열을 보세요

  Status 200        서버가 파일 전체를 보내줌 (첫 방문)
  Status 304        "Not Modified — 안 바뀌었어, 네가 가진 거 써!"
                    → 본문 없이 답장만 옴. 이게 브라우저 캐시 히트!
  (memory cache)    아예 서버에 묻지도 않고 메모리에서 꺼냄 — 최강 히트
  (disk cache)      디스크에 저장해 둔 사본 사용

④ 304 응답을 클릭 → Headers에서 찾아보세요:
  If-Modified-Since / ETag   ← 브라우저: "내 사본은 이 버전인데?"
  → 서버가 비교해 보고 같으면 304, 다르면 200 + 새 파일

⑤ 이번엔 Ctrl+Shift+R (강력 새로고침) → 전부 200으로 바뀌죠?
  "캐시 무시하고 전부 새로 줘"라고 강제한 거예요.
  섹션 4의 무효화를 사용자가 수동으로 한 셈!`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. 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: 'Redis' },
  { n: 4, label: '캐시 무효화' },
  { n: 5, label: 'NoSQL vs RDB' },
  { n: 6, label: 'MongoDB 한 입' },
  { n: 7, label: '선택 기준' },
  { n: 8, label: '실습: 304' },
];

export default function CacheNosqlPage() {
  return (
    <div>
      {/* 히어로: 캐시와 NoSQL, 두 가지 맛보기를 한 코스에 */}
      <div className="hero">
        <div className="eyebrow">Course · 서버와 데이터</div>
        <h1>캐시와 NoSQL 맛보기</h1>
        <p>
          자주 쓰는 물건을 책상 위에 두듯, 자주 쓰는 데이터를 가까이 두는 게 <strong>캐시</strong>예요.
          그리고 모든 데이터가 엑셀 표처럼 반듯하진 않다는 걸 인정하는 게 <strong>NoSQL</strong>이고요 —
          이 코스에서 둘 다 한 입씩 맛봅니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 50분</span>
          <span className="chip">직접 확인 실습 3개</span>
          <span className="chip">선수 지식: 데이터베이스 기초</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>에 두고, 가끔 보는 책은 책장에,
          거의 안 보는 책은 도서관에 두죠. 매번 도서관까지 걸어가면 공부가 안 되니까요.
          컴퓨터도 똑같은 전략을 써요 — 이게 <strong>캐시(cache)</strong>입니다.
        </p>
        <Code>{CODE_CACHE_LAYERS}</Code>
        <p>
          여러분은 이미 캐시의 수혜자예요. 어떤 사이트가 <strong>두 번째 방문부터 훨씬 빨리</strong> 열리는
          이유 — 브라우저가 이미지·CSS·JS 파일을 내 PC에 <strong>브라우저 캐시</strong>로 저장해 뒀다가
          다시 쓰기 때문이에요. 서버 쪽에서도 마찬가지로, DB까지 가지 않고 메모리에서 바로 답하는
          <strong> 서버 캐시</strong>를 둡니다. 위치만 다를 뿐 <strong>"멀리 안 가고 가까운 사본을 쓴다"</strong>는
          원리는 하나예요.
        </p>
        <div className="tip">
          <b>미리 보기</b> "내 브라우저가 정말 캐시를 쓰고 있나?"는 섹션 8에서
          개발자 도구로 직접 눈으로 확인할 거예요. 그때까지 이 페이지를 닫지 마세요!
        </div>
      </Section>

      <Section n={2} title="캐시 히트와 캐시 미스" sub="사물함에 있으면 히트, 없으면 미스">
        <p>
          캐시에 물어봤을 때 <strong>찾는 게 있으면 히트(hit)</strong>, <strong>없으면 미스(miss)</strong>예요.
          사물함을 열었는데 교과서가 있으면 히트 — 바로 수업 들어가면 되고,
          없으면 미스 — 도서관(DB)까지 다녀와야 하죠. 다녀오는 김에 사물함에 한 권 넣어 두면,
          다음 사람부터는 히트입니다.
        </p>
        <Code>{CODE_HIT_MISS}</Code>
        <p>
          그래서 캐시의 성적표는 <strong>히트율</strong>이에요. 히트율이 높을수록 DB는 한가해지고
          응답은 빨라집니다. 반대로 히트율이 낮으면? 사물함 확인하는 수고만 늘고 결국 매번
          도서관에 가는 셈이라, <strong>캐시를 두는 게 오히려 손해</strong>일 수도 있어요.
          "모든 걸 캐시하면 빨라진다"가 아니라 <strong>"자주 또 찾는 것"</strong>을 캐시해야 하는 이유죠.
        </p>
        <svg className="diagram" viewBox="0 0 640 250" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="캐시 히트와 미스 흐름도: 앱이 캐시에 요청해 있으면 바로 응답하고(히트), 없으면 DB에 다녀와 캐시에 복사한다(미스)">
          <rect x="24" y="64" width="104" height="72" rx="8" fill="var(--card)" stroke="var(--line)" strokeWidth="1.5" />
          <text x="76" y="94" textAnchor="middle" fontSize="15" fontWeight="600" fill="var(--ink)">앱</text>
          <text x="76" y="116" textAnchor="middle" fontSize="12" fill="var(--muted)">요청하는 쪽</text>
          <rect x="268" y="64" width="104" height="72" rx="8" fill="var(--card)" stroke="var(--line)" strokeWidth="1.5" />
          <text x="320" y="94" textAnchor="middle" fontSize="15" fontWeight="600" fill="var(--ink)">캐시</text>
          <text x="320" y="116" textAnchor="middle" fontSize="12" fill="var(--muted)">Redis · 메모리</text>
          <rect x="512" y="64" width="104" height="72" rx="8" fill="var(--card)" stroke="var(--line)" strokeWidth="1.5" />
          <text x="564" y="94" textAnchor="middle" fontSize="15" fontWeight="600" fill="var(--ink)">DB</text>
          <text x="564" y="116" textAnchor="middle" fontSize="12" fill="var(--muted)">PostgreSQL</text>
          <line x1="132" y1="84" x2="254" y2="84" stroke="var(--muted)" strokeWidth="2" />
          <path d="M254 79 L264 84 L254 89 Z" fill="var(--muted)" />
          <text x="198" y="74" textAnchor="middle" fontSize="12" fill="var(--ink)">① 요청</text>
          <line x1="268" y1="116" x2="142" y2="116" stroke="var(--teal)" strokeWidth="2" />
          <path d="M142 111 L132 116 L142 121 Z" fill="var(--teal)" />
          <text x="198" y="134" textAnchor="middle" fontSize="12" fill="var(--teal)">② 히트: 바로 응답</text>
          <line x1="376" y1="84" x2="498" y2="84" stroke="var(--amber)" strokeWidth="2" />
          <path d="M498 79 L508 84 L498 89 Z" fill="var(--amber)" />
          <text x="442" y="74" textAnchor="middle" fontSize="12" fill="var(--amber)">③ 미스: DB 조회</text>
          <line x1="512" y1="116" x2="386" y2="116" stroke="var(--amber)" strokeWidth="2" />
          <path d="M386 111 L376 116 L386 121 Z" fill="var(--amber)" />
          <text x="442" y="134" textAnchor="middle" fontSize="12" fill="var(--amber)">④ 응답 + 캐시에 복사</text>
          <line x1="24" y1="158" x2="616" y2="158" stroke="var(--line)" strokeWidth="1" />
          <circle cx="40" cy="177" r="5" fill="var(--teal)" />
          <text x="54" y="182" fontSize="13" fill="var(--ink)">히트: ① → ②  캐시에 있어 즉시 응답 (빠름)</text>
          <circle cx="40" cy="203" r="5" fill="var(--amber)" />
          <text x="54" y="208" fontSize="13" fill="var(--ink)">미스: ① → ③ → ④  첫 요청만 느리고, 다음부터는 히트</text>
          <text x="54" y="234" fontSize="12" fill="var(--muted)">히트율 = 히트 ÷ 전체 요청 — 히트율 90%면 DB 부담은 1/10</text>
        </svg>
        <p className="muted" style={{ fontSize: 12.5, textAlign: 'center', marginTop: 2 }}>그림: 캐시 히트/미스 흐름 — 미스가 한 번 나야 그다음부터 히트</p>
        <div className="warn">
          <b>흔한 오해</b> 캐시는 원본이 아니라 <strong>사본</strong>이에요. 캐시가 통째로 날아가도
          서비스는 (느려질 뿐) 죽지 않아야 정상입니다. 원본은 언제나 DB에 있어야 해요.
        </div>
      </Section>

      <Section n={3} title="Redis — 메모리에 사는 키-값 창고" sub="포스트잇 벽처럼 단순해서 빠르다">
        <p>
          서버 캐시의 대표 선수가 <strong>Redis</strong>예요. 구조는 놀랄 만큼 단순합니다 —
          <strong> 키(이름표)</strong>를 주면 <strong>값(내용)</strong>을 돌려주는 게 전부예요.
          벽에 붙은 포스트잇처럼 "제목 보고 → 떼서 읽기", 끝.
        </p>
        <Code>{CODE_REDIS_KV}</Code>
        <p>
          빠른 비결은 두 가지예요. 첫째, 데이터를 디스크가 아니라 <strong>메모리(RAM)</strong>에 둡니다.
          책장(디스크)이 아니라 <strong>손에 들고 있는</strong> 셈이라 꺼내는 속도의 차원이 달라요.
          둘째, 키를 <strong>해시</strong>라는 계산으로 찾아요. "김미림 자료가 어디 있지?" 하고
          뒤지는 게 아니라, 이름을 계산기에 넣으면 <strong>"3번 칸"</strong>이라고 위치가 바로 나오는
          방식이라 데이터가 백만 개여도 찾는 시간이 거의 같습니다.
        </p>
        <p>
          대신 메모리는 비싸고, <strong>서버가 꺼지면 내용이 날아갈 수 있어요</strong>.
          그래서 Redis에는 "날아가도 다시 만들 수 있는 것"(캐시, 세션, 실시간 카운터)을 담고,
          날아가면 안 되는 원본은 PostgreSQL에 두는 게 정석입니다 — 섹션 2의 경고 그대로죠.
        </p>
      </Section>

      <Section n={4} title="캐시 무효화는 왜 어려울까" sub="유통기한을 미리 알 수 없는 우유">
        <p>
          여기까지 들으면 캐시는 만능 같지만, 진짜 어려운 문제가 하나 남아 있어요.
          <strong> 원본이 바뀌었는데 캐시가 옛날 사본을 계속 주면?</strong> 사용자는 낡은 정보를
          보게 됩니다. 이 낡은 사본을 골라 버리는 일이 <strong>캐시 무효화(invalidation)</strong>인데,
          "컴퓨터 과학에서 어려운 문제 둘 중 하나"라는 농담이 있을 정도예요.
        </p>
        <Code>{CODE_INVALIDATION}</Code>
        <p>
          어려운 이유의 본질은 <strong>미래를 모른다</strong>는 거예요. 우유 유통기한은 병에 적혀
          있지만, "인기 코스 1위"가 언제 바뀔지는 아무도 몰라요. 그래서 실무는 완벽한 답 대신
          <strong> 타협</strong>을 골라요 — "최대 60초 낡은 건 봐준다"(TTL)거나, "바꾸는 쪽이
          책임지고 캐시도 지운다"(즉시 삭제)거나. 어느 쪽이든 <strong>얼마나 낡아도 되는 데이터인가</strong>를
          먼저 정해야 캐시 설계가 시작됩니다.
        </p>
        <div className="tip">
          <b>직접 확인해 보기</b> 우리 플랫폼 어딘가의 프로필 사진이나 공지를 바꿔 본 적 있나요?
          바꿨는데 화면에 예전 것이 보이면 — 방금 배운 <strong>무효화 문제를 실제로 목격</strong>한 거예요.
          그때 <span className="kbd">Ctrl</span>+<span className="kbd">Shift</span>+<span className="kbd">R</span>
          (강력 새로고침)을 누르면 해결되죠? 브라우저 캐시를 수동으로 무효화한 겁니다.
          다음에 이 상황을 만나면 "아, 캐시가 낡았구나"라고 진단해 보세요.
        </div>
      </Section>

      <Section n={5} title="NoSQL vs RDB — 표 vs 자유 문서" sub="모든 데이터가 엑셀에 들어맞진 않는다">
        <p>
          우리가 쓰는 <strong>PostgreSQL</strong>은 <strong>관계형 DB(RDB)</strong>예요.
          데이터를 <strong>표</strong>에 넣죠 — 열이 미리 정해져 있고, 모든 행이 같은 모양이어야 해요.
          반듯하고 검증하기 좋지만, 세상 모든 데이터가 표에 들어맞진 않아요.
          학생마다 형식이 제각각인 <strong>자유 양식 학습일지</strong>를 표에 구겨 넣으려면
          빈칸투성이 열이 수십 개 생겨 버립니다.
        </p>
        <Code>{CODE_RDB_VS_NOSQL}</Code>
        <p>
          <strong>NoSQL</strong>은 "Not Only SQL" — 표 말고 다른 모양도 쓰자는 큰 흐름이에요.
          방금 배운 Redis(키-값)도 NoSQL의 한 갈래고, 이번 섹션의 주인공인
          <strong> 문서(document) 방식</strong>도 그중 하나예요. 문서 방식은 데이터를
          <strong> JSON 문서</strong> 그대로 저장합니다. 규격 원고지(RDB)와
          자유 양식 리포트(NoSQL)의 차이 — 어느 쪽이 좋은 게 아니라 <strong>글의 종류가 다른</strong> 거예요.
        </p>
      </Section>

      <Section n={6} title="MongoDB 한 입" sub="JSON을 그대로 넣고 그대로 꺼내는 DB">
        <p>
          문서형 NoSQL의 대표가 <strong>MongoDB</strong>예요. React에서 늘 다루는
          객체(<span className="icode">{'{ name: "김미림" }'}</span>) 모양 그대로 저장하고,
          그 모양 그대로 꺼냅니다. 표로 바꿨다가 다시 객체로 조립하는 번역 과정이 없어서,
          프론트엔드 개발자에게 유난히 친숙하게 느껴지는 DB죠.
        </p>
        <Code>{CODE_MONGO_TASTE}</Code>
        <p>
          유연함의 대가도 있어요. 표처럼 <strong>모양을 강제하지 않으니</strong>,
          어떤 문서엔 <span className="icode">email</span>이 있고 어떤 문서엔 없는 상황을
          코드가 알아서 감당해야 해요. RDB라면 DB가 "그 열은 필수야!"라고 막아 줬을 실수를,
          NoSQL에선 개발자가 스스로 지켜야 합니다. <strong>자유엔 책임이 따른다</strong> — DB 세계도 똑같아요.
        </p>
        <div className="tip">
          <b>직접 확인해 보기</b> MongoDB가 없어도 문서 감각은 지금 바로 익힐 수 있어요.
          우리 플랫폼에서 <span className="kbd">F12</span> → <span className="kbd">Network</span> 탭 →
          새로고침 후 <span className="icode">api</span>로 시작하는 요청을 클릭 →
          <span className="kbd">Response</span>를 보세요. 서버가 보낸 <strong>JSON</strong>이 보이죠?
          MongoDB는 바로 저 모양을 통째로 저장하는 DB예요. 중괄호 안에 배열, 배열 안에 또 객체 —
          표로는 담기 힘든 <strong>중첩 구조</strong>를 직접 찾아보세요.
        </div>
      </Section>

      <Section n={7} title="언제 뭘 쓰나 — 결정 기준" sub="경쟁이 아니라 역할 분담">
        <p>
          "그래서 PostgreSQL, Redis, MongoDB 중에 뭐가 제일 좋아요?"는
          "숟가락, 포크, 젓가락 중에 뭐가 제일 좋아요?"와 같은 질문이에요.
          <strong> 음식(데이터)의 성격</strong>에 따라 고르는 겁니다. 실무에서 실제로 던지는
          질문 세 개로 정리해 볼게요.
        </p>
        <Code>{CODE_DECISION}</Code>
        <p>
          실제 서비스는 대부분 <strong>여럿을 같이</strong> 씁니다. 원본과 관계는 RDB가 지키고,
          그 앞에 Redis가 속도를 붙이고, 형태가 자유로운 데이터는 문서 DB가 받는 식이죠.
          우리 회사 AWESOMEDEV의 서비스들도 PostgreSQL을 원본 창고로 쓰면서 필요한 곳에
          캐시를 얹는 구조예요. 새 기능을 설계할 때 <strong>"이 데이터는 사라지면 안 되나?
          모양이 고정인가? 얼마나 자주 읽나?"</strong> 세 질문부터 던지는 습관을 들여 보세요.
        </p>
        <svg className="diagram" viewBox="0 0 640 310" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="저장소 역할 분담도: 서비스가 데이터 성격에 따라 PostgreSQL은 원본과 관계, Redis는 속도, MongoDB는 자유 형식 문서를 맡긴다">
          <rect x="210" y="24" width="220" height="56" rx="8" fill="var(--card)" stroke="var(--line)" strokeWidth="1.5" />
          <text x="320" y="47" textAnchor="middle" fontSize="15" fontWeight="600" fill="var(--ink)">우리 학습 플랫폼</text>
          <text x="320" y="68" textAnchor="middle" fontSize="12" fill="var(--muted)">데이터 성격에 맞는 창고 고르기</text>
          <polyline points="320,80 320,118 111,118 111,148" fill="none" stroke="var(--muted)" strokeWidth="2" />
          <path d="M105 148 L117 148 L111 156 Z" fill="var(--muted)" />
          <line x1="320" y1="80" x2="320" y2="148" stroke="var(--muted)" strokeWidth="2" />
          <path d="M314 148 L326 148 L320 156 Z" fill="var(--muted)" />
          <polyline points="320,80 320,118 529,118 529,148" fill="none" stroke="var(--muted)" strokeWidth="2" />
          <path d="M523 148 L535 148 L529 156 Z" fill="var(--muted)" />
          <rect x="16" y="156" width="190" height="108" rx="8" fill="var(--card)" stroke="var(--line)" strokeWidth="1.5" />
          <text x="111" y="182" textAnchor="middle" fontSize="12" fill="var(--muted)">사라지면 큰일 나나?</text>
          <text x="111" y="207" textAnchor="middle" fontSize="15" fontWeight="600" fill="var(--primary)">PostgreSQL</text>
          <text x="111" y="230" textAnchor="middle" fontSize="12" fill="var(--ink)">원본과 관계 지킴이</text>
          <text x="111" y="250" textAnchor="middle" fontSize="12" fill="var(--muted)">회원 · 코스 · 수강기록</text>
          <rect x="225" y="156" width="190" height="108" rx="8" fill="var(--card)" stroke="var(--line)" strokeWidth="1.5" />
          <text x="320" y="182" textAnchor="middle" fontSize="12" fill="var(--muted)">빠르게, 아주 자주 읽나?</text>
          <text x="320" y="207" textAnchor="middle" fontSize="15" fontWeight="600" fill="var(--teal)">Redis</text>
          <text x="320" y="230" textAnchor="middle" fontSize="12" fill="var(--ink)">속도 담당 (사본·캐시)</text>
          <text x="320" y="250" textAnchor="middle" fontSize="12" fill="var(--muted)">세션 · 조회수 · 인기 목록</text>
          <rect x="434" y="156" width="190" height="108" rx="8" fill="var(--card)" stroke="var(--line)" strokeWidth="1.5" />
          <text x="529" y="182" textAnchor="middle" fontSize="12" fill="var(--muted)">모양이 제각각인가?</text>
          <text x="529" y="207" textAnchor="middle" fontSize="15" fontWeight="600" fill="var(--amber)">MongoDB</text>
          <text x="529" y="230" textAnchor="middle" fontSize="12" fill="var(--ink)">자유 양식 문서 담당</text>
          <text x="529" y="250" textAnchor="middle" fontSize="12" fill="var(--muted)">학습일지 · 설문 응답</text>
          <text x="320" y="294" textAnchor="middle" fontSize="13" fill="var(--muted)">셋은 경쟁자가 아니라 역할 분담 — 원본은 RDB, 속도는 캐시, 자유 형식은 문서 DB</text>
        </svg>
        <p className="muted" style={{ fontSize: 12.5, textAlign: 'center', marginTop: 2 }}>그림: PostgreSQL · Redis · MongoDB 역할 분담</p>
      </Section>

      <Section n={8} title="실습 — 브라우저 캐시를 눈으로 확인하기" sub="상태 코드 304, 캐시 히트의 영수증">
        <p>
          약속대로, 섹션 1에서 배운 브라우저 캐시가 <strong>진짜로 일하고 있다는 증거</strong>를
          찾으러 갑니다. 열쇠는 상태 코드 <strong>304 Not Modified</strong> — 서버가
          "그 파일 안 바뀌었어, 네가 갖고 있는 사본 그대로 써!"라고 답하는 신호예요.
          파일 본문 없이 답장만 오니까 <strong>전송량이 거의 0</strong>입니다.
        </p>
        <Code>{CODE_304_LAB}</Code>
        <p>
          ④의 <span className="icode">ETag</span>가 재밌는 부분이에요. 브라우저가
          "내 사본의 버전 번호는 이거야"라고 함께 보내면, 서버는 파일을 다시 보내는 대신
          <strong> 버전만 비교</strong>해요. 같으면 304(그대로 써), 다르면 200(새 걸로 받아).
          섹션 4에서 배운 무효화 문제를 <strong>"버전 확인"</strong>이라는 방식으로 푸는 현장을
          지금 눈으로 본 겁니다.
        </p>
        <div className="tip">
          <b>직접 확인해 보기 (심화)</b> Network 탭 상단의 <strong>Size 열</strong>을 정렬해 보세요.
          <span className="icode">(memory cache)</span>·<span className="icode">(disk cache)</span>로 표시된
          요청과 실제 KB가 찍힌 요청의 <strong>Time</strong>을 비교하면, 캐시 히트가 얼마나 빠른지
          숫자로 보입니다. 스크린샷을 찍어 멘토에게 "여기가 히트, 여기가 미스"라고 설명해 보세요 —
          설명이 되면 이 코스는 통과예요.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🗄️ 여기까지 왔다면</h3>
          <p className="muted">
            이제 여러분은 <strong>캐시</strong>(가까운 사본으로 속도 벌기, 히트/미스, 무효화의 어려움)와{' '}
            <strong>NoSQL</strong>(표 대신 문서, Redis와 MongoDB, 역할 분담)을 한 입씩 맛봤어요.
            핵심은 하나 — <strong>데이터의 성격에 맞는 창고를 고른다</strong>는 것.
            표의 세계를 더 단단히 다지고 싶다면 <Link to="/learn/sql"><strong>SQL 첫걸음</strong></Link> 코스로,
            이 데이터들이 어떤 길로 오가는지 궁금하다면 <Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스로
            이어 가세요. 그리고 과제 — 우리 플랫폼에서 <strong>"캐시하면 좋을 데이터 후보 3개"</strong>를 골라
            섹션 7의 세 질문으로 검증한 뒤 멘토와 이야기해 보세요.
          </p>
        </div>
      </div>
    </div>
  );
}
