レート制限の時刻境界を検証する
한국어 원문으로 표시합니다.
목표
가상 시계로 슬라이딩 윈도와 Retry-After를 검사하고 사용자별 한도를 분리합니다.
왜 중요한가
트래픽이 늘자 서버는 모든 요청을 같은 목록에 기록하기 시작했다. 한 사용자의 연속 요청이 다른 사용자의 정상 요청까지 막았다. 창의 마지막 시각에서 항목을 지우는 비교 연산도 틀려 제한이 1초 더 유지됐다. 실제로 수십 초 기다리는 테스트는 이런 경계를 느리고 불안정하게 만든다.
단계
/root/work/fa-rate-window-lab/service.py에서 validate_limit(limit, window)는 bool 제외 양의 int limit와 양의 유한 int/float window만 허용해 (limit, float(window))를 반환합니다. 나머지는 ValueError입니다.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/fa-rate-window-lab
test -e /root/work/fa-rate-window-lab/service.py || cp /opt/fixtures/ten_labs/fa-rate-window-lab/service.py /root/work/fa-rate-window-lab/service.py
cd /root/work/fa-rate-window-lab
-
/root/work/fa-rate-window-lab/service.py에서 active(history, now, window)는 now-window보다 큰 시각만 원래 순서의 새 리스트로 반환합니다. history는 정렬된 비감소 시각입니다. -
/root/work/fa-rate-window-lab/service.py에서 retry_after(history, now, window)는 이미 정리된 비어 있지 않은 history의 첫 시각+window-now를 ceil한 값과 0 중 큰 정수입니다. 빈 리스트는 0입니다. -
/root/work/fa-rate-window-lab/service.py에서 history_for(state, key)는 없는 키면 빈 리스트, 있으면 그 기록의 사본을 반환합니다. 조회만으로 state를 수정하지 않습니다. -
/root/work/fa-rate-window-lab/service.py에서 admit(state, key, now, limit, window)는 설정 검증 후 해당 키의 만료 기록을 정리합니다. 여유가 있으면 now를 추가하고 (True,0), 꽉 찼으면 추가하지 않고 (False,retry_after)를 반환합니다. -
/root/work/fa-rate-window-lab/service.py에서 client_key(value)는 1~40자 ASCII 영문·숫자·하이픈 문자열을 그대로 반환하고 나머지는 ValueError입니다. -
/root/work/fa-rate-window-lab/service.py에서 limited_response(wait)는 상태 429, 본문 {error:'rate_limited'}, Retry-After 헤더는 wait를 문자열로 만든 JSONResponse입니다. -
/root/work/fa-rate-window-lab/service.py에서 create_app(clock, limit=2, window=10)는 GET /work에서 X-Client-ID를 검사해 잘못된 키는 400 {error:'invalid_client'}, 허용은 200 {ok:True}, 초과는 limited_response입니다. state는 앱별로 분리합니다.
참고
- 인터넷과 패키지 설치 없이 기존 lab-dev 환경에서 수행합니다.
- 각 단계는 45초 채점 예산 안에서 실행됩니다. 실제 sleep이나 네트워크 호출을 추가하지 마세요.
- 채점은 제출 모듈을 새로 불러오고 독립 입력과 임시 DB로 검사합니다. 예상값을 상수로 반환하는 대신 계약을 구현하세요.
- FastAPI 공식 문서 · pytest 공식 문서 · Python sqlite3
- 한계: 프로세스 메모리에 있는 단일 워커용 예제다. 여러 파드가 공유하는 전역 한도나 악의적인 클라이언트의 신원을 보장하지 않는다. X-Client-ID는 테스트용 키이므로 운영에서는 인증된 주체에서 키를 얻어야 한다. 지속적인 시계 역행은 단조 시계 사용으로 피해야 하며 이 실습의 clock은 비감소한다.
설정을 검증한다
/root/work/fa-rate-window-lab/service.py에서 validate_limit(limit, window)는 bool 제외 양의 int limit와 양의 유한 int/float window만 허용해 (limit, float(window))를 반환합니다. 나머지는 ValueError입니다.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/fa-rate-window-lab
test -e /root/work/fa-rate-window-lab/service.py || cp /opt/fixtures/ten_labs/fa-rate-window-lab/service.py /root/work/fa-rate-window-lab/service.py
cd /root/work/fa-rate-window-lab
bool은 int의 하위 타입입니다. NaN과 무한대도 따로 거절해야 합니다.
저장 후 bash /opt/lab/checks/fa-rate-window-lab/01-contract.sh로 확인하세요.
창의 왼쪽 경계를 제외한다
/root/work/fa-rate-window-lab/service.py에서 active(history, now, window)는 now-window보다 큰 시각만 원래 순서의 새 리스트로 반환합니다. history는 정렬된 비감소 시각입니다.
정확히 만료된 시각을 남기는 >=와 >의 차이를 확인합니다.
저장 후 bash /opt/lab/checks/fa-rate-window-lab/02-contract.sh로 확인하세요.
대기 시간을 올림한다
/root/work/fa-rate-window-lab/service.py에서 retry_after(history, now, window)는 이미 정리된 비어 있지 않은 history의 첫 시각+window-now를 ceil한 값과 0 중 큰 정수입니다. 빈 리스트는 0입니다.
0.2초 남았다고 Retry-After를 0으로 내면 클라이언트가 즉시 재요청합니다.
저장 후 bash /opt/lab/checks/fa-rate-window-lab/03-contract.sh로 확인하세요.
키마다 기록을 나눈다
/root/work/fa-rate-window-lab/service.py에서 history_for(state, key)는 없는 키면 빈 리스트, 있으면 그 기록의 사본을 반환합니다. 조회만으로 state를 수정하지 않습니다.
공유 리스트를 반환하면 한 요청의 정리가 다른 요청의 기록을 바꿀 수 있습니다.
저장 후 bash /opt/lab/checks/fa-rate-window-lab/04-contract.sh로 확인하세요.
허용 요청만 기록한다
/root/work/fa-rate-window-lab/service.py에서 admit(state, key, now, limit, window)는 설정 검증 후 해당 키의 만료 기록을 정리합니다. 여유가 있으면 now를 추가하고 (True,0), 꽉 찼으면 추가하지 않고 (False,retry_after)를 반환합니다.
거절된 요청을 추가하면 재시도할 때마다 만료 시각이 밀립니다.
저장 후 bash /opt/lab/checks/fa-rate-window-lab/05-contract.sh로 확인하세요.
클라이언트 키를 검증한다
/root/work/fa-rate-window-lab/service.py에서 client_key(value)는 1~40자 ASCII 영문·숫자·하이픈 문자열을 그대로 반환하고 나머지는 ValueError입니다.
무제한 키 크기로 상태 메모리를 압박하지 않게 입력의 범위를 제한합니다.
저장 후 bash /opt/lab/checks/fa-rate-window-lab/06-contract.sh로 확인하세요.
거절 응답을 만든다
/root/work/fa-rate-window-lab/service.py에서 limited_response(wait)는 상태 429, 본문 {error:'rate_limited'}, Retry-After 헤더는 wait를 문자열로 만든 JSONResponse입니다.
클라이언트가 재시도 시간을 알 수 있게 상태와 헤더를 함께 보냅니다.
저장 후 bash /opt/lab/checks/fa-rate-window-lab/07-contract.sh로 확인하세요.
가상 시간으로 요청 흐름을 끝낸다
/root/work/fa-rate-window-lab/service.py에서 create_app(clock, limit=2, window=10)는 GET /work에서 X-Client-ID를 검사해 잘못된 키는 400 {error:'invalid_client'}, 허용은 200 {ok:True}, 초과는 limited_response입니다. state는 앱별로 분리합니다.
실제로 sleep하지 말고 리스트에 담은 현재 시각을 clock 함수로 전달합니다.
저장 후 bash /opt/lab/checks/fa-rate-window-lab/08-contract.sh로 확인하세요.