FDE 캡스톤: 창고가 같은 주문을 세 번 받았다 · 빠르게를 숫자로 · 이론
고객은 '빠르게' 라고만 썼다
한 줄 요약
인수 기준은 고객의 형용사를 지표·계산 방법·기준값·측정 조건의 네 조각으로 바꾼 것이고, 인수 시험 실행기는 그 기준표를 코드 밖에서 읽어 요청을 보내고 증거와 판정을 함께 남긴다.
왜 이게 필요했나
구매팀 메일에는 "견적 조회가 빠르게 되어야 합니다" 한 줄뿐이었다. 개발팀은 평균 80ms 가 나오니 빠르다고 했고, 운영팀은 어제 오후에 조회 화면이 3초씩 멈췄다고 했다. 둘 다 사실이다. 스무 번에 한 번 멈추는 서비스는 평균으로 보면 빠르다. 인수 회의에서 이 차이가 드러나지 않으면, 납품 뒤에 "빠르다고 하셨잖아요" 와 "빠르지 않잖아요" 가 부딪힌다.
FDE 에게 인수 기준은 문서 작업이 아니라 협상의 결과물이다. 고객이 원하는 경험을 숫자로 옮기되, 그 숫자가 무엇을 어떻게 잰 값인지까지 합의해야 나중에 같은 숫자를 두고 다른 말을 하지 않는다. 그리고 합의한 숫자를 코드에 박아 두면, 기준이 바뀔 때마다 실행기를 고쳐야 하고 어느 판의 실행기가 어느 기준으로 판정했는지 흐려진다. 기준표를 파일로 두는 이유다.
어떻게 동작하나
Google SRE 책의 [서비스 수준 목표 장](https://sre.google/sre-book/service-level-objectives/)은 SLI 를 평균으로만 모으면 대부분은 빠르고 긴 꼬리는 훨씬 느린 상황을 가린다고 경고하고, 백분위수로 분포의 모양을 보라고 권한다. 같은 장은 SLO 의 자연스러운 형태를 "SLI ≤ 목표" 로 적는다. 이 실습의 기준표 한 줄이 바로 그 모양이다.
{"id": "quote-latency", "metric": "p95_ms", "op": "<=", "threshold": 250, "source": "견적 조회가 빠르게 되어야 합니다"}계산 방법까지 적어야 한다. "p95" 는 하나의 값이 아니다. 파이썬 [statistics.quantiles](https://docs.python.org/3/library/statistics.html)는 기본값이 exclusive 방식이고 두 표본 사이를 선형 보간하며, inclusive 방식도 따로 있다. 실측으로 40개 요청 중 38개가 5ms, 2개가 400ms 일 때 세 방식을 비교했다. nearest-rank(정렬 뒤 ceil(0.95×n) 번째 값)는 5.0, quantiles(n=100) 의 95번째 절단점은 exclusive 로 380.25, inclusive 로 24.75 였다. 기준값이 250ms 라면 같은 증거가 방식에 따라 통과도 실패도 된다. 그래서 계약서에 nearest-rank 를 명시하고, 증거로 누구나 다시 계산할 수 있게 한다.
측정 조건도 기준이다. 지연은 요청을 보내고 본문을 다 받을 때까지를 [time.perf_counter](https://docs.python.org/3/library/time.html)로 잰다. 문서는 이 시계를 짧은 구간을 재기 위한 가장 정밀한 시계라고 설명하고, 기준점이 정해져 있지 않아 두 호출의 차이만 의미가 있다고 적는다. CPython 에서는 뒤로 가지 않는 monotonic 시계와 같은 시계다. 반면 time.time() 은 두 호출 사이에 시스템 시계가 뒤로 조정되면 더 작은 값을 돌려줄 수 있다고 같은 문서에 적혀 있으니 구간 측정에 쓰지 않는다. 오류율은 "무엇을 오류로 세나" 가 먼저다. 이 고객과는 200 이 아닌 응답과 1000ms 안에 오지 않은 응답을 세기로 했다. [urllib.request](https://docs.python.org/3/library/urllib.request.html) 문서는 urlopen 의 timeout 을 연결 시도 같은 블로킹 동작 하나하나의 제한이라고 설명한다. 요청 전체의 마감이 아니라는 뜻이라, 조금씩 흘려 보내는 서버에서는 기준보다 길게 걸릴 수 있다. 실행기는 잰 시간을 증거에 그대로 남겨 이런 경우를 드러낸다.
정확성과 재현성은 따로 잰다. 정확성은 고객이 준 사례표의 기대값과 비교하고, 재현성은 같은 사례표를 두 번 조회해 결과끼리 비교한다. 두 번째 비교에서는 request_id 나 생성 시각처럼 매번 바뀌는 게 정상인 필드를 빼야 하는데, 어떤 필드를 뺄지도 기준표에 적는다. 코드에 적어 두면 서버가 필드 이름을 바꾸는 순간 정상 빌드가 떨어진다.
| 고객 문장 | 지표 | 기준 | 측정 조건 |
| --- | --- | --- | --- |
| 빠르게 | p95_ms (nearest-rank) | 250 이하 | 요청 전체, 본문 수신까지 |
| 오류 없이 | error_rate | 0.01 이하 | 200 아님 + 1000ms 초과 |
| 금액이 맞게 | mismatches | 0 | 1회차, 사례표 기대값과 비교 |
| 다시 돌려도 같게 | rerun_diffs | 0 | 1·2회차 비교, 변하는 필드 제외 |
현장에서 만나는 모습
가장 흔한 실패는 판정만 있는 보고서다. "인수 시험 통과" 한 줄을 받은 고객 운영팀은 그 말을 검증할 방법이 없고, 두 달 뒤 장애가 나면 시험이 무엇을 쟀는지부터 다시 따진다. 요청마다 지연·상태·응답을 남긴 증거가 있으면 고객이 자기 방식으로 다시 계산할 수 있다. 증거에서 다시 계산한 값과 보고서의 값이 다르면 보고서를 믿지 않는다. 이 실습 채점기가 하는 일이 정확히 그것이다.
두 번째는 기준을 사후에 고치는 것이다. 후보 빌드가 금액 기준에서 떨어지자 "1원 차이는 반올림이니 허용하자" 며 기준표를 바꾸고 다시 돌리는 장면이다. 허용 오차가 필요할 수는 있다. 그러나 그것은 고객과 새로 합의할 일이지, 실행기를 돌린 사람이 결과를 보고 정할 일이 아니다. 판정 보고서에 기준값을 그대로 옮겨 적는 이유도 여기에 있다.
실무에서 진짜 중요한 것
- 형용사 하나마다 지표·계산 방법·기준값·측정 조건을 적고, 근거가 된 고객 문장을 함께 남긴다.
- 기준값은 기준표에서 읽는다. 실행기에 숫자가 박혀 있으면 기준이 바뀐 날 조용히 틀린다.
- 요청 수를 정확히 지킨다. 예열이나 재시도를 섞으면 오류율과 백분위수의 분모가 달라진다.
- 증거를 먼저 남기고 판정은 증거에서 계산한다. 재계산이 같아야 보고서다.
다음 실습에서 할 것
고객 메일과 회의록을 기준표로 옮긴 뒤, 지연·오류율·정확성·재현성을 차례로 실행기에 넣는다. 채점기는 가짜 견적 서버로 꼬리가 느린 빌드, 가끔 멈추거나 500 을 내는 빌드, 일부 금액이 1원 틀린 빌드, 재조회가 흔들리는 빌드를 띄우고 기준값과 사례표를 매번 바꿔 실행기를 돌린다. 마지막에 고객의 후보 rc2 를 직접 띄워 인수 여부를 판정한다.