Skip to content
isdnetworks
Go back

제한기가 워커 수만큼 곱해진다

서버 운영 방식을 바꾸는 검토를 하면서 자동 확장을 붙일 수 있는지 봤다. 이 서비스는 AsyncLimiter 를 두고 외부 API 를 부르는데 그쪽에 호출 속도 제한이 걸려 있다.

Table of contents

Open Table of contents

검증 — 제한기를 봤다

속도 제한 구현을 열었다.

# 모듈 전역
_limiter = AsyncLimiter(max_rate=9, time_period=1)

async def call_vendor(...):
    async with _limiter:
        return await client.get(...)

_limiter 가 모듈 전역 변수이고 공유 저장소가 없다.

max_rate 계산이 프로세스 메모리 안에만 있는 상태라는 뜻이다. 그러면 프로세스가 여럿일 때 무슨 일이 일어나는지가 곧바로 따라 나온다.

원인 — 프로세스마다 따로였다

uvicorn 워커마다 자기 _limiter 를 따로 갖는다.

프로세스 1  →  자기 제한기 (초당 9)
프로세스 2  →  자기 제한기 (초당 9)
...

각자 max_rate 인 초당 9까지 보내므로 합치면 프로세스 수만큼 곱해진다.

AsyncLimiter 는 자기 프로세스 안에서만 정확하다. 인스턴스를 늘리기 전에 이미 그 안에서 전제가 깨져 있었다.

결과 — 최대 72 대 한도 19

실행 설정을 봤다.

--workers 8

--workers 가 8이므로 최댓값이 이렇게 된다.

초당 9 × 8 = 최대 72

외부 API 가 허용하는 실효 한도는 약 19였다.

이미 세 배 넘게 넘길 수 있는 상태였다. 확장을 붙이기 전부터 단일 인스턴스라는 전제가 내부에서 무너져 있었던 것이다.

주의 — 안 터진 이유

그런데 지금까지 안 터지고 있었다.

전체 약 8/초
워커당 약 1/초

워커당 실트래픽이 낮아서 async with _limiter 에서 대기할 일이 없었다.

_limiter 가 있으나 마나인 상태로 돌고 있었고 트래픽이 늘면 그때 터진다. 안 터지는 것이 설계 때문이 아니라 운 때문이었다.

제약 — 확장이 해로운 이유

원래 물음으로 돌아가면 자동 확장은 붙일 수 없었다.

인스턴스를 늘린다

제한기가 그만큼 더 곱해진다

외부 한도를 확실히 초과

차단당한다

늘리면 처리량이 는다는 기대가 이 구조에서는 반대로 작동한다.

배포 설정에는 복제본을 1로 고정하고 호출자가 단일 주소를 부르게 하는 전제가 있었다. 그런데 그 하나 안에 워커가 8개라서 전제가 이미 내부적으로 깨져 있었다.

결론 — 선결 과제와 중립이 된 축

확장이 필요해지면 AsyncLimiter 부터 바꿔야 한다.

[지금]  프로세스별 메모리
[필요]  공유 저장소 기반

--workers 나 인스턴스 수 조정으로 해결할 문제가 아니었다.

원래 검토는 어느 운영 방식이 나은가였고 확장 가능이 한쪽의 장점이었다. 이 앱에는 확장이 무의미하므로 그 축이 중립이 됐고 다른 기준으로 정해야 했다.

나가는 경로도 함께 봤는데 고정 IP 가 필요한지가 쟁점이었다. 나가는 트래픽이 별도 로드밸런서를 경유하고 있어서 앱 자체의 고정 주소는 필요 없었고 인스턴스가 바뀌어도 나가는 주소는 그대로라 그 축도 중립이 됐다.

이 조사에서 지킨 것이 하나 있다. 제한기가 있으니 지켜지겠지로 넘기지 않고 전역 변수이고 프로세스별이라는 것을 코드로 재확인했는데 있다와 동작한다는 다르다.

정리


Share this post on:

Previous Post
처리율 저하가 스톨은 아니다
Next Post
자동 복구가 매분 돌고 있었다