---
template: policy-doc@1
doc_id: 03-rate-limits-and-batch
title: 빈도·묶음 한도 — 몇 번이나 부르는가, 한 번에 몇 개를 처리하는가
config: ../_meta/template-config.yaml
last_verified: 4d798f55
policies:
  - id: limits:bulk-write-chunking
    status: active-prod
    evidence: [S-constant, S-guard]
    anchors:
      - insuniverse-server/apps/api/src/modules/client/client/client.repository.ts:39
      - insuniverse-server/apps/api/src/modules/treat/treat.repository.ts:46
      - insuniverse-server/apps/api/src/modules/order/order-detail/order-detail.service.ts:115
      - insuniverse-server/apps/api/src/modules/treat/log/log-treat.service.ts:52
      - insuniverse-server/apps/api/src/modules/client/client/client.repository.ts:44
      - insuniverse-server/apps/api/src/modules/treat/treat.repository.ts:49
      - insuniverse-server/apps/api/src/modules/order/order-detail/order-detail.service.ts:195
      - insuniverse-server/apps/api/src/modules/treat/log/log-treat.service.ts:74
  - id: limits:client-poll-cadence
    status: active-prod
    evidence: [S-constant, S-component]
    limitations:
      - 화면이 얼마나 자주 묻는지는 화면 상수가 정한다. 서버가 그 주기를 강제하거나 제한하는 자리를 동결 기준점에서 찾지 못했다
    anchors:
      - insuniverse-site/front/src/pages/alimTalk/QueuePage.tsx:16
      - insuniverse-site/front/src/pages/alimTalk/B2cQueuePage.tsx:15
  - id: limits:certify-throttle-fails-open
    status: conditional-unknown
    evidence: [S-constant, S-guard]
    unknown_reason: 분당 허용 횟수를 정하는 환경 설정의 운영값을 이 조사가 확보하지 않았다. 코드는 설정이 없을 때 이 관문을 통과시키므로 운영에서 이 한도가 켜져 있는지 판정하지 못한다
    limitations:
      - 세는 단위인 전화번호는 요청 본문이 싣는 값이고 이 관문은 인증보다 앞에 선다. 접속 주소는 그 값이 비었을 때만 쓰이므로, 신원을 돌려 가며 부르면 한도가 켜져 있어도 세는 자리가 매번 새로 시작한다. 같은 사람의 반복을 막는다는 설명은 번호를 정직하게 쓰는 호출에만 성립한다
    anchors:
      - insuniverse-server/apps/api/src/modules/treat/certify-rate-limit.guard.ts:31
      - insuniverse-server/apps/api/src/modules/treat/certify-rate-limit.guard.ts:50
      - insuniverse-server/apps/api/src/modules/treat/certify-rate-limit.guard.ts:51
  - id: limits:provider-pool-budget-shape
    status: conditional-unknown
    evidence: [S-constant, S-guard]
    unknown_reason: 분산 풀의 예산을 정하는 환경 설정의 운영값을 이 조사가 확보하지 않았다. 자리 이름과 계산 형태만 코드에서 읽었고 각 자리에 실제로 들어가는 수는 확보하지 않았다
    anchors:
      - insuniverse-server/apps/api/src/config/configuration.ts:33
      - insuniverse-server/apps/api/src/config/configuration.ts:38
      - insuniverse-server/apps/api/src/modules/treat/dispatch/treat-queue.service.ts:166
  - id: limits:subscription-batch-is-serial
    status: staged-only
    evidence: [S-constant, S-guard]
    anchors:
      - insuniverse-server/apps/api/src/modules/fc/agency/subscription-batch.ts:1
      - insuniverse-server/apps/api/src/modules/fc/agency/subscription-batch.ts:8
  - id: limits:car-canary-flow-cap
    status: staged-only
    evidence: [S-constant, S-guard]
    anchors:
      - insuniverse-server/apps/api/src/modules/treat/treat-car-canary.service.ts:6
      - insuniverse-server/apps/api/src/modules/treat/treat-car-canary.service.ts:27
  - id: limits:client-retry-caps
    status: staged-only
    evidence: [S-constant, S-component]
    limitations:
      - 재시도 상한은 화면이 스스로 지키는 값이며 서버가 그것을 확인하지 않는다. 화면을 거치지 않는 호출에 상한이 걸리는지는 이 문서가 판정하지 않는다
    anchors:
      - insuniverse-site/front/src/pages/alimTalk/QueuePage.tsx:17
      - insuniverse-site/front/src/pages/alimTalk/Step2Page.tsx:41
      - insuniverse-site/front/src/pages/alimTalk/Step2Page.tsx:40
---

# 빈도·묶음 한도 — 몇 번이나 부르는가, 한 번에 몇 개를 처리하는가

이 문서는 **단위 시간당 횟수**와 **한 번에 처리하는 개수**만 다룬다. 한 번의 유효 기간은 이 문서의
축이 아니다 — 시간 한도 문서가 든다. 값이 밀리초로 적혀 있어도 그것이 "얼마 동안 유효한가" 를
정하면 시간 문서이고, "얼마마다 다시 부르는가" 를 정하면 이 문서다.

읽는 사람이 여기서 얻어야 하는 것은 **빈도 한도의 대부분이 지금 켜져 있지 않거나 아직 운영에 닿지
않았다**는 사실이다. 실제로 도는 빈도 제어는 저장·조회를 묶어 나누는 것과 화면이 스스로 지키는 확인
주기뿐이고, 서버가 요청 횟수를 세어 막는 관문은 존재하지만 설정 없이는 통과시킨다.
**아직 운영에 닿지 않은 정책은 셋이다.**

기능 게이트의 이름과 운영 활성 여부는 이 문서가 쓰지 않는다. 관문이 어떤 기전으로 도는지와 그
기전이 어디에 있는지까지만 적고, 이름과 활성 판정은 기능 게이트 문서로 넘긴다.

여기 없는 것: 금액과 개수의 상한, 한 번의 유효 기간, 스케줄 주기와 벤더별 대기 시간, 기능 게이트의
운영 활성 여부, 동시 분석 자리의 실제 수. 각각 다른 문서의 소유이며 마지막 것은 다음 마일스톤이다.

## 정책 (기획)

### limits:bulk-write-chunking

한 번에 많은 행을 쓰거나 읽을 때는 통째로 하지 않고 나눠 돈다. 쓰기는 **500건**씩, 대용량 내려받기의
읽기는 **2,000건**씩이다.

두 값이 다른 것이 이 정책의 축이다. 쓰기 쪽은 한 트랜잭션이 잡는 잠금의 크기를 줄이는 것이 목적
이고, 읽기 쪽은 한 번에 메모리에 올리는 양을 줄이는 것이 목적이다. 목적이 다르므로 값도 다르고,
한쪽 값을 다른 쪽에 맞출 이유가 없다.

**묶음 크기를 정하는 자리는 넷이다** — 대외 분석 저장, 진료 저장, 분석 관리 내려받기, 분석 로그
내려받기. 각각 자기 상수를 들고 있으며 공유 상수가 아니다.

이용자에게 만드는 결과: 큰 요청이 한 번에 실패하는 대신 나눠 처리된다. 내려받기는 묶음이 상한보다
적게 오면 마지막 묶음으로 보고 끝낸다.

### limits:client-poll-cadence

대기열 화면은 **2초**마다 서버에 순번을 묻는다. 일반 대기열과 기업 대기열 두 화면이 같은 주기를
쓴다.

이 주기가 다른 값들과 묶이는 자리가 있다. 대기열의 자리 배정 자격 창이 이 주기의 여섯 배로 잡혀
있어, 확인이 몇 번 연속으로 끊겨야 자격이 사라지도록 맞춰져 있다. 주기를 바꾸면 그 관계가 깨진다 —
값 하나가 아니라 두 값의 비율이 정책이다.

주기를 정하는 것은 화면이고 서버가 그것을 강제하거나 제한하는 자리를 동결 기준점에서 찾지 못했다.
화면이 더 자주 물어도 서버가 막지 않는다.

이용자에게 만드는 결과: 순번은 즉시가 아니라 주기마다 갱신된다. 화면을 배경으로 내리면 확인이
끊기고, 그때 벌어지는 일은 시간 한도 문서의 두 창이 정한다.

## 강제 지점 (개발)

### limits:bulk-write-chunking

네 자리가 각각 자기 상수를 선언하고 각자 소비한다. 저장 두 자리는 저장소 계층에서 배열을 잘라
넣고, 내려받기 두 자리는 서비스 계층에서 묶음 크기로 조회한 뒤 그보다 적게 오면 끝낸다.

- 슬롯 — 대외 분석 CHUNK_SIZE: 값 `500` · 층 `—` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/client/client/client.repository.ts:39`
- 슬롯 — 진료 CHUNK_SIZE: 값 `500` · 층 `—` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/treat.repository.ts:46`
- 슬롯 — 분석관리 BATCH: 값 `2000` · 층 `—` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/order/order-detail/order-detail.service.ts:115`
- 슬롯 — 분석로그 BATCH: 값 `2000` · 층 `—` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/log/log-treat.service.ts:52`
- 슬롯 — 대외 분석 묶음 절단: 값 `—` · 층 `저장소 질의` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/client/client/client.repository.ts:44`
- 슬롯 — 진료 묶음 절단: 값 `—` · 층 `저장소 질의` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/treat.repository.ts:49`
- 슬롯 — 분석관리 묶음 종료 판정: 값 `—` · 층 `서비스 가드` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/order/order-detail/order-detail.service.ts:195`
- 슬롯 — 분석로그 묶음 종료 판정: 값 `—` · 층 `서비스 가드` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/log/log-treat.service.ts:74`

### limits:client-poll-cadence

두 화면이 각각 상수를 선언한다. 공유 상수가 아니라 같은 값의 복제이며, 서버 쪽에 대응하는 강제
자리가 없다.

- 슬롯 — 대기열 POLL_MS: 값 `2000` · 층 `클라이언트 상수` · 출처 `고정값` · 앵커 `insuniverse-site/front/src/pages/alimTalk/QueuePage.tsx:16`
- 슬롯 — 기업 대기열 POLL_MS: 값 `2000` · 층 `클라이언트 상수` · 출처 `고정값` · 앵커 `insuniverse-site/front/src/pages/alimTalk/B2cQueuePage.tsx:15`

### limits:certify-throttle-fails-open

관문은 설정에서 분당 허용 횟수를 읽고, 그 수가 양수가 아니거나 분산 저장소가 없으면 그대로
통과시킨다. 켜져 있으면 전화번호를 우선 키로 삼고 없을 때 접속 주소를 키로 삼아 1분 창 안의 횟수를
세며, 넘으면 요청이 많다는 응답으로 거절한다. 분산 저장소 예외는 통과 쪽으로 접힌다.

- 슬롯 — perMin: 값 `0` · 층 `—` · 출처 `설정 미지정 시` · 앵커 `insuniverse-server/apps/api/src/modules/treat/certify-rate-limit.guard.ts:31`
- 슬롯 — 분당 창 길이: 값 `60000` · 층 `서비스 가드` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/certify-rate-limit.guard.ts:50`
- 슬롯 — 분당 초과 판정: 값 `—` · 층 `서비스 가드` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/certify-rate-limit.guard.ts:51`

### limits:provider-pool-budget-shape

설정 문자열이 공급자마다 네 자리를 콤마와 콜론으로 구분해 담는다. 자리 이름은 키·분당 한도·플로우
비용·단가이며, 대기열이 앞의 두 수를 나눠 그 공급자가 동시에 열 수 있는 자리 수를 계산한다. 이
문서는 자리 이름과 계산 형태까지만 적고 각 자리의 수는 적지 않는다.

**이 문자열은 폴백이다.** 풀을 읽는 자리는 저장소의 활성 행을 먼저 보고, 그 결과가 비었거나 조회가
실패했을 때만 이 문자열로 내려간다. 그래서 문자열을 고쳐도 저장소에 행이 있으면 실효 용량은 안
바뀐다. 어느 쪽이 실제로 쓰이는지의 우선순위와 그 해석은 이 문서의 축이 아니라 운영 문서가 든다 —
이 문서가 드는 것은 상수의 정의와 자리 이름까지다.

- 슬롯 — treatPool: 값 `octover:30:3:1,coocon:30:3:1` · 층 `—` · 출처 `설정 미지정 시` · 앵커 `insuniverse-server/apps/api/src/config/configuration.ts:33`
- 슬롯 — 예산 자리 이름: 값 `key·limitPerMin·flowCost·unitCost` · 층 `—` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/config/configuration.ts:38`
- 슬롯 — 동시 슬롯 상한 산출: 값 `—` · 층 `서비스 가드` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/dispatch/treat-queue.service.ts:166`

### limits:subscription-batch-is-serial

묶음 크기 상수가 하나 있고 그것이 반복문의 보폭이다. 값이 1이면 묶음 처리가 아니라 순차 처리와
같아진다. 선언 자리의 주석이 같은 기업 계약 행을 동시에 갱신하다 생기는 잠금 충돌을 막으려
줄였다고 적고 있다.

- 슬롯 — BATCH_SIZE: 값 `1` · 층 `—` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/fc/agency/subscription-batch.ts:1`
- 슬롯 — 순차 진행 보폭: 값 `—` · 층 `서비스 가드` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/fc/agency/subscription-batch.ts:8`

### limits:car-canary-flow-cap

전역 자리 수 상수가 하나 있고 예약 호출의 인자로 넘어간다. 예약이 실패하면 자동차 수집을 건너뛰고
건강 수집만 진행한다.

- 슬롯 — CAR_MAX_FLOWS: 값 `3` · 층 `—` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/treat-car-canary.service.ts:6`
- 슬롯 — 전역 좌석 예약 인자: 값 `—` · 층 `서비스 가드` · 출처 `고정값` · 앵커 `insuniverse-server/apps/api/src/modules/treat/treat-car-canary.service.ts:27`

### limits:client-retry-caps

두 화면이 각각 재시도 상한 상수를 들고, 그중 한 화면은 재개 확인 주기도 따로 갖는다. 세 자리 전부
화면 안에서만 쓰이며 서버가 이 수를 확인하지 않는다.

- 슬롯 — RESUME_MAX_ERRORS: 값 `3` · 층 `클라이언트 상수` · 출처 `고정값` · 앵커 `insuniverse-site/front/src/pages/alimTalk/QueuePage.tsx:17`
- 슬롯 — CERTIFY_RESUME_MAX_ERRORS: 값 `3` · 층 `클라이언트 상수` · 출처 `고정값` · 앵커 `insuniverse-site/front/src/pages/alimTalk/Step2Page.tsx:41`
- 슬롯 — CERTIFY_RESUME_POLL_MS: 값 `1500` · 층 `클라이언트 상수` · 출처 `고정값` · 앵커 `insuniverse-site/front/src/pages/alimTalk/Step2Page.tsx:40`

## 적용 범위 (actor × 서비스)

| 정책 id | 관리자 | 영업대리점 | 기업담당자 | 일반회원 | 기업회원 | server | site | admin |
|---|---|---|---|---|---|---|---|---|
| limits:bulk-write-chunking | 적용 | — | 적용 | 적용 | 적용 | 강제 지점 | — | — |
| limits:client-poll-cadence | — | — | 적용 | 적용 | 적용 | — | 강제 지점 | — |
| limits:certify-throttle-fails-open | — | — | 적용 | 적용 | 적용 | 강제 지점 | — | — |
| limits:provider-pool-budget-shape | 적용 | — | 적용 | 적용 | 적용 | 강제 지점 | — | — |
| limits:subscription-batch-is-serial | 적용 | 적용 | — | — | — | 강제 지점 | — | — |
| limits:car-canary-flow-cap | — | — | 적용 | 적용 | 적용 | 강제 지점 | — | — |
| limits:client-retry-caps | — | — | 적용 | 적용 | 적용 | — | 강제 지점 | — |

서비스 열 셋은 기계가 대조한다. 이 표에서 읽어야 하는 것은 **화면 두 정책의 server 열이 `—`** 라는
사실이다. 확인 주기와 재시도 상한은 화면이 스스로 지키는 값이며, 서버가 같은 수를 다시 확인하는
자리를 찾지 못했다. 화면을 거치지 않는 호출에 대해 이 문서는 막힌다고도 막히지 않는다고도 말하지
않는다.

actor 다섯 열은 대조 밖이다. 권한 모델을 동결 트리에서 되짚을 경로가 이 조사에 없다.

## 예외·폴백

| 정책 id | 예외 조건 | 폴백 동작 | 확인 근거 |
|---|---|---|---|
| limits:bulk-write-chunking | 묶음이 상한보다 적게 옴 | 마지막 묶음으로 보고 반복을 끝낸다 | `order-detail.service.ts:195` |
| limits:bulk-write-chunking | 한 묶음이 실패 | 한 호출의 묶음 전체가 하나의 트랜잭션이라 앞 묶음도 함께 되돌아간다 | `treat.repository.ts:49` |
| limits:client-poll-cadence | 화면이 배경으로 내려감 | 확인이 끊긴다. 그다음 일은 대기열 두 창이 정한다 | `QueuePage.tsx:16` |
| limits:client-poll-cadence | 화면이 더 자주 물음 | 서버가 막지 않는다. 강제 자리를 찾지 못했다 | `B2cQueuePage.tsx:15` |
| limits:certify-throttle-fails-open | 분당 허용 횟수가 설정되지 않음 | 관문이 그대로 통과시킨다. 동작이 켜기 전과 같다 | `certify-rate-limit.guard.ts:31` |
| limits:certify-throttle-fails-open | 분산 저장소 장애 | 통과 쪽으로 접힌다. 실제 이용자를 자르지 않는다 | `certify-rate-limit.guard.ts:51` |
| limits:certify-throttle-fails-open | 세기는 성공하고 만료 걸기만 실패 | 이번 요청은 통과하나 만료 없는 값이 남아 이후 요청이 계속 막힐 수 있다 | `certify-rate-limit.guard.ts:50` |
| limits:certify-throttle-fails-open | 매 요청 다른 전화번호를 실어 보냄 | 세는 자리가 매번 새로 시작한다. 접속 주소 폴백은 번호가 빈 경우에만 걸린다 | `certify-rate-limit.guard.ts:45` |
| limits:certify-throttle-fails-open | 전화번호도 접속 주소도 없음 | 셀 키가 없어 통과시킨다 | `certify-rate-limit.guard.ts:50` |
| limits:provider-pool-budget-shape | 설정 문자열이 비었거나 형식이 아님 | 자리마다 폴백 수가 쓰인다. 값은 이 문서가 적지 않는다 | `configuration.ts:38` |
| limits:subscription-batch-is-serial | 한 건이 실패 | 나머지가 계속 진행되고 결과가 건별로 모인다 | `subscription-batch.ts:8` |
| limits:car-canary-flow-cap | 전역 자리가 만석이거나 저장소 장애 | 자동차 수집을 건너뛰고 건강 수집만 진행한다 | `treat-car-canary.service.ts:27` |
| limits:client-retry-caps | 상한만큼 실패 | 화면이 재개를 포기하고 처음 단계로 돌린다 | `QueuePage.tsx:17` |

`찾지 못했다` 와 `막지 않는다` 는 다르다. 앞엣것은 볼 수단을 확보하지 못했다는 뜻이고 뒤엣것은
보았고 막는 장치가 없음을 확인했다는 뜻이다.

## 부록

이 절의 정책 다섯은 두 부류다. 둘은 **한도를 정하는 설정의 운영값을 확보하지 못했고**, 셋은 **아직
운영에 닿지 않았다**. 본문에 두면 독자가 지금 확정된 사실로 읽는다. 확보되거나 배포가 닿으면 표기가
바뀌고 배치도 함께 본문으로 옮긴다.

### limits:certify-throttle-fails-open

인증 요청에는 같은 사람이 짧은 시간에 여러 번 부르는 것을 막는 관문이 있다. 1분 창 안에 허용할
횟수를 설정에서 읽고, 그 수가 양수가 아니면 아무것도 세지 않고 통과시킨다. 설정이 없을 때의 값은
0이므로 **설정 없이는 이 한도가 존재하지 않는 것과 같다**.

켜져 있을 때의 동작에는 확정된 사실이 셋 있다. 세는 단위는 전화번호가 우선이고 없을 때만 접속
주소다. 창은 첫 요청에서 시작해 1분 뒤 끝난다. 초과 응답은 남은 시간을 알려주지 않으므로 화면은
언제 다시 되는지 알 수 없다.

**세는 단위가 전화번호라는 데에 신뢰 경계가 걸려 있다.** 그 번호는 요청 본문이 실어 보내는 값이고
이 관문은 인증보다 앞에 선다 — 부르는 쪽이 그 값을 정한다. 접속 주소는 번호가 비어 있을 때만
쓰이므로, 매 요청 다른 번호를 넣으면 한도가 켜져 있어도 세는 자리가 매번 새로 시작한다. 따라서
"같은 사람의 반복을 막는다" 는 설명은 번호를 정직하게 쓰는 호출에만 성립하고, 신원을 돌려 가며
부르는 호출에는 성립하지 않는다. 이것을 버그로 판정하지는 않는다 — 관측이며 이 문서의 축 밖이다.

이 정책을 부록에 두는 이유는 **켜져 있는지를 확보하지 못했기** 때문이다.
허용 횟수는 설정으로 덮인다 — 코드가 정하는 것은 설정이 없을 때의 동작뿐이고, 운영 환경이 어떤 값을
넣고 있는지 이 조사는 확보하지 않았다.

한 가지가 더 확정돼 있다. 분산 저장소에 장애가 나면 이 관문은 막는 쪽이 아니라 **통과시키는 쪽으로
접힌다**. 저장소가 죽었다는 이유로 정상 이용자를 자르지 않는다는 판단이며, 그 판단의 대가는 저장소가
통째로 응답하지 않는 동안 이 한도가 사라진다는 것이다.

**다만 통과 쪽으로 접히는 것이 모든 장애 모양에서 보장되지는 않는다.** 세는 일과 만료를 거는 일이
저장소 왕복 두 번으로 나뉘어 있어, 세기는 성공하고 만료 걸기만 실패하면 만료 시각 없는 세는 값이
남는다. 그 값은 저절로 사라지지 않으므로 이후 요청이 허용 횟수를 넘기는 순간부터 그 전화번호나 접속
주소가 계속 막힌다. 이것을 버그로 판정하지는 않는다 — 관측이며, 남은 세는 값을 지워야 풀린다.

이용자에게 만드는 결과: 켜져 있고 초과하면 요청이 많다는 안내를 받는다. 언제 다시 되는지는 안내에
들어 있지 않다. 그 안내는 한 번 뜨고 끝난다 — 거절 응답이 재시도 가능으로 분류되지 않아 화면은
일반 실패로 접고 대기 상태를 푼다. 뒤에 나오는 재시도 상한은 이 자리가 아니라 인증 재개 상태를
확인하는 조회의 오류에만 걸린다.

### limits:provider-pool-budget-shape

여러 공급자에 부하를 나누는 모드에서는 공급자마다 예산이 설정 문자열로 주어진다. 한 공급자의
항목은 자리 넷으로 이뤄지고 각각 키·분당 한도·플로우 비용·단가다. 대기열은 분당 한도를 플로우
비용으로 나눠 그 공급자가 동시에 열 수 있는 자리 수를 계산한다.

이 문서가 적는 것은 **형태까지**다. 각 자리의 수는 설정으로 덮인다 — 코드가 들고 있는 것은 설정이
없을 때 쓸 문자열뿐이고, 그 설정의 운영값을 이 조사가 확보하지 않았다. 동시에 몇 사람이 분석을 돌릴
수 있는지에 대한 답은 이 문서에서 형태까지만 나오며 수는 기능 게이트 문서가 든다.

이 정책을 부록에 두는 이유가 그것이다. 계산 형태는 코드에 있고 확정이지만, 그 계산에 들어가는 수가
확보되지 않아 결과를 말할 수 없다.

이용자에게 만드는 결과: 만석이면 대기열로 들어간다. 만석의 기준이 몇인지는 설정에 달려 있다.

### limits:subscription-batch-is-serial

구독 일괄 처리는 여러 건을 묶어 동시에 돌 수 있는 구조인데, 묶음 크기가 1이라 실제로는 한 건씩
순차로 돈다. 구조는 병렬인데 설정이 순차인 상태다.

이렇게 둔 이유가 선언 자리에 적혀 있다. 같은 기업의 계약 행을 여러 건이 동시에 갱신하면 저장소
잠금이 서로를 기다리다 엉킨다. 묶음 크기를 1로 줄이면 그 충돌이 구조적으로 사라진다.

이 정책을 부록에 두는 이유는 이 변경이 **아직 운영에 닿지 않았기** 때문이다. 지금 상태를 본문에
적으면 독자가 운영에서 이미 순차로 돈다고 읽는다.

이용자에게 만드는 결과: 배포가 닿으면 대량 구독 일괄 처리가 느려지는 대신 잠금 충돌로 실패하는
건이 사라진다. 관리자만 관측한다.

### limits:car-canary-flow-cap

자동차 수집을 자체 엔진으로 시험할 때는 전역 자리 수가 3개로 묶인다. 이 수는 공급자별 예산과
별개이며 전역이다.

자리를 잡지 못하면 자동차 수집을 건너뛰고 건강 수집만 진행한다. 실패로 끝내지 않고 범위를 줄여
계속하는 형태다.

이 정책을 부록에 두는 이유는 **아직 운영에 닿지 않았기** 때문이다.

이용자에게 만드는 결과: 배포가 닿은 뒤 자동차 결과가 비어 오는 경우가 생길 수 있고, 그것은 실패가
아니라 자리를 잡지 못한 결과다.

### limits:client-retry-caps

인증이 끊겼다 이어질 때 화면은 무한히 다시 묻지 않는다. 연속 실패가 **3회**에 이르면 재개를
포기하고 처음 단계로 돌린다. 인증 화면은 재개 확인을 1.5초 주기로 보낸다.

이 상한은 화면이 스스로 지키는 값이다. 서버가 이 수를 확인하지 않으므로, 화면을 거치지 않는 호출에
대해 이 문서는 상한이 걸린다고 말하지 않는다.

이 정책을 부록에 두는 이유는 세 자리 전부 **아직 운영에 닿지 않은 변경**에서 왔기 때문이다.

이용자에게 만드는 결과: 배포가 닿으면 네트워크가 계속 불안정할 때 화면이 무한히 기다리지 않고
처음으로 돌려 다시 시도하게 한다. 지금 운영에서 어떻게 도는지는 이 문서가 적지 않는다.

## 연혁

릴리스 태그가 존재하지 않는다. 릴리스 열은 `—` 로 두고 커밋으로 추적한다.

| 릴리스 | 날짜 | SHA | 변경 요약 |
|---|---|---|---|
| — | 2026-02-02 | `fb52e11c` | 진료·대외 분석 저장이 묶음으로 나뉘어 들어갔다 |
| — | 2026-06-05 | `af16d981` | 분산 공급자 예산 문자열과 그 자리 넷이 생겼다 |
| — | 2026-06-05 | `33f63bd3` | 만석 시 대기열 화면과 그 확인 주기가 생겼다 |
| — | 2026-06-09 | `757c7b90` | 분석 관리 내려받기가 묶음 스트리밍으로 바뀌었다 |
| — | 2026-06-09 | `f7317c78` | 분석 로그 내려받기가 같은 형태로 바뀌었다 |
| — | 2026-07-01 | `31f4f8f3` | 인증 요청 분당 관문이 기본 꺼짐으로 들어왔다 |
| — | 2026-07-12 | `352c6078` | 인증 끊김 복구와 재시도 상한이 화면에 들어왔다 |
| — | 2026-07-20 | `8507677b` | 자동차 카나리의 전역 자리 상한이 생겼다 |
| — | 2026-07-27 | `164524c2` | 구독 일괄처리가 순차로 줄어 계약 행 잠금 충돌을 없앴다 |
