JIN.PROC // v3.1
~/ / AI 활용법 / 2026-04-29-ai-guide-8-performance-budget

[AI 활용법 8] 성능 예산 — 숫자가 없으면 최적화는 정치다

Core Web Vitals와 ROI 우선순위로 AI 코드의 무게 잡기

DATE 2026.04.29 UPDATED 2026.04.29 READ ~ 3 MIN WORDS 501

시리즈 8편. “더 빠르게 해 줘”는 끝이 없다. 숫자를 박아 두면 끝이 보인다.

왜 성능 예산인가

LLM은 의외로 무거운 라이브러리를 잘 추천한다. 작은 문제큰 도구 를 끌어오는 경향이 있다 — 학습 데이터에 그런 패턴이 흔하기 때문이다.

이걸 막으려면 사전 합의된 숫자 가 필요하다. “느려진 것 같은데”는 의견, “LCP 2.5s 넘었어”는 사실.

권장 기본 예산 (웹)

메트릭 기준 의미
LCP ≤ 2.5s 가장 큰 콘텐츠가 보이는 시점
INP ≤ 200ms 사용자 인터랙션 응답
CLS ≤ 0.1 시각적 안정성
TTFB ≤ 800ms 첫 바이트
초기 JS 번들 ≤ 200KB (gzip) 첫 페인트 차단 자원
이미지 첫 화면 ≤ 200KB 위와 동일

이 숫자는 Google Web Vitals와 PageSpeed의 권장치를 따른다. 절대값 이라기보다 합의된 최저선.

Backend 예산

메트릭 기준
p50 응답 ≤ 100ms
p95 응답 ≤ 300ms
p99 응답 ≤ 1s
DB 쿼리/요청 ≤ 5

p99까지 보지 않으면 꼬리(tail) 의 문제를 놓친다. 평균은 거짓말을 잘 한다.

ROI 우선순위 — 어디부터 손보는가

최적화는 체감 효과 / 작업 비용 의 비율이 큰 순서로 한다.

1. 네트워크   — 캐싱, gzip, CDN, prefetch, dedupe
2. 렌더링     — 차단 리소스 제거, lazy load, defer
3. 알고리즘   — 자료구조 선택, 인덱스, N+1 제거
4. 마이크로   — 인스트럭션 단위 최적화 (마지막)

LLM에게 아무 최적화 시키면 4번부터 손대기 쉽다. 명시적으로 위에서부터 라고 알려 주자.

측정 없이 최적화하지 않는다

가장 흔한 실수: 체감 으로 “빨라졌다”고 끝내는 것. 다음 셋 중 하나는 반드시 거친다.

  • Lighthouse (CI에 자동화 가능)
  • Chrome Performance 패널 (수동, 깊은 분석)
  • performance.mark / performance.measure (코드에 직접)
performance.mark("a");
heavyWork();
performance.mark("b");
performance.measure("heavy", "a", "b");
console.table(performance.getEntriesByName("heavy"));

before/after 숫자를 PR 본문 에 박는 것을 규칙으로 만들면, 성능 PR이 말장난이 안 된다.

Lightweight-first

“일단 만들고 나중에 최적화”가 아니라 “처음부터 가벼운 구조를 기본값으로”.

  • 작은 유틸 하나를 위해 거대 라이브러리 추가 금지
  • 표준 라이브러리·플랫폼 API를 우선 고려
  • 의존성 추가는 별도 커밋 + 이유 명시

LLM에게 “moment.js 써서 시간 포맷 해 줘”라고 시키지 말고, “Intl.DateTimeFormat 가능한가?” 부터 물어보자. 99% 가능하다.

N+1 — 가장 흔한 백엔드 함정

ORM과 LLM의 조합이 만드는 단골 사고:

# N+1
for user in User.objects.all():
    print(user.posts.count())   # 매 user마다 쿼리 1개

# fix
for user in User.objects.annotate(c=Count("posts")):
    print(user.c)               # 1쿼리

LLM에게 “단일 쿼리 또는 prefetch_related/select_related 명시” 를 요구하면 자동으로 안 잡히는 경우가 많다.

가상 스크롤 — 큰 리스트의 디폴트

100개 이상의 행을 한 번에 렌더링한다면 가상 스크롤(virtualized list)을 디폴트로 한다. React에서는 react-window, Vue에서는 vue-virtual-scroller. 직접 구현도 어렵지 않다 — 뷰포트에 들어오는 것만 DOM에 둔다 가 핵심.

폰트와 이미지 — 잊기 쉬운 둘

  • 폰트: font-display: swap, 서브셋, <link rel="preload" as="font">
  • 이미지: width/height 명시 (CLS 방지), loading="lazy", srcset/sizes (해상도별)

이 둘만 챙겨도 Core Web Vitals 점수가 크게 움직인다.

캐싱 — 무효화 전략부터

캐시는 넣는 것 보다 빼는 것 이 어렵다. 시작 전에 정한다:

  • 무효화 키 — mtime, ETag, version, hash
  • 스코프 — per-user / global / region
  • 만료(TTL) — 짧을수록 안전, 길수록 빠름

LazyClaude의 mtime 캐시(이 시리즈 [LazyClaude 심층 1편] 참고)도 무효화 키 가 명확해서 잘 작동한 사례.

한 줄 요약

숫자가 없는 최적화는 정치다. 예산 을 박고, 측정 으로 합의하라.


이전 편: AI 활용법 7 — 모듈화 임계값

시리즈 진행 (8편 / 진행 중)

  1. 정직성
  2. 컨텍스트 관리
  3. 자율모드
  4. 가드레일
  5. Git 워크플로우
  6. ADR
  7. 모듈화 임계값
  8. 성능 예산