BE-commerce 프로젝트
약 4분 분량 성능·용량검색·추천

과부하에서 무엇을 포기하나: 정책은 지연을 정하고 개인화 비율은 모델 용량이 정한다

목차

추천 모델이 감당할 수 있는 양보다 요청이 많을 때 넘친 요청을 어떻게 포기할지 세 정책을 비교했습니다. 개인화 결과를 받은 요청의 비율(coverage)은 세 정책이 ±0.3%p 안에서 같았고 갈린 것은 p95(160ms, 320ms, 454ms)였습니다. 아무것도 거절하지 않는 정책은 4배 부하에서 coverage가 31.4%로 오히려 좋아 보였는데 요청을 받지도 못해 분모에서 빠진 생존자 편향이었습니다.

결론부터

  • 모델 처리량이 초당 80건으로 고정된 조건에서는 어떤 정책도 개인화 coverage를 크게 늘리지 못했습니다.
  • 정책이 바꾼 것은 개인화를 포기하는 비율이 아니라 대기시간과 실패가 나타나는 방식이었습니다.
  • UNBOUNDED의 coverage 31.4%는 요청을 받지 못한 생존자 편향이므로, 달성한 유입과 함께 해석해야 합니다.

모델이 느려질 때 선택할 정책

홈 화면 추천은 모델 호출에 기댑니다. 모델은 죽지 않고 느려질 수 있고 그때 대기열에 상한이 없으면 모델은 살아 있어도 API가 죽습니다. 그래서 넘친 요청을 언제 포기하고 인기 상품으로 물러설지 정해야 했습니다. 이 글은 그 정책을 고르면서 “정책이 무엇을 바꾸고 무엇을 못 바꾸는가”를 재서 확인한 기록입니다.

모델 처리량을 초당 80건으로 고정하고 유입을 1배, 2배, 4배로 올렸습니다. 모델은 같은 프로세스 안의 스텁이라 네트워크 왕복이 없고 CPU를 쓰지 않습니다.

과부하 정책은 지연을 정하고 개인화 비율은 모델 용량이 정한다. ADMISSION p95 160ms, BOUNDED 320ms, UNBOUNDED 454ms이고 coverage는 셋이 거의 같다. UNBOUNDED의 31.4%는 생존자 편향

측정 전에 세운 세 정책

정책방식
UNBOUNDED아무것도 거절하지 않고 모델 앞에 줄을 세운다(대기 상한 400ms)
BOUNDED대기열 길이에 상한(24)을 둔다
ADMISSION대기 예산(100ms)을 넘길 요청은 받자마자 인기 상품으로 돌린다

가설은 “정책을 잘 고르면 개인화를 덜 버린다”였습니다.

coverage는 정책이 사지 못했다

유입ADMISSIONBOUNDEDUNBOUNDED
80/s (1배)93.6%93.7%93.5%
160/s (2배)47.1%47.1%46.9%
320/s (4배)23.7%23.7%31.4%

세 정책의 차이는 ±0.3%p였습니다. coverage는 모델 처리량 ÷ 유입에서 곧장 나옵니다. 모델이 초당 80건을 처리하는데 160건이 들어오면 절반만 개인화를 받습니다. 정책이 바꾸는 것은 누가 포기하느냐이고 얼마나 포기하느냐는 바꾸지 못합니다. 가설은 틀렸습니다. 개인화를 덜 버리려면 정책이 아니라 모델 용량을 늘려야 합니다.

정책이 사는 것은 지연이었다

정책서빙 p95무엇이 그 값을 정하나
ADMISSION160ms대기 예산 100ms + 처리 50ms
BOUNDED320ms상한 24 ÷ 처리량 80/s + 처리 50ms
UNBOUNDED454ms대기 상한 400ms + 처리 50ms

p95는 부하가 두 배, 네 배가 되어도 거의 같았습니다. 지연을 정하는 것은 부하가 아니라 정책의 파라미터였습니다. 대기열의 길이를 정하는 것이 곧 지연을 정하는 일입니다.

ADMISSION을 고른 이유

BOUNDED의 상한 24는 지금 처리량에서 “약 300ms를 기다리게 한다”는 뜻입니다. 그런데 그 뜻은 처리량이 바뀌면 달라집니다. 모델을 두 배로 늘리면 같은 24가 150ms가 되고 절반으로 줄면 600ms가 됩니다. 상한은 지연을 고정하지 못합니다.

ADMISSION은 기다릴 수 있는 시간을 직접 말합니다. 처리량이 바뀌어도 지연 목표가 유지되고 실측에서 세 부하 모두 p95가 159~160ms였습니다. 대가는 모델 용량을 알아야 한다는 것입니다. 용량이 바뀌면 설정도 바꿔야 하고 최근 처리량으로 용량을 자동 추정하는 기능은 만들지 않았습니다. 정책은 설정값이므로 BOUNDED로 되돌리는 비용은 없습니다.

가장 좋아 보인 숫자가 가장 틀렸다

4배 부하에서 UNBOUNDED의 coverage는 31.4%로 다른 두 정책(23.7%)보다 높았습니다. 표만 보면 거절하지 않는 쪽이 개인화를 더 많이 준 것처럼 읽힙니다.

같은 런의 달성 부하가 답을 줬습니다. 초당 320건을 보냈는데 UNBOUNDED가 실제로 받은 것은 초당 212건이었고 3,102건은 요청조차 처리되지 못했습니다. 다른 두 정책은 초당 283~284건을 받았습니다. 스레드가 모델 앞 줄에 갇히면 새 요청을 받을 수 없습니다. 받지 못한 요청은 coverage의 분모에서 빠지고 받은 요청은 모델이 꾸준히 처리했으니 비율이 높아 보인 것입니다. 같은 조건의 p95는 453ms였습니다.

그래서 읽는 규칙을 하나 정했습니다. coverage는 달성 부하와 함께 읽고 달성률이 낮은 런의 coverage는 좋아 보이는 방향으로 틀린다고 봅니다. 부하 스크립트가 달성 부하를 함께 출력하고 리포트도 따로 표로 냅니다.

이 측정이 말하지 않는 것

  • 포기한 요청이 받은 인기 상품 추천이 개인화보다 얼마나 나쁜지는 재지 않았습니다. coverage는 “포기했다”는 사실이지 “그게 얼마나 손해인가”가 아닙니다.
  • 모델이 같은 프로세스의 스텁이라 실제 모델이 서빙과 CPU를 다투는 상호작용은 재지 않았습니다.
  • 대기 예산 100ms, 상한 24, 대기 상한 400ms는 각각 하나의 선택이고 바꿔 가며 재지 않았습니다.

이어서 읽기

이 글에서 이어진 작업은 따로 적었습니다.

참고

프로필 사진
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글

댓글 수정/삭제는 GitHub Discussions에서 가능합니다.