Skip to content
isdnetworks
Go back

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

(수정됨)

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

Table of contents

Open Table of contents

검증 — 제한기를 봤다

속도 제한 구현을 열었다.

// 모듈 전역
const limiter = new Bottleneck({ reservoir: 9, reservoirRefreshInterval: 1000, reservoirRefreshAmount: 9 });

function callVendor(params) {
  return limiter.schedule(() => client.get(params));
}

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

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

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

pm2 클러스터 프로세스마다 자기 limiter 를 따로 갖는다.

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

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

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

결과 — 최대 72 대 한도 19

실행 설정을 봤다.

instances: 8   # pm2 ecosystem

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

초당 9 × 8 = 최대 72

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

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

주의 — 안 터진 이유

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

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

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

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

제약 — 확장이 해로운 이유

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

인스턴스를 늘린다
    ↓
제한기가 그만큼 더 곱해진다
    ↓
외부 한도를 확실히 초과
    ↓
차단당한다

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

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

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

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

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

instances 나 인스턴스 수 조정으로 해결할 문제가 아니었다. 워커 수를 줄이는 것과 제한기를 공유 저장소로 옮기는 것 두 가지 방법 중 선결 과제로 뒤쪽을 골랐다. 이유는 워커를 줄이면 처리량이 같이 줄어서다.

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

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

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

정리


Share this post on:

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