// 이 파일이 하는 일: "성능 측정과 프로파일링" 코스 — 감으로 하는 최적화가 왜 실패하는지에서
// 출발해, 백분위 지표·부하 테스트·JVM 프로파일링·병목 진단·상시 관측·성능 예산까지 7개 섹션으로
// 안내하는 정적 학습 페이지. 프레이밍은 "추측하지 말고 측정하라" — 서비스를 이미 만들어 본
// 수습이, 잘 만드는 법(설계·성능·보안)으로 넘어가는 고급 과정의 성능 편이다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

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

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

const CODE_LOOP = `느낌으로 하는 최적화              측정으로 하는 최적화
─────────────────────           ─────────────────────
"여기가 느릴 것 같아"             ① 측정: 지금 얼마나 느린가? (기준선)
→ 일주일 코드 수술                ② 분석: 어디서 시간을 쓰는가? (프로파일)
→ ...별로 안 빨라짐               ③ 개선: 가장 큰 병목 '하나만' 고친다
→ 코드만 복잡해짐                 ④ 재측정: 정말 빨라졌나? 숫자로 확인
                                 → ①로 돌아가 반복

핵심: 병목은 대개 예상과 다른 곳에 있다.
"쿼리가 느리겠지" 했는데 직렬화가 범인이고,
"코드가 느리겠지" 했는데 커넥션 풀 대기가 범인인 일이 흔하다.
그래서 순서가 생명 — 고치기 전에 반드시 잰다.`;

const CODE_PERCENTILE = `어떤 API의 응답 시간 100건 (ms):
  대부분 50ms 근처, 그런데 5건이 2,000ms

  평균  = 약 147ms   ← "음, 나쁘지 않네?" (거짓말)
  p50   = 52ms       ← 절반의 사용자는 52ms 이하
  p95   = 1,980ms    ← 상위 5%는 2초 가까이 기다림!
  p99   = 2,050ms    ← 100명 중 1명은 2초 넘게 기다림

p95 = "요청 100건을 느린 순으로 세웠을 때, 95번째까지는 이 시간 안에 끝났다"
평균은 소수의 아주 느린 요청을 다수의 빠른 요청 속에 숨겨 버린다.
사용자가 체감하는 '느리다'는 평균이 아니라 꼬리(p95·p99)에서 온다.
하루 100만 요청이면 p99 밖의 1%만 해도 1만 건 — 결코 적지 않다.`;

const CODE_K6 = `// load-test.js — k6로 핵심 API에 현실적인 부하 만들기
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 10 },   // ① 웜업: 가볍게 몸 풀기 (JIT·캐시 덥히기)
    { duration: '1m',  target: 100 },  // ② 램프업: 서서히 100 VU까지 올리기
    { duration: '3m',  target: 100 },  // ③ 지속: 100 VU를 3분간 유지 (본 측정 구간)
    { duration: '30s', target: 0 },    // ④ 램프다운
  ],
  thresholds: {
    http_req_duration: ['p(95)<300'],  // p95가 300ms를 넘으면 테스트 실패!
    http_req_failed:   ['rate<0.01'],  // 에러율 1% 미만
  },
};

export default function () {
  const res = http.get('http://localhost:8080/api/market/quotes/005930');
  check(res, { 'status 200': (r) => r.status === 200 });
  sleep(1);  // 실제 사용자처럼 요청 사이에 숨 고르기
}

// 실행: k6 run load-test.js
// 끝나면 p90·p95 응답시간, 초당 요청수(TPS), 에러율이 요약되어 나온다.`;

const CODE_PROFILER = `# async-profiler — 돌고 있는 JVM에 붙여서 30초간 CPU 샘플링
./asprof -d 30 -f cpu-profile.html <PID>
# → 불꽃 그래프(flame graph) HTML 생성. 가로로 넓은 함수 = CPU를 많이 먹는 함수

# 할당(alloc) 프로파일 — "누가 객체를 이렇게 많이 만드나?" (GC 압박의 범인 찾기)
./asprof -e alloc -d 30 -f alloc-profile.html <PID>

# 힙 덤프 — 메모리 누수 의심 시, 힙의 스냅샷을 떠서 분석 도구로 연다
jcmd <PID> GC.heap_dump /tmp/heap.hprof

# GC 로그 켜기 (JVM 옵션) — stop-the-world가 언제 얼마나 길었는지 기록
-Xlog:gc*:file=gc.log:time,uptime

# gc.log에서 이런 줄을 찾는다:
#   Pause Young (Normal) ... 12.3ms   ← 짧은 젊은 세대 수거, 정상
#   Pause Full ... 1843.7ms           ← 1.8초 전체 멈춤! 이 동안 모든 요청이 정지
# Full GC가 잦다면: 힙이 작거나, 누수가 있거나, 객체를 너무 많이 만드는 것.`;

const CODE_POOL = `# 커넥션 풀 포화의 전형적 증상 — 코드는 멀쩡한데 p95만 치솟는다
# HikariCP 기본 풀 크기: 10개. 동시 요청 100개가 들어오면?
#   → 10개는 DB 작업, 90개는 '커넥션 나올 때까지 줄서기'
#   → 프로파일러엔 안 잡힌다 (CPU를 안 쓰고 그냥 기다리니까)
#   → 풀 대기 시간을 재는 지표를 따로 봐야 보인다

spring:
  datasource:
    hikari:
      maximum-pool-size: 10        # 무작정 늘리면 DB가 먼저 쓰러진다
      connection-timeout: 3000     # 3초 기다려도 못 얻으면 예외 (무한 대기 금지)

# 스레드 풀도 같은 원리 — 톰캣 기본 최대 200 스레드.
# 스레드 하나가 느린 외부 API를 '동기로' 3초씩 기다리면,
# 200개가 전부 기다림에 묶이는 순간 서버는 새 요청을 아예 못 받는다.
# 게이트웨이처럼 모든 트래픽이 지나는 길목에서 이게 터지면 전 서비스가 멈춘다.`;

const CODE_MICROMETER = `// Micrometer — 코드 안에 '측정 지점'을 심어 두는 표준 계측 라이브러리
// Spring Boot Actuator를 넣으면 기본 지표(HTTP·JVM·풀)는 공짜로 나온다.
// 우리 서비스 고유의 지표는 직접 심는다:

@Service
public class QuoteService {
  private final Counter cacheMiss;   // ① 카운터: 늘어나기만 하는 누적값
  private final Timer fetchTimer;    // ② 타이머: 횟수 + 걸린 시간 분포(백분위까지)

  public QuoteService(MeterRegistry registry, TickBuffer buffer) {
    this.cacheMiss  = registry.counter("quote.cache.miss");
    this.fetchTimer = registry.timer("quote.fetch");
    // ③ 게이지: '지금 이 순간'의 값을 들여다보는 창 (오르내림)
    registry.gauge("tick.buffer.size", buffer, TickBuffer::size);
  }

  public Quote fetch(String symbol) {
    return fetchTimer.record(() -> {      // 이 블록의 소요 시간이 자동 기록
      Quote hit = cache.get(symbol);
      if (hit != null) return hit;
      cacheMiss.increment();               // 캐시 미스 1건 추가
      return loadFromUpstream(symbol);
    });
  }
}

// 구분법: 몇 번? → 카운터 / 얼마나 걸렸나? → 타이머 / 지금 얼마나? → 게이지`;

const CODE_BUDGET = `# 성능 예산(performance budget) — 기능마다 미리 정해 두는 '속도의 상한선'
기능                          예산(p95)      근거
──────────────────────────────────────────────────────
시세 단건 조회 API             150ms         화면 갱신 체감 한계
관심종목 목록 (로그인 직후)     300ms         첫 화면 이탈률과 직결
공시 검색                      800ms         '검색'은 사용자가 조금 기다려 줌
SSE 첫 tick 도착               2s            연결 후 빈 화면 허용 한도

# CI에 회귀 감시 붙이기 — 예산을 어기면 머지가 막힌다
# (k6 thresholds가 곧 예산의 코드화 — 섹션 3의 p(95)<300이 그것)
perf-check:
  script:
    - k6 run --quiet load-test.js     # thresholds 위반 시 exit code 99
  rules:
    - if: $CI_MERGE_REQUEST_ID        # MR마다 자동 실행

# 예산 안에 들어왔으면? 거기서 멈춘다.
# 150ms 예산에 120ms면 충분 — 80ms로 줄이는 일주일은 다른 기능에 쓰는 게 낫다.`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. 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: 'JVM 프로파일링' },
  { n: 5, label: '앱 병목' },
  { n: 6, label: '상시 관측' },
  { n: 7, label: '성능 예산' },
];

export default function Lv3PerformanceProfilingPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 고급 과정</div>
        <h1>📈 성능 측정과<br />프로파일링</h1>
        <p>
          추측하지 말고 측정하라 — <strong>병목은 늘 예상 밖에 숨어 있습니다.</strong>{' '}
          "여기가 느릴 것 같은데"라는 감으로 시작한 최적화는 대개 엉뚱한 곳을 수술하고
          끝나요. 이 코스에선 <strong>측정→분석→개선→재측정</strong>의 루프를 몸에 익히고,
          부하 테스트로 숫자를 얻고, 프로파일러로 범인을 지목하는 법을 배웁니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 65분</span>
          <span className="chip">실습: 부하 → p95 측정 → 병목 3개 지목</span>
          <span className="chip">선수 지식: Spring Boot·DB 튜닝 기초</span>
        </div>
      </div>

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

      <Section n={1} title="추측하지 말고 측정하라" sub="성급한 최적화가 비싼 이유">
        <p>
          성능 이야기의 출발점은 유명한 격언 하나예요. <strong>"성급한 최적화는 만악의
          근원이다."</strong> 왜 '악'까지 갈까요? 감으로 하는 최적화는 세 가지 비용을
          치르기 때문입니다. <strong>① 시간</strong> — 병목이 아닌 곳을 고치느라 며칠을
          쓰고, <strong>② 복잡도</strong> — 빨라지지도 않았는데 코드는 읽기 어려워지고,{' '}
          <strong>③ 착각</strong> — "최적화했으니 빨라졌겠지"라는 검증 안 된 믿음이
          남습니다.
        </p>
        <Code>{CODE_LOOP}</Code>
        <p>
          병목이 예상과 다른 곳에 있는 건 우연이 아니에요. 우리가 코드를 읽을 때 눈에
          띄는 건 <strong>복잡해 보이는 로직</strong>이지만, 실제 시간을 잡아먹는 건{' '}
          <strong>기다림</strong> — DB 커넥션 대기, 느린 외부 API, 디스크 I/O — 인 경우가
          많거든요. 기다림은 코드에 안 보입니다. 그래서 잽니다.
        </p>
        <p>
          루프에서 하나 더 눈여겨볼 것: <strong>③에서 병목을 '하나만' 고친다</strong>는
          부분이에요. 한 번에 세 군데를 고치면, 재측정에서 빨라져도{' '}
          <strong>무엇이 효과였는지 알 수 없어요.</strong> 변인 통제 — 과학 실험과
          똑같습니다.
        </p>
        <div className="tip">
          <b>오해 방지</b> "최적화를 미루라"는 말이지 "설계를 대충 하라"는 말이 아니에요.
          인덱스 없는 테이블, 무한정 쌓이는 캐시처럼 <strong>나중에 고치기 어려운 구조적
          선택</strong>은 처음부터 제대로 하는 게 맞습니다. 미루는 건 '증거 없는 미세
          튜닝'이지, '상식적인 설계'가 아니에요.
        </div>
      </Section>

      <Section n={2} title="무엇을 측정하나" sub="평균은 거짓말을 한다 — p95·p99를 보라">
        <p>
          측정하기로 했으면 다음 질문은 "무엇을?"입니다. 가장 먼저 떠오르는 건{' '}
          <strong>평균 응답 시간</strong>이겠지만, 평균에는 치명적인 함정이 있어요.
        </p>
        <Code>{CODE_PERCENTILE}</Code>
        <p>
          그래서 실무에선 <strong>백분위(percentile)</strong>를 봅니다.{' '}
          <span className="icode">p95</span>는 "95%의 요청이 이 시간 안에 끝났다",{' '}
          <span className="icode">p99</span>는 "99%가 이 안에 끝났다"는 뜻이에요. 느린
          꼬리를 숨길 수 없죠. 지연시간 목표를 말할 땐 반드시{' '}
          <strong>"p95 300ms"처럼 백분위로</strong> 말하는 습관을 들이세요.
        </p>
        <p>
          지연시간(latency) 말고도 함께 봐야 할 기본 지표가 셋 있습니다.{' '}
          <strong>① 처리량(throughput)</strong> — 초당 몇 건을 처리하나(TPS),{' '}
          <strong>② 에러율</strong> — 빨라도 실패하면 의미 없으니까,{' '}
          <strong>③ 자원 사용률</strong> — CPU·메모리·커넥션이 얼마나 차 있나. 넷은 서로를
          비추는 거울이에요. 예를 들어 에러율이 0인데 p95만 치솟는다면 "죽진 않았지만
          어딘가에서 줄을 서고 있다"는 신호입니다.
        </p>
        <div className="tip">
          <b>레이턴시 vs 스루풋</b> 이 둘은 종종 <strong>맞바꿈(trade-off)</strong> 관계예요.
          요청을 모아서 한꺼번에 처리(배치)하면 처리량은 오르지만 개별 요청의 대기는
          길어지고, 반대로 매 건 즉시 처리하면 지연은 짧지만 총 처리량이 줄 수 있어요.
          "무엇이 더 중요한 API인가"를 먼저 정해야 튜닝의 방향이 정해집니다 — 시세
          스트림은 지연이, 야간 정산 배치는 처리량이 왕이죠.
        </div>
      </Section>

      <Section n={3} title="부하 테스트" sub="k6로 현실적인 부하를 만들어 숫자를 얻는다">
        <p>
          p95를 재려면 요청이 많이 필요해요. 혼자 브라우저로 새로고침 몇 번 해서는 아무것도
          알 수 없죠. 그래서 <strong>부하 테스트 도구</strong>로 가상 사용자(VU)를 만들어
          동시에 요청을 퍼붓습니다. 대표 주자는 <strong>k6</strong>(스크립트가 JS라
          가볍고 CI 친화적)와 <strong>JMeter</strong>(GUI 기반, 오래된 만큼 기능이 방대)
          — 이 코스에선 k6로 갑니다.
        </p>
        <Code>{CODE_K6}</Code>
        <p>
          시나리오의 4단계엔 각각 이유가 있어요. <strong>웜업</strong>을 건너뛰면 JVM의
          JIT 컴파일러가 코드를 아직 기계어로 덥히기 전이라, 실제보다 훨씬 느린 숫자가
          나옵니다(캐시도 비어 있고요). <strong>램프업</strong>은 갑자기 100명을 때려 넣는
          대신 서서히 올려서 "몇 명부터 무너지는가"를 관찰하게 해 주고,{' '}
          <strong>지속 구간</strong>이 진짜 측정 구간 — 안정 상태(steady state)의 p95가
          우리가 원하는 숫자입니다.
        </p>
        <p>
          한 가지 주의: <strong>SSE·스트리밍 엔드포인트</strong>는 일반 API처럼 재면
          안 돼요. 요청-응답이 짧게 끝나는 게 아니라 <strong>연결을 오래 물고 있는</strong>{' '}
          모델이라, "응답 시간"보다 <strong>동시 연결 수를 몇 개까지 버티나</strong>,{' '}
          <strong>첫 이벤트가 도착할 때까지 얼마나 걸리나</strong>가 진짜 질문입니다.
          연결 1만 개가 각자 스레드를 하나씩 잡는 구조라면 그게 곧 한계가 되죠.
        </p>
        <div className="tip">
          <b>운영 서버에 쏘지 마세요</b> 부하 테스트는 이름 그대로 서버를{' '}
          <strong>고의로 한계까지 미는 행위</strong>예요. 반드시 로컬이나 스테이징 같은
          격리된 환경에서 하고, 공용 환경이라면 팀에 미리 알리세요. 남의 서비스에 무단으로
          쏘면 공격(DoS)과 구분되지 않습니다.
        </div>
      </Section>

      <Section n={4} title="JVM 프로파일링" sub="불꽃 그래프로 핫스팟, 힙 덤프로 누수, GC 로그로 멈춤">
        <p>
          부하 테스트가 "<strong>얼마나</strong> 느린가"를 알려 줬다면, 프로파일러는
          "<strong>어디서</strong> 느린가"를 알려 줍니다. JVM 진영의 사실상 표준은{' '}
          <strong>async-profiler</strong> — 돌고 있는 프로세스에 붙여서 주기적으로 "지금 뭐
          하고 있어?"를 묻고(샘플링), 그 답을 쌓아{' '}
          <strong>불꽃 그래프(flame graph)</strong>로 그려 줘요. 가로로 넓은 함수일수록
          CPU 시간을 많이 쓴 함수 — 넓은 막대를 따라가면 핫스팟이 나옵니다.
        </p>
        <Code>{CODE_PROFILER}</Code>
        <p>
          CPU만 보면 절반이에요. <span className="icode">-e alloc</span>으로{' '}
          <strong>할당 프로파일</strong>을 뜨면 "누가 객체를 쏟아내는가"가 보이는데, 객체를
          많이 만들수록 GC가 자주 돌고, GC가 돌수록 앱이 자주 멈추니까요. 메모리가 계속
          우상향으로 차오르며 안 내려온다면 <strong>누수</strong> 의심 —{' '}
          <strong>힙 덤프</strong>를 떠서 "어떤 타입의 객체가 몇 개 살아 있고, 누가 그걸
          붙들고 있나"를 추적합니다.
        </p>
        <p>
          마지막 조각은 <strong>GC 로그</strong>예요. GC 중에서도{' '}
          <strong>stop-the-world</strong> — 수거하는 동안 <strong>모든 애플리케이션
          스레드가 정지</strong>하는 구간 — 이 길면, 그 순간 들어온 요청 전부가 그만큼
          지연됩니다. "p99만 이상하게 튀는" 미스터리의 단골 범인이 바로 이 Full GC예요.
          로그에서 <span className="icode">Pause Full</span>이 자주, 길게 보인다면 힙 크기·
          할당 패턴·누수를 순서대로 의심하세요.
        </p>
        <div className="tip">
          <b>샘플링이라 부담이 적다</b> async-profiler는 모든 호출을 기록하는 게 아니라
          주기적으로 스냅샷을 뜨는 <strong>샘플링 방식</strong>이라 오버헤드가 작아요.
          그래서 부하 테스트 중인 서버에 붙여도 측정 자체가 결과를 크게 왜곡하지 않습니다
          — "측정이 대상을 바꾸면 안 된다"는 원칙에 맞는 도구죠.
        </div>
      </Section>

      <Section n={5} title="애플리케이션 병목" sub="쿼리·직렬화·풀 포화 — 자주 나오는 범인 도감">
        <p>
          프로파일을 떠 보면 병목은 대개 몇 가지 익숙한 얼굴로 나타나요. 첫째 그룹은{' '}
          <strong>데이터 접근</strong> — 인덱스 없는 느린 쿼리, 목록 하나 그리는데 쿼리가
          N+1번 나가는 패턴(앞 강 <strong>DB 심화</strong>에서 다룬 그 녀석들), 그리고
          거대한 객체 그래프를 통째로 JSON으로 바꾸는 <strong>과한 직렬화</strong>. 불꽃
          그래프에서 직렬화 라이브러리가 넓게 차지하고 있다면, 응답에 안 쓰는 필드까지
          퍼 나르고 있을 확률이 높습니다 — DTO로 필요한 만큼만 담으세요.
        </p>
        <p>
          둘째 그룹이 더 교묘해요. <strong>풀(pool) 포화</strong> — 코드가 아니라
          '줄서기'가 병목인 경우입니다.
        </p>
        <Code>{CODE_POOL}</Code>
        <p>
          이 유형이 무서운 이유는 <strong>CPU 프로파일에 안 잡히기 때문</strong>이에요.
          기다리는 스레드는 CPU를 안 쓰니까 불꽃 그래프에선 조용하죠. 그래서 풀 지표
          (활성 커넥션 수·대기 시간·대기 큐 길이)를 <strong>따로</strong> 봐야 합니다 —
          다음 섹션의 상시 관측이 필요한 이유이기도 해요.
        </p>
        <p>
          셋째, <strong>동기 블로킹 호출의 전염</strong>. 서비스 하나가 느려지는 건 사고가
          아니지만, 그 느린 서비스를 <strong>모든 트래픽이 지나는 게이트웨이가 동기로
          기다리면</strong> 이야기가 달라져요. 게이트웨이의 스레드 풀이 그 기다림에 전부
          묶이는 순간, 멀쩡한 다른 서비스로 가는 요청까지 통째로 막힙니다. 한 칸의 정체가
          고속도로 전체의 정체가 되는 거죠. 타임아웃을 짧게 걸고, 실패가 반복되면 아예
          빠르게 포기하는 방어선이 필요합니다.
        </p>
        <div className="tip">
          <b>실습 힌트</b> 이 코스의 실습 — "부하를 주고 p95를 잰 뒤 상위 병목 3개 지목" —
          에서 병목 후보를 찾을 땐 이 도감 순서로 훑으세요. ① 불꽃 그래프의 넓은 막대
          (쿼리·직렬화) → ② 풀 지표(커넥션·스레드 대기) → ③ GC 로그(Pause Full). 세 곳을
          보면 열에 아홉은 걸립니다.
        </div>
      </Section>

      <Section n={6} title="지표 상시 관측" sub="Micrometer로 코드 안에 측정 지점을 심는다">
        <p>
          부하 테스트와 프로파일링은 <strong>일회성 검사</strong>예요. 건강검진처럼
          유용하지만, 검진일 사이에 생기는 문제는 못 봅니다. 그래서 잘 만든 서비스는
          지표를 <strong>상시로</strong> 내보내요 — 그 표준 도구가{' '}
          <strong>Micrometer</strong>입니다. Spring Boot Actuator를 붙이면 HTTP 응답
          시간·JVM 메모리·커넥션 풀 같은 기본 지표는 자동으로 나오고, 우리 도메인 고유의
          지표는 코드에 직접 심습니다.
        </p>
        <Code>{CODE_MICROMETER}</Code>
        <p>
          세 가지 계측기의 구분이 핵심이에요. <strong>카운터</strong>는 늘어나기만 하는
          누적값(총 캐시 미스 횟수, 총 에러 수) — 그 자체보다 <strong>증가 속도</strong>를
          봅니다. <strong>타이머</strong>는 횟수에 더해 <strong>소요 시간의 분포</strong>까지
          기록해서 p95·p99를 뽑을 수 있고요. <strong>게이지</strong>는 지금 이 순간의 값
          (버퍼 크기, 활성 연결 수)을 들여다보는 창이라 오르내립니다. "몇 번?"은 카운터,
          "얼마나 걸렸나?"는 타이머, "지금 얼마나?"는 게이지 — 이 한 줄만 기억하세요.
        </p>
        <div className="tip">
          <b>다음 강으로 이어지는 다리</b> 심어 둔 지표는 어딘가에 모아서 그래프로 보고,
          이상하면 알림을 받아야 비로소 쓸모가 있죠. 그 수집·시각화·경보의 세계
          (Prometheus·Grafana)가 <strong>모니터링 강</strong>의 주제입니다. 이번 강에서
          지표를 <strong>심는 법</strong>을 익혀 두면, 다음 강에선 그걸{' '}
          <strong>읽고 대응하는 법</strong>으로 자연스럽게 이어져요.
        </div>
      </Section>

      <Section n={7} title="성능 예산과 회귀 방지" sub="어디까지 빠르면 충분한가 — 멈출 지점을 정한다">
        <p>
          마지막 질문은 의외로 이겁니다. <strong>"언제 멈추나?"</strong> 최적화는 중독성이
          있어서, 기준이 없으면 끝없이 파고들게 돼요. 그래서 잘하는 팀은 기능마다{' '}
          <strong>성능 예산(performance budget)</strong> — "이 API는 p95 300ms까지 허용" —
          을 <strong>미리</strong> 정해 둡니다. 예산이 있으면 두 가지가 명확해져요. 예산을
          넘으면 <strong>고쳐야 하고</strong>, 예산 안이면 <strong>멈춰도 됩니다.</strong>
        </p>
        <Code>{CODE_BUDGET}</Code>
        <p>
          예산의 진짜 힘은 <strong>회귀(regression) 방지</strong>에서 나와요. 성능은 한 번
          좋게 만들어 놓아도, 기능이 추가될 때마다 조금씩 무거워지다가 어느 날 갑자기
          "느려졌어요"가 터집니다. 그 '조금씩'을 잡으려면 사람의 기억이 아니라{' '}
          <strong>CI가 감시</strong>해야 해요 — 섹션 3에서 본 k6의{' '}
          <span className="icode">thresholds</span>가 바로 예산의 코드화입니다. MR마다
          자동으로 부하를 걸고, p95가 예산을 넘으면 빌드를 깨뜨리는 거죠.
        </p>
        <p>
          그리고 멈출 용기. 예산이 150ms인데 지금 120ms라면, 80ms로 줄이겠다고 일주일을
          쓰는 건 <strong>최적화가 아니라 낭비</strong>예요. 그 일주일은 예산을 넘긴 다른
          API나, 아예 새로운 기능에 쓰는 게 팀 전체로 이득입니다. 성능 작업도 결국{' '}
          <strong>우선순위 게임</strong> — 숫자가 있어야 그 판단이 가능해지고, 그래서 이
          코스의 처음과 끝이 같은 문장인 거예요. 추측하지 말고, 측정하라.
        </p>
        <div className="tip">
          <b>예산은 사용자에게서 거꾸로</b> 예산 숫자는 서버 사정이 아니라{' '}
          <strong>사용자 경험에서 역산</strong>하세요. "시세 화면은 0.15초 안에 떠야
          거래에 지장이 없다" → 그게 API 예산이 됩니다. 기술이 아니라 제품이 기준을 정하는
          것 — 고급 과정 전체를 관통하는 감각이에요.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>📈 여기까지 왔다면</h3>
          <p className="muted">
            이제 "느려요"라는 말을 들었을 때 코드부터 뒤지는 대신, <strong>측정→분석→개선→
            재측정</strong>의 루프를 돌릴 수 있게 됐어요. p95로 말하고, k6로 부하를 만들고,
            불꽃 그래프와 GC 로그로 범인을 지목하고, Micrometer로 측정 지점을 심고, 성능
            예산으로 멈출 지점까지 정하는 것 — 잘 만드는 개발자의 성능 감각을 손에
            쥐었습니다. 그런데 서비스가 빨라지고 커질수록, 노리는 사람도 많아져요. 다음{' '}
            <Link to="/learn/lv3-web-security"><strong>웹 보안 심화 — OWASP Top 10</strong></Link>{' '}
            코스에서, 공격자의 눈으로 우리 서비스를 살펴보는 법을 이어서 익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
