핵심 개념
리소스와 컨텍스트, 그리고 컨텍스트별 평판 셀 — 점수·쿨다운·복귀·차단 목록이 어떻게 맞물리는지.
리소스 (Resource)
리소스는 풀에서 서로 대체 가능한 구성원 하나이고, kind 와 value 로 식별됩니다.
kind—PROXY,ACCOUNT,SESSION중 하나.value— 여러분이 정하고 여러분에게만 의미가 있는 불투명한 문자열. 프록시 엔드포인트, 계정 id, 세션 핸들 같은 것. 풀은 이 값을 절대 파싱하지 않습니다.
이 한 쌍이 곧 신원입니다. PROXY/proxy-1 과 ACCOUNT/proxy-1 은 서로 다른 리소스입니다. 등록은 멱등이므로 워커가 부팅할 때마다 Register 를 부르는 것이 정상적인 패턴입니다.
컨텍스트 (Context)
컨텍스트는 그 리소스를 무엇에 쓰는지 이름 붙인 문자열입니다 — checkout-us, search-eu, login-jp 처럼. Acquire 와 Report 에 함께 넘기고, 모든 평판 조회의 나머지 절반을 담당합니다.
컨텍스트는 자유 형식이고 처음 쓰는 순간 생깁니다 — 등록 단계가 없습니다. 하나의 컨텍스트가, 리소스를 독립적으로 태울 수 있는 하나의 대상이 되게 고르세요. 보통 목적지, 여러분 쪽의 고객, 또는 워크로드 단위입니다. 너무 굵으면(모든 것에 default) 목적지 하나가 나빠졌을 때 그 리소스가 전부에서 빠지고, 너무 잘게 쪼개면(요청마다 하나) 어떤 셀도 쓸 만한 이력을 쌓지 못하는 데다 새 셀마다 풀의 셀 예산을 깎아먹습니다.
ReputationCell — (리소스 × 컨텍스트) 한 칸
평판은 리소스별로 저장되지 않습니다. 리소스 × 컨텍스트 조합별로 저장되고, 그 조합을 평판 셀(ReputationCell)이라고 부릅니다. 리소스 하나는 그동안 쓰인 컨텍스트 수만큼의 셀을 갖고, 각 셀이 자기 점수·자기 연속 기록·자기 쿨다운을 따로 가집니다.
| 필드 | 의미 |
|---|---|
score | [-100, 100] 범위의 연속적인 평판 점수. 0.0 에서 시작하고, 보고된 결과마다 움직이며, 선택 가중치가 됩니다. |
state | HEALTHY · COOLING · RECOVERING · BLOCKLISTED — 이 셀을 내줄 수 있는지 결정하는 관문. |
consecutiveFailures | 연속 실패 횟수. 쿨다운을 시작시키는 것은 이 값이 임계치에 닿는 순간입니다. |
consecutiveSuccesses | 연속 성공 횟수. 관찰 기간을 끝내는 것은 이 값이 복귀 임계치에 닿는 순간입니다. |
windowSize | 최근 결과를 몇 개 보관하는지(기본 10). 연속 카운터는 상한 없이 누적되는 값이고, 윈도는 크기가 정해진 최근 이력입니다. |
cooldownUntil | 현재 쿨다운이 끝나는 시각. 쿨다운 중이 아니면 null. |
updatedAt | 이 셀에 마지막 결과가 반영된 시각. |
셀을 만드는 것은 Report 이고, Acquire 는 절대 만들지 않습니다. 그래서 방금 등록한 리소스는 보고를 하기 전까지 대시보드에 contexts: 0 으로 보입니다. Acquire 는 후보를 평가할 때 저장되지 않는 임시 신규 셀로 점수를 매기므로, 아무도 보고하지 않은 리소스는 중립이자 HEALTHY 로 취급됩니다 — 새 리소스는 자격을 쌓을 필요 없이 곧바로 선택 대상이 됩니다.
컨텍스트별로 나누는 것이 이 모델의 핵심입니다
프록시 하나와 목적지 둘을 생각해 봅시다. 그 프록시가 결제 엔드포인트에서는 강하게 차단당했지만 검색 쪽에서는 완벽하게 동작합니다. 평판을 리소스별로 저장하면 그 차단은 "프록시에 대한 사실"이 되므로, 검색에서 잘 되던 프록시를 잃든지 아니면 결제에 계속 타 버린 프록시를 밀어넣든지 둘 중 하나입니다. 둘 다 틀렸고, 어느 쪽이 되는지는 여러분이 추측해서 정한 임계값에 달려 있습니다.
PROXY proxy-1.example.net:8080
├─ context "checkout-us" score -34.0 state COOLING cooldownUntil 10:41:02
└─ context "search-eu" score 35.0 state HEALTHY cooldownUntil null
Acquire("search-eu") → may return proxy-1 (its search-eu cell is healthy)
Acquire("checkout-us") → will not return it (its checkout-us cell is cooling)셀이 있으면 그 차단은 이 컨텍스트에서의 이 프록시에 대한 사실이 됩니다. 프록시는 검색 트래픽을 그대로 다 받고, 결제는 그 프록시를 우회하며, 결제 쪽 신뢰는 스스로 다시 벌어옵니다. 이걸 여러분 코드에서 모델링할 필요가 없습니다 — 보고를 컨텍스트별로 하기만 하면 격리는 키에서 저절로 따라옵니다.
네 가지 상태
| 상태 | 선택 대상인가? | 의미 |
|---|---|---|
HEALTHY | 예 | 신뢰됨. 정상 상태입니다. |
COOLING | 아니오 | 쿨다운이 끝날 때까지 빼 둔 상태. |
RECOVERING | 예 | 관찰 기간 — 다시 내주지만 아직 자기 증명 중입니다. |
BLOCKLISTED | 아니오 | 명시적으로 풀어 줄 때까지 격리. 트래픽만으로는 절대 벗어나지 않습니다. |
consecutiveFailures >= coolAfter (2)
┌───────────┐ ───────────────────────────────────▶ ┌───────────┐
│ HEALTHY │ │ COOLING │
└───────────┘ ◀─────────────────────┐ └───────────┘
▲ │ │
│ consecutiveSuccesses │ │ cooldown expired,
│ >= recoverAfter (2) │ │ then one success
│ ┌────────────┐ ◀───────────┘
└────────────────────── │ RECOVERING │
└────────────┘
BLOCKLISTED is reachable from any state via an operator block, and is left
only by unblock or block expiry — never by reported outcomes.쿨다운: 실패 하나가 실제로 하는 일
보고된 실패는 모두 점수를 내리고 연속 실패 횟수를 하나 올립니다. 그 횟수가 쿨다운 임계치(기본 2)에 닿을 때에만 셀이 COOLING 으로 넘어갑니다 — 한 번 튄 것으로 건강한 리소스를 빼지 않습니다. 벌점과 쿨다운 길이는 둘 다 여러분이 보고한 실패 유형에 달려 있습니다.
| FailureType | 점수 벌점 | 기본 쿨다운 | 이럴 때 쓰세요 |
|---|---|---|---|
BLOCKED | 30 | 1 시간 | 실제 차단 — 403, 캡차 벽, 밴 페이지. |
TLS_HANDSHAKE | 15 | 5 분 | TLS 협상 실패 — 중간 개입이거나 죽은 엔드포인트인 경우가 많습니다. |
CONNECTION_RESET | 10 | 2 분 | 전송 중에 연결이 끊긴 경우. |
TIMEOUT | 5 | 1 분 | 시간 안에 응답이 없는 경우. |
SLOW | 2 | 30 초 | 완료됐지만 쓸 만하다고 보기 어려울 만큼 느린 경우. |
쿨다운은 연속 실패마다 그 기본값을 두 배로 늘리고 64 배에서 멈춥니다 — base(type) × 2^min(consecutiveFailures - 1, 6). 그래서 계속 차단당하는 프록시는 1 시간, 2 시간, 4 시간… 으로 늘어나고, 영원히 한 시간마다 다시 시도되지 않습니다. 그저 느렸을 뿐인 리소스는 30 초에서 시작합니다.
쿨다운이 진행되는 동안 들어온 추가 실패는 점수는 계속 움직이지만 쿨다운을 다시 시작시키거나 cooling 이벤트를 다시 내보내지는 않습니다 — 늦게 도착한 결과는 이미 벌을 받고 있는 그 사고에 속하고 새로운 사고가 아닙니다. 나쁜 1 분이 지수적으로 커지는 격리로 번지지 않게 막는 장치입니다.
복귀: 셀이 신뢰를 다시 벌어오는 방법
복귀는 시간만으로 되지 않고 성공이 있어야 합니다. 쿨다운이 끝나도 셀이 조용히 건강해지지는 않습니다 — 그 뒤 처음 보고된 성공이 셀을 RECOVERING 으로 옮기고, 연속 성공 카운트는 그 순간부터 다시 셉니다. 그래서 아직 쿨다운 중일 때 우연히 보고된 성공들로 관찰 기간을 건너뛸 수 없습니다. recoverAfter 만큼(기본 2) 연속 성공하면 HEALTHY 로 승격되고 ResourceRecovered 이벤트가 나갑니다.
RECOVERING 은 선택 대상입니다. 그게 핵심입니다 — 관찰 기간은 "벤치에서 기다리는 중"이 아니라 "로테이션에 돌아왔고 지켜보는 중"이라는 뜻입니다. 관찰 기간에 실패가 한 번 나오면 연속 성공이 초기화되고, 쿨다운 임계치에 닿으면 다시 빠집니다.
점수와 선택 방식
점수는 [-100, 100] 사이의 연속값이고 양끝에서 잘립니다. 성공은 5 를 더하고, 실패는 그 유형의 벌점을 뺍니다. 상태와는 별개의 신호입니다 — 상태는 셀을 내줄 수 있는지를 정하고, 점수는 내줄 수 있는 것들 중에서 얼마나 자주 뽑힐지를 정합니다.
선택은 "항상 가장 좋은 것"이 아니라 점수 가중 무작위입니다. 자격 있는 후보 각각의 가중치는 (score − lowestScoreAmongCandidates) + 1.0 이므로 점수가 높으면 더 자주 뽑히지만 자격 있는 모든 후보가 0 이 아닌 확률을 유지합니다. 여기서 두 가지가 따라 나오고 둘 다 의도적입니다. 부하가 가장 좋은 리소스 하나를 때리는 대신 건강한 풀 전체로 퍼지고, 가장 약한 자격 후보도 이따금 순서를 받습니다 — 복귀 직전의 리소스가 영원히 굶지 않고 다시 시험받는 방식이 이것입니다.
차단 목록 (Blocklist)
차단 목록은 운영자의 수동 개입이고, 위의 모든 것과 달리 리소스 단위입니다(셀 단위가 아닙니다). 차단하면 모든 컨텍스트에서 한 번에 선택에서 격리되며, 엔진은 스스로 리소스를 이 집합에 넣거나 빼지 않습니다.
- 임시 —
POST …/block?seconds=3600. 스스로 만료됩니다. - 영구 —
POST …/block?permanent=true. 명시적 해제로만 풀립니다. - 해제 —
DELETE …/block.RESOURCE_UNBLOCKED이벤트를 내보냅니다.
차단은 진행 중인 확보도 이깁니다. 리소스가 선택된 뒤 리스가 확정되기 전에 차단되면, 그 확정은 존중되지 않고 되돌려집니다 — 이미 응답이 돌아간 block 호출을 우회할 방법은 없습니다. 그리고 Renew 는 차단 목록에 오른 리소스의 리스를 연장하지 않으므로, 이미 잡혀 있던 리스는 살아남지 않고 TTL 에서 끝납니다.
BLOCKLISTED 는 엔진에게 종단 상태이므로, 차단된 리소스에 대해 보고된 결과는 점수와 윈도는 계속 움직이지만(나중에 해제를 판단할 근거가 됩니다) 상태를 바꾸지는 못합니다.
리스 (Lease)
Acquire 는 리소스를 추천하는 데서 그치지 않고 리스합니다. 한 컨텍스트에 대한 배타적 점유이고 기본 30 초 동안 유효합니다. 리스된 동안에는 다른 Acquire 가 그 리소스를 내주지 않습니다 — 컨텍스트가 다르더라도 그렇습니다 — 그래서 두 워커가 실수로 한 프록시를 같이 쓰는 일이 없습니다.
Renew는 리스를 TTL 만큼 더 연장합니다. 작업이 창을 넘길 때 쓰세요.Release는 리소스를 즉시 돌려줍니다. 정확성을 위해 필수는 아니지만(만료가 안전망입니다) 제때 반납하는 것이 풀을 TTL 만료를 기다리는 상태가 아니라 일하는 상태로 유지합니다.- 리스는 단조 증가하는 펜싱 토큰을 함께 들고 있습니다.
Renew와Release는 현재 보유자를 위해서만 동작하므로, 이미 리스가 만료돼 다른 워커가 다시 가져간 뒤에 늦게 도착한 호출이 새 리스를 건드릴 수 없습니다.
리스는 런타임 조정 장치이고 영속 상태가 아닙니다. 풀 스냅샷에 포함되지 않으므로 재시작 직후에는 아무것도 잡혀 있지 않습니다.
이벤트
위의 모든 판단은 이벤트로 나갑니다. 실시간 gRPC SubscribeEvents 스트림과 GET /api/events 가 읽는 영구 감사 로그 양쪽으로 갑니다. 보게 될 이벤트 유형은 RESOURCE_LEASED, LEASE_RELEASED, RESOURCE_COOLED(원인이 된 실패 유형과 쿨다운 종료 시각 포함), RESOURCE_RECOVERED, RESOURCE_BLOCKLISTED, RESOURCE_UNBLOCKED 입니다. 자격 있는 후보를 찾지 못한 확보는 실시간 스트림에서 AcquisitionRejected 로 보고됩니다.
기본값 한눈에
| 손잡이 | 기본값 | 무엇을 정하는가 |
|---|---|---|
windowSize | 10 | 셀마다 보관하는 최근 결과 수. |
coolAfter | 2 | 쿨다운에 들어가기까지의 연속 실패 수. |
recoverAfter | 2 | 관찰 기간을 벗어나기까지의 연속 성공 수. |
| lease TTL | 30 초 | 확보한 리스가 유효한 시간. |
| 쿨다운 백오프 상한 | 기본값의 64 배 | 지수적으로 늘어나는 쿨다운의 천장. |
| 점수 범위 | −100 … 100 | 연속적인 평판 값을 자르는 범위. |
이 값들은 호스티드 배포의 설정이고, 엔진의 레퍼런스 기본값과 같습니다. 이 페이지의 모든 규칙 — 벌점, 백오프 곡선, 전이 조건, 선택 가중치 — 은 오픈소스 엔진 PreAgile/reputation-pool 에 구현돼 있으므로, 이 페이지의 말을 믿는 대신 소스를 읽을 수 있습니다. 다음: 인증.