제한과 정책
Deplite는 호출 폭주를 막기 위해 여러 층의 제한을 둬요.
| 층 | 위치 | 기준 |
|---|---|---|
| 워크플로우 단위 rate limit | 서버 | 분·시간·일당 호출 횟수 |
| 토큰 단위 rate limit | 서버 | 분·시간·일당 호출 횟수 |
| 토큰 읽기(조회) rate limit | 서버 | 분당 호출 횟수 |
워크플로우 YAML에는 따로 limit 설정 필드가 없어요. 모든 제한은 대시보드에서 워크플로우 또는 API 토큰에 설정해요.
워크플로우 단위 rate limit
대시보드의 워크플로우 상세에서 분·시간·일당 호출 횟수를 설정해요.
| 필드 | 의미 |
|---|---|
rateLimitPerMinute | 1분 안에 허용할 호출 수 |
rateLimitPerHour | 1시간 안에 허용할 호출 수 |
rateLimitPerDay | 1일 안에 허용할 호출 수 |
세 값은 독립적으로 평가돼요.
한 윈도우라도 초과하면 trigger 호출은 즉시 HTTP 429로 거절돼요.
{
"statusCode": 429,
"code": "rate_limited",
"message": "Rate limit exceeded for workflow \"deploy-prod\" (minute: 6/5)",
"window": "minute"
}거절 이벤트는 rate_limit_blocks 테이블에 기록되고, audit log에 rate_limit.block 액션으로 남아요.
토큰 단위 rate limit
API 토큰 발급 시 함께 설정할 수 있어요.
동일 토큰의 호출이 분·시간·일당 한도를 넘으면 워크플로우 단위와 마찬가지로 HTTP 429로 거절돼요.
{
"name": "CI/CD",
"scopes": [{ "type": "trigger", "triggerIds": ["..."] }],
"rateLimitPerMinute": 10,
"rateLimitPerHour": 100,
"rateLimitPerDay": 1000
}워크플로우 단위 제한과 토큰 단위 제한은 서로 독립이라, 둘 다 통과해야 호출이 디스패치돼요.
토큰 읽기(조회) rate limit
토큰 접근 범위 조회 엔드포인트(GET /v1/token, /v1/agents, /v1/devices, /v1/workflows)에만 적용되는 별도 한도예요.
읽기 호출은 Job을 만들지 않아서, Job 카운트 기반인 토큰 단위 rate limit과는 따로 계산해요.
기본값은 토큰당 분당 60회이고, 서버 환경변수 API_TOKEN_READ_RATE_LIMIT_PER_MINUTE로 조정해요.
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded for api token reads (minute: 61/60)",
"scope": "token_read",
"window": "minute",
"limit": 60,
"observed": 61
}차단되면 audit log에 rate_limit.block(scope: 'token_read')이 남아요.
쓰기 증폭을 막기 위해 토큰당 윈도우 1회만 기록해요.
셀프 호스팅 운영 주의: 이 카운터는 인스턴스 메모리에 있어서, 다중 인스턴스 배포에서는 인스턴스당 한도로 동작해요.
전역 한도가 아니라 열거(enumeration) 폭주를 늦추는 완화책으로 봐주세요.
force 우회
서버 측 워크플로우 rate limit을 명시적으로 우회해야 할 때(긴급 핫픽스 등)는 trigger 호출 시 force: true로 보내요.
curl -X POST .../triggers/<id>/run \
-d '{"ref": "main", "force": true, "forceReason": "hotfix CVE-2026-X"}'| 조건 | 동작 |
|---|---|
| 호출자 role | 조직의 owner만 허용. member 호출 시 거절 |
forceReason | 비어 있으면 거절 (forceReason is required when force=true) |
| Job 기록 | forced: true, forceReason이 함께 저장 |
| Audit log | trigger.force_run 액션이 별도로 남음 |
토큰 단위 rate limit은 force로도 우회되지 않아요(토큰 자체를 회전하거나 한도를 늘려야 함).
중복 제거 (Idempotency)
Webhook 호출 시 Idempotency-Key 헤더를 보내면, 같은 키의 두 번째 호출은 첫 Job을 그대로 돌려줘요.
curl -X POST .../triggers/<id>/run \
-H "Idempotency-Key: deploy-2026-05-31-001" \
-d '{"ref": "main"}'내부적으로 idempotency 키는 ${triggerId}:${workflowName}:${clientKey} 형태로 저장돼요.
같은 클라이언트 키라도 트리거+워크플로우 단위로 별도 추적되므로, 다른 트리거에 같은 키를 보내도 충돌하지 않아요.
Agent 큐 동작
Agent는 동시에 처리할 수 있는 Job을 내부 채널(기본 크기 4)로 받아 순차 실행해요.
큐가 차 있는 상태에서 새 deploy 이벤트가 도착하면, 현재 구현에서는 Agent가 로컬 로그에만 기록하고 silent drop해요.
서버에는 rejected job으로 보고되지 않으니, 대시보드에서 누락이 보이면 Agent 로그(job queue full; rejecting)를 확인해주세요.
많은 동시 실행이 필요하면 Agent를 여러 대로 늘려주세요.
실전 시나리오
A — 머지 폭주에서 워크플로우 보호
여러 PR이 동시에 머지되면서 같은 deploy 트리거가 폭주할 때, 대시보드 워크플로우 설정에서 rateLimitPerMinute = 1로 잡아 두면 1분에 한 번만 허용돼요.
두 번째 호출은 HTTP 429로 거절돼요.
B — 헬스체크 트리거 안정화
Cron 트리거를 1분 간격으로 걸어두고, 워크플로우에 rateLimitPerMinute = 1을 함께 설정하면 외부에서 webhook으로 또 같은 워크플로우를 호출해도 1분 1회 한도를 지켜줘요.
C — 긴급 핫픽스로 우회
평소엔 rateLimitPerHour = 4이지만 보안 패치는 즉시 배포해야 할 때, 대시보드의 트리거 상세 → Run 버튼에서 Force 체크박스를 켜고 ref와 forceReason을 입력해요.
체크박스는 조직 owner 권한 사용자에게만 노출돼요.
{
"ref": "hotfix/cve-2026-x",
"force": true,
"forceReason": "CVE-2026-X 패치 — Sec팀 승인"
}Job 기록은 forced: true + forceReason 으로 남고, audit log에는 trigger.force_run 액션이 추가로 남아요.