전송과 재시도 v0.5.0
SDK가 이벤트를 서버로 전송하는 HTTP 배치 구조, 엔벨로프 포맷, 재시도 정책, 버퍼 관리를 상세히 설명합니다.
HTTP 배치 구조
SDK는 이벤트를 개별로 전송하지 않고, **배치(batch)**로 묶어 한 번에 전송합니다.
요청 형식
POST {endpoint}/v1/events
Content-Type: application/json
Authorization: Bearer dk_live_...
User-Agent: @dicolytics/discord.js/0.5.0응답 형식
// 202 Accepted
{
"accepted": 48,
"dropped": 2
}accepted는 서버가 수락한 이벤트 수, dropped는 검증 실패로 버린 이벤트 수입니다.
엔벨로프 포맷
하나의 HTTP 요청에 담기는 JSON 구조입니다.
{
"batchId": "01912345-6789-7abc-8def-0123456789ab",
"sdk": {
"name": "@dicolytics/discord.js",
"version": "0.5.0"
},
"sentAt": "2025-01-15T12:00:00.000Z",
"events": [
{
"id": "01912345-6789-7def-8abc-0123456789cd",
"type": "interaction_slash",
"ts": "2025-01-15T11:59:58.123Z",
"guildId": "123456789012345678",
"channelId": "234567890123456789",
"userId": "987654321098765432",
"data": {
"commandName": "play rock",
"interactionKind": "slash",
"latencyMs": 42,
"success": true,
"shardId": 0
}
}
]
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
batchId | string | 전송 시도마다 새로 생성되는 UUIDv7 |
sdk.name | string | SDK 패키지 이름 (@dicolytics/discord.js 또는 dicolytics-discord.py) |
sdk.version | string | SDK 버전 |
sentAt | string | 전송 시각 (UTC ISO-8601) |
events | array | 이벤트 배열 |
이벤트 필드
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
id | string | ✅ | 이벤트 고유 ID (UUIDv7, RFC 9562) |
type | string | ✅ | 이벤트 타입 |
ts | string | ✅ | 이벤트 발생 시각 (UTC ISO-8601) |
guildId | string | 서버 ID | |
channelId | string | 채널 ID | |
userId | string | 사용자 ID | |
data | object | ✅ | 이벤트별 데이터 (타입에 따라 구조 다름) |
이벤트 ID와 중복 제거
각 이벤트의 id는 UUIDv7 형식으로 생성됩니다. 재시도 시에도 동일한 id를 재사용하므로, 서버 측에서 중복 이벤트를 자동으로 제거할 수 있습니다.
배치 제한
| 항목 | 제한 |
|---|---|
| 배치당 최대 이벤트 수 | 500개 |
| 배치당 최대 크기 | 1 MiB (1,048,576 바이트) |
초과 시 동작
- 큐에 500개 이상의 이벤트가 있으면 500개씩 분할하여 순차 전송합니다.
- 직렬화된 배치가 1 MiB를 초과하면 반으로 분할하여 재시도합니다. 재귀적으로 분할합니다.
- 단일 이벤트가 1 MiB를 초과하면 해당 이벤트는 폐기됩니다.
플러시 정책
| 조건 | 동작 |
|---|---|
| 큐에 50개 이상 대기 | 즉시 플러시 |
| 마지막 플러시 후 5초 경과 | 타이머 기반 플러시 |
flush() 직접 호출 | 즉시 플러시 |
프로세스 종료 (SIGINT/SIGTERM) | 최대 3초간 플러시 |
참고
플러시 중에 새로운 플러시가 요청되면 현재 전송이 완료될 때까지 대기합니다. 동시에 여러 HTTP 요청이 발생하지 않으며, 이벤트 순서가 보장됩니다.
재시도 정책
HTTP 응답 상태 코드에 따라 다르게 동작합니다.
| 상태 코드 | 동작 | 설명 |
|---|---|---|
| 2xx | 성공 | 이벤트 수락 완료 |
| 301, 302 | 전송 영구 비활성화 | 리다이렉트 감지. endpoint를 최종 URL로 변경하라는 경고 출력 |
| 400 | 배치 폐기 | 잘못된 요청. 재시도해도 동일하므로 버림 |
| 401 | 전송 영구 비활성화 | API 키 무효. 큐 전체 비우고 경고 출력 |
| 403 | 재시도 | CDN/WAF의 일시적 차단일 수 있으므로 백오프 재시도 |
| 429 | Quiet Period | 레이트 리밋. Retry-After 헤더 준수 |
| 5xx | 재시도 | 서버 오류. 지수 백오프 재시도 |
| 네트워크 오류 | 재시도 | 연결 실패, 타임아웃 등 |
지수 백오프
재시도 가능한 오류(403, 5xx, 네트워크 오류)에는 full-jitter 지수 백오프를 적용합니다.
delay = random() * min(BACKOFF_MAX, BACKOFF_BASE * 2^(attempt - 1))| 상수 | 값 |
|---|---|
BACKOFF_BASE | 1,000ms (1초) |
BACKOFF_MAX | 30,000ms (30초) |
| 최대 시도 횟수 | 5회 |
예시 (최대 지연):
- 1차 재시도: 최대 1초
- 2차 재시도: 최대 2초
- 3차 재시도: 최대 4초
- 4차 재시도: 최대 8초
- 5차 재시도: 최대 16초
5회 시도 후에도 실패하면 해당 배치는 폐기됩니다.
429 Quiet Period
서버가 429 응답을 반환하면 SDK는 Quiet Period에 진입합니다.
| 항목 | 값 |
|---|---|
| 기본 Quiet Period | 300초 (5분) |
Retry-After 헤더 있을 때 | 헤더 값 사용 |
| 최대 Quiet Period | 86,400초 (24시간) |
참고
Quiet Period 동안 일반 이벤트(상호작용, 작업, 커스텀 등)는 큐에 쌓이지만 전송되지 않습니다. 단, heartbeat과 guild_snapshot은 예외적으로 계속 전송됩니다. 이는 봇의 온라인 상태와 서버 정보를 대시보드에서 계속 확인할 수 있도록 하기 위함입니다.
버퍼 관리
| 항목 | 값 |
|---|---|
| 최대 버퍼 크기 | 5,000건 |
| 초과 시 동작 | 가장 오래된 이벤트부터 폐기 |
| 정상 복구 시 | 자동으로 전송 재개 |
버퍼 초과는 언제 발생하나요?
정상적인 네트워크 환경에서는 5,000건에 도달하기 어렵습니다. 네트워크 장애, 서버 다운, 429 Quiet Period가 길어질 때 발생할 수 있습니다. 버퍼가 가득 차면 새로운 이벤트를 위해 오래된 이벤트를 폐기합니다.
전송 비활성화
다음 상황에서 SDK의 전송 기능이 영구적으로 비활성화됩니다. 봇을 재시작해야 전송이 재개됩니다.
| 상황 | 원인 |
|---|---|
| 401 응답 | API 키가 무효하거나 만료됨 |
| 301/302 응답 | endpoint가 리다이렉트되는 URL임 |
비활성화 시 큐가 비워지고 경고 로그가 출력됩니다. 이후 track() 등의 호출은 정상 동작하지만 이벤트가 전송되지는 않습니다.
종료 시 동작
프로세스 종료 시 SDK는 마지막 이벤트를 보존하기 위해 다음과 같이 동작합니다.
| 시그널 | 동작 |
|---|---|
SIGINT | 최대 3초간 플러시 → 다른 핸들러 없으면 시그널 재전달 |
SIGTERM | 최대 3초간 플러시 → 다른 핸들러 없으면 시그널 재전달 |
beforeExit | 최대 3초간 플러시 |
shutdown() 직접 호출 | 리스너 해제 → 최대 3초간 플러시 |
타이머 unref
SDK의 모든 내부 타이머는 unref()로 설정됩니다. SDK 타이머만으로는 Node.js 프로세스가 유지되지 않으므로, 다른 활성 작업이 없으면 프로세스가 정상 종료됩니다.
상수 요약
| 상수 | 값 | 설명 |
|---|---|---|
FLUSH_AT_EVENTS | 50 | 이 수만큼 큐에 쌓이면 즉시 플러시 |
DEFAULT_FLUSH_INTERVAL_MS | 5,000 | 타이머 기반 플러시 주기 |
MAX_BUFFERED_EVENTS | 5,000 | 큐 최대 크기 |
MAX_BATCH_EVENTS | 500 | 배치당 최대 이벤트 수 |
MAX_BATCH_BYTES | 1,048,576 | 배치당 최대 크기 (1 MiB) |
MAX_SEND_ATTEMPTS | 5 | 최대 재시도 횟수 |
BACKOFF_BASE_MS | 1,000 | 백오프 기본값 |
BACKOFF_MAX_MS | 30,000 | 백오프 최대값 |
DEFAULT_RETRY_AFTER_SECONDS | 300 | 429 기본 Quiet Period |
DEFAULT_REQUEST_TIMEOUT_MS | 10,000 | HTTP 요청 타임아웃 |
SHUTDOWN_FLUSH_TIMEOUT_MS | 3,000 | 종료 시 플러시 타임아웃 |
관련 문서
- 수집 체계 -- 전체 수집 흐름과 버퍼 구조
- SDK API 레퍼런스 --
flush(),shutdown()메서드 - 설정 옵션 --
flushIntervalMs,requestTimeoutMs등 관련 옵션