성능 숫자를 의사결정으로 바꾸는 측정과 회고
측정 전에 가설과 성공 기준을 고정한다
성능 개선은 “느린 것 같다”에서 시작할 수 있지만 완료는 숫자로 판단해야 한다. 먼저 사용자 행동과 병목 가설을 적는다. 예를 들어 “글 상세의 cold 응답은 Markdown 렌더링 때문에 느리고, 렌더 결과를 캐시하면 p95가 200ms 아래로 내려간다”처럼 대상 URL, 상태, 지표, 목표가 포함되어야 한다.
성공 기준에는 부작용도 넣는다.
- 50회 요청의 p95가 200ms 미만이다.
- HTML과 JSON 응답 내용은 변경되지 않는다.
- SQL 쿼리 수가 데이터 수에 비례해 늘지 않는다.
- 캐시 무효화 후 최신 본문이 표시된다.
- 메모리와 응답 크기가 유의하게 증가하지 않는다.
기준을 먼저 고정하면 빠른 숫자 하나를 골라 성공이라고 해석하는 일을 피할 수 있다.
환경과 표본을 함께 기록한다
성능 수치는 실행 환경 없이 재사용할 수 없다. Rails environment, 서버 종류, 데이터 개수, CPU architecture, 캐시 상태, 동시성, 표본 수를 결과 옆에 적는다. 개발 모드의 class reloading 비용과 production 모드의 실제 경로를 섞지 않는다.
요청 시간은 한 번이 아니라 충분한 횟수로 모아 median과 p95를 계산한다. 평균은 소수의 큰 지연에 끌려가고, 최솟값은 사용자가 거의 경험하지 못하는 이상적인 순간일 수 있다.
samples = 50.times.map { measure_request("/posts/example") }
sorted = samples.sort
median = sorted[sorted.length / 2]
p95 = sorted[(sorted.length * 0.95).ceil - 1]
puts({ median_ms: median, p95_ms: p95 })
첫 요청은 cold 표본으로 따로 보존하고, warm-up 뒤 반복 결과와 구분한다. 측정 스크립트와 명령까지 남겨야 다음 변경에서 같은 조건으로 비교할 수 있다.
서버 시간과 브라우저 경험을 분리한다
서버 p95가 낮아도 큰 CSS, 이미지 layout shift, 느린 font 때문에 사용자는 페이지를 느리게 느낄 수 있다. 서버에서는 요청 시간, SQL 수, allocation을 보고 브라우저에서는 LCP, CLS, 접근 가능성, 전송 크기를 본다. 서로 다른 계층의 지표를 한 숫자로 합치지 않는다.
이미지에는 width와 height를 제공해 레이아웃 공간을 예약하고, 첫 화면의 대표 이미지에만 높은 fetch priority를 준다. self-host font는 font-display: swap과 적절한 preload 정책을 사용한다. JavaScript module을 모두 eager preload하기보다 현재 화면에 필요한 controller만 로드한다. 각 최적화 뒤에는 전후 수치를 비교해 실제 효과가 없으면 되돌린다.
비교 불가능한 결과를 기준선으로 쓰지 않는다
arm64 개발 머신에서 linux/amd64 이미지를 QEMU로 실행하면 애플리케이션보다 에뮬레이션 비용과 런타임 불안정성이 크게 나타날 수 있다. 이 결과는 “이미지가 부팅되는가”를 확인하는 smoke test로는 쓸 수 있지만 production 성능 기준선으로 사용하면 안 된다. native architecture 또는 실제 배포 환경에서 같은 workload를 다시 측정한다.
네트워크 프록시, 디버그 로그, 백그라운드 작업도 결과를 흔든다. 비교할 때는 한 번에 한 변수만 바꾸고 나머지 조건을 고정한다. 값이 예상보다 좋아도 환경 차이가 있다면 개선으로 단정하지 않는다. 성능 측정에서 가장 중요한 태도는 좋은 숫자를 얻는 것이 아니라 비교 가능한 숫자를 얻는 것이다.
회고는 다음 측정을 더 싸게 만드는 문서다
작업이 끝나면 결과뿐 아니라 실패한 접근과 재현 명령을 짧게 남긴다. 회고는 감상이 아니라 다음 개발 주기의 입력이어야 한다.
- 가설: Markdown 렌더링이 cold 응답의 주 병목이었다.
- 변경: versioned fragment cache를 추가했다.
- 결과: 50회 기준 median 18ms, p95 31ms.
- 실패: QEMU 측정은 변동이 커서 기준선에서 제외했다.
- 새 규칙: renderer 계약 변경 시 cache version 테스트를 추가한다.
특히 반복될 가능성이 있는 실수는 프로젝트 하네스 규칙으로 승격한다. 측정 명령, 데이터 준비법, 허용 예산을 저장소에 두면 다음 작업자는 환경을 다시 추측하지 않아도 된다. 반대로 일회성 숫자를 영구 규칙으로 만들지는 않는다.
좋은 성능 작업은 “빨라졌다”로 끝나지 않는다. 가설, 재현 가능한 조건, 분포 지표, 부작용 검증, 실패에서 얻은 규칙까지 남아야 팀의 다음 의사결정 속도도 함께 빨라진다.
댓글
아직 댓글이 없습니다.
댓글 남기기