BE-commerce 프로젝트
약 4분 분량 성능·용량

과부하 게이트가 모델 지연을 절반으로 잘못 알면 대기 예산이 세 배로 샜다

목차

추천 모델 앞의 과부하 게이트는 모델이 한 건에 몇 ms 걸리는지 설정값으로 알고 있었습니다. 모델이 실제로 두 배 느리면 대기 예산 100ms에서 응답 p95가 315ms가 됐습니다. 설정값 대신 최근 2초 동안 끝난 모델 호출 수로 대기를 추정하게 바꾸자 193ms로 돌아왔고 모델 속도를 제대로 알 때의 값도 거의 그대로였습니다(168ms → 153ms).

개요

홈 추천은 모델 서버를 부릅니다. 모델의 처리 용량을 넘는 요청이 오면 일부는 인기 상품 목록으로 대신 답합니다. 개인화 비율을 조금 잃는 대신 지연을 지키는 선택입니다. ch40에서 고른 정책(ADMISSION)은 이렇게 동작합니다. 요청이 오면 “지금 줄에 서면 얼마나 기다릴까”를 계산해서 대기 예산을 넘으면 줄에 세우지 않고 바로 인기 상품으로 답합니다.

그 글에서 대가로 두 가지를 적었습니다. 예산 같은 파라미터를 바꾸면 결과가 어떻게 되는지 모르고 모델 용량을 안다고 가정한다는 것입니다. 코드를 보니 실험용 스텁 모델과 게이트가 같은 설정값(모델 지연 50ms)을 읽고 있었습니다. 게이트는 실험 내내 진짜 지연을 알았고 그 가정은 시험된 적이 없었습니다.

게이트가 모델 지연을 설정값으로 알면, 모델이 느려졌을 때 줄을 두 배로 받는다. 최근 2초에 끝난 호출 수로 대기를 추정하게 바꿨다

실험 조건

스텁 모델은 동시 4건, 한 건 50ms라 초당 80건을 처리합니다. 부하는 초당 160건(2배)과 320건(4배), 40초씩 k6로 걸었습니다. 스텁의 실제 지연을 따로 주는 설정을 더해 게이트가 믿는 지연과 떼어 놓았습니다. 판정 기준은 측정 전에 이슈에 적었습니다.

예산을 바꾸면

정책p95(2배 · 4배)개인화 비율(2배 · 4배)
ADMISSION 예산 50ms111 · 108ms44.6 · 22.7%
ADMISSION 예산 100ms168 · 164ms44.5 · 22.6%
ADMISSION 예산 200ms281 · 276ms44.5 · 22.6%
줄 길이 상한 12168 · 164ms44.6 · 22.6%
줄 길이 상한 24348 · 332ms40.8 · 22.6%
줄 길이 상한 48461 · 455ms44.2 · 22.6%

예산 정책은 예측대로였습니다. p95는 예산에 60~80ms를 더한 값을 따라갔고 개인화 비율은 예산과 상관없이 모델 용량이 정했습니다(2배 부하에서 절반, 4배에서 4분의 1).

줄 길이 상한 48은 예측이 틀렸습니다. 48건 × 50ms ÷ 4 + 50ms = 650ms로 봤는데 461ms에서 멈췄습니다. 모델 앞에서 기다리는 시간의 상한(400ms)에 먼저 걸려 1,095건이 대기 초과로 빠졌기 때문입니다. 상한을 올려도 지연은 대기 상한 너머로 늘지 않고 대기 초과만 늡니다.

상한 12는 예산 100ms와 같은 값이 나왔습니다. 이 용량에서는 줄 12건이 곧 대기 100ms이기 때문입니다. 용량이 바뀌면 같은 12가 다른 대기를 뜻합니다.

모델이 두 배 느린데 게이트가 모를 때

실제 지연을 100ms로 두고 게이트는 50ms로 믿게 했습니다. 게이트는 대기를 절반으로 계산하니 줄을 두 배로 받습니다.

조건p95판정선
설정값으로 추정315ms(예산 100 + 실제 지연 100) × 1.5 = 300ms
줄 길이 상한 24503ms

315ms가 판정선을 넘었습니다. 예산 정책의 장점은 “예산으로 지연을 약속한다”인데 이 약속은 모델 용량을 알 때만 지켜집니다. 모델 서버가 느려지는 일은 흔합니다. 배포, 다른 요청, 모델 교체 모두 원인이 됩니다.

끝난 호출 수로 대기를 추정한다

게이트가 끝난 모델 호출을 100ms 칸 20개(2초)에 셉니다. 표본이 20건 이상 모이면 대기를 줄 길이 ÷ 초당 끝난 수로 계산합니다. 표본이 모이기 전에는 설정값을 씁니다.

처음에는 호출에 걸린 시간의 평균을 쓰려 했습니다. 그런데 호출 시간에는 모델 앞에서 기다린 시간이 섞입니다. 이것을 모델 지연으로 쓰면 줄이 길수록 지연을 크게 보고 더 많이 거절합니다. 줄이 짧아지면 다시 받아들여 줄이 길어집니다. 이 되먹임을 피하려고 끝나는 속도를 씁니다. 끝나는 속도는 줄 길이와 상관없이 모델이 실제로 소화하는 양입니다.

조건설정값 추정끝난 호출 수로 추정기준
모델이 두 배 느림, p95315ms193ms240ms 이하
제대로 앎, p95(2배 · 4배)168 · 164ms153 · 153ms±10%
제대로 앎, 개인화 비율44.5 · 22.6%44.7 · 22.7%±10%

기준을 모두 넘어 끝난 호출 수로 추정하는 방식을 기본으로 했습니다.

기본 정책이 설정에 없었다

ch40의 설계 문서는 “기본 정책은 ADMISSION”이라고 적었습니다. 그런데 설정 파일과 코드의 기본값은 처음부터 줄 길이 상한(BOUNDED)이었고 기본값을 바꾼 커밋은 없었습니다. 문서에 결정만 남고 적용이 빠진 것입니다. 이번에 ADMISSION + 끝난 호출 수 추정으로 맞췄습니다. 운영 설정에서 정책을 따로 적지 않았다면 이번 배포부터 동작이 바뀝니다.

한계

  • 모델은 Thread.sleep 스텁입니다. 실제 모델의 지연 꼬리는 반영되지 않았습니다
  • 창 길이(2초)와 최소 표본(20건)은 고른 값입니다. 용량이 갑자기 바뀌면 2초 동안은 옛 처리량으로 판단합니다. 이 구간은 재지 않았습니다
  • 부하가 용량보다 낮으면 끝난 수가 곧 도착률이라 용량을 낮게 봅니다. 그때는 줄이 비어 대기가 0이라 판단에 쓰이지 않습니다
  • 각 조건 한 번씩 쟀습니다

정리

  • 기본 정책은 ADMISSION, 대기는 최근 2초에 끝난 모델 호출 수로 추정합니다
  • 예산 정책의 p95는 예산 + 60~80ms를 따라가고 개인화 비율은 예산과 무관하게 모델 용량이 정합니다
  • 실제 모델 서버를 켜면 같은 실험을 실제 모델로 다시 돕니다
프로필 사진
작성자 @범수

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

댓글

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