데이터 수집과 거버넌스
`/api/track` 수집, raw event 저장, materialization, 이벤트 거버넌스, retention 대상을 설명합니다.
이 문서는 이벤트가 수집 API에 들어온 뒤 분석 화면에 반영되기까지의 흐름을 설명합니다.
운영 중 장애를 볼 때는 accepted, duplicates, queue 상태, materialization 상태를 함께 확인하세요.
수집 진입점
POST /api/trackOPTIONS /api/track
/api/track는 SDK와 직접 HTTP 수집을 모두 받는 단일 ingest entrypoint입니다.
브라우저 공개 환경은 reopt-write-key, 서버 환경은 reopt-client-id와 reopt-client-secret으로 인증합니다.
v2 수집 계약
현재 수집 API는 SDK가 생성하는 v2 계약을 기준으로 검증합니다. 모든 이벤트에는 다음 공통 필드가 필요합니다.
| 필드 | 설명 |
|---|---|
type | track, identify, increment, decrement 중 하나 |
eventId | UUID. 프로젝트 범위의 클라이언트 멱등성 키 |
timestamp | 클라이언트 이벤트 생성 시각, millisecond timestamp |
payload | 이벤트 타입별 payload |
alias 타입은 아직 지원하지 않으며, 요청에 포함되면 400으로 거절됩니다.
단일 이벤트 object와 비어 있지 않은 이벤트 배열을 모두 받을 수 있습니다.
요청 body는 512KB를 넘으면 거절됩니다.
처리 흐름
- 요청 body와 header를 읽습니다.
writeKey또는clientId + clientSecret으로 client를 인증합니다.- project rate limit과 organization quota를 확인합니다.
- v2 event contract를 검증합니다.
- 같은 프로젝트에
eventId가 이미 저장되어 있는지 확인합니다. - 새 raw event라면 서버가 별도의 전역 고유
rawEventId를 만들고, browser mode에서만 short-window deduplication을 수행합니다. - 두 ID를 함께 PostgreSQL
raw_events에 적재합니다. - raw event id만 담은 materialize task를 background queue에 enqueue합니다.
- worker 또는 Vercel queue consumer가 raw event를 다시 읽어 event, session, profile을 갱신합니다.
- usage counter와 realtime publish를 처리합니다.
큐 메시지에는 전체 payload를 복사하지 않고 raw event id만 넣습니다. 이 구조 덕분에 queue retry, DLQ replay, 수동 replay가 모두 같은 raw event 원본을 기준으로 동작합니다.
Deduplication
deduplication은 두 층으로 동작합니다.
| 층 | 기준 | 동작 |
|---|---|---|
| client event dedup | projectId + eventId | 이미 저장된 raw event는 새로 저장하지 않고 duplicate로 응답합니다. |
| short-window dedup | project, device, type, name, time | 같은 사용자가 같은 짧은 구간에 보낸 중복 이벤트를 raw 저장 전 제외합니다. |
short-window dedup은 브라우저의 중복 클릭을 위한 규칙이며 server mode에는 적용하지 않습니다. 이미 존재하는
eventId가 다시 들어오면 새 raw event는 만들지 않지만, 기존 내부 rawEventId로 materialize task를 다시 enqueue할 수 있습니다.
따라서 queue 장애 후 같은 요청을 재시도해도 원본 raw event를 기준으로 재처리할 수 있습니다.
응답 읽는 법
성공 응답은 다음 정보를 포함합니다.
{
"status": "ok",
"requestId": "req_...",
"accepted": 1,
"duplicates": 0,
"queued": true
}| 필드 | 의미 |
|---|---|
accepted | 이번 요청에서 새 raw event로 저장된 이벤트 수 |
duplicates | deduplication으로 새 raw event 저장이 생략된 이벤트 수 |
queued | materialize 또는 usage background task가 생성되었는지 여부 |
requestId | 로그와 장애 추적에 사용하는 요청 ID |
accepted가 0이어도 duplicates가 1 이상이면 요청 자체는 정상적으로 처리된 것입니다.
반대로 queued가 false이면 materialization 대상이 없는 요청이므로 분석 테이블 업데이트를 기대하면 안 됩니다.
background queue enqueue에 실패하면 수집 API는 원본 raw event를 저장한 뒤 202로 응답할 수 있습니다.
이때 응답과 header에 replay: "unmaterialized-raw-events"가 포함되며, /api/cron/process-ingest/replay-unmaterialized 또는 replay CLI로 다시 enqueue합니다.
직접 HTTP 호출 예시
SDK를 통하지 않고 호출할 때도 eventId와 millisecond timestamp를 직접 넣어야 합니다.
curl -X POST "$REOPT_API_URL/api/track" \
-H "Content-Type: application/json" \
-H "reopt-write-key: $NEXT_PUBLIC_REOPT_WRITE_KEY" \
-d '{
"type": "track",
"eventId": "00000000-0000-4000-8000-000000000001",
"timestamp": 1763769600000,
"payload": {
"name": "page_view",
"properties": { "path": "/home" }
}
}'Raw Event와 Materialization
수집 단계는 원본 보존과 분석 반영을 분리합니다.
raw_events: SDK가 보낸 payload, client timestamp, 수신 시각, device/profile context를 저장합니다.raw_event_materializations: raw event가 어떤 분석 테이블로 반영되었는지 추적합니다.events: 분석 쿼리에서 읽는 event log입니다.sessions: device와 session 기준 집계입니다.profiles: identify와 profile property 업데이트 결과입니다.
materialize 실패는 raw event 손실을 의미하지 않습니다. raw event가 남아 있으면 replay endpoint나 worker로 다시 materialize할 수 있습니다.
브라우저 SDK 운영 특성
브라우저 SDK는 다음 기본값으로 동작합니다.
- 이벤트는 메모리 큐에 들어간 뒤
flushInterval마다 전송됩니다. - 큐 크기가
batchSize에 도달하면 즉시 flush를 시도합니다. - 페이지가 숨겨지거나 언로드되면
keepaliveflush를 시도합니다. localStorage가 가능하면 device ID, consent, offline queue를 저장합니다.localStorage가 막혀도 수집은 계속 시도하지만 offline queue와 device ID 영속성은 약해집니다.
브라우저에서 SDK를 여러 번 init()하면 이전 singleton은 close()되어 listener와 pending flush가 정리됩니다.
React Provider도 unmount 시 close()를 호출합니다.
서버 SDK 운영 특성
서버 SDK는 공개 키 대신 clientId + clientSecret을 사용합니다.
- 일반 서버:
track()으로 큐에 넣고flush()또는close()로 남은 이벤트를 전송합니다. - 서버리스/route handler:
trackAndFlush,identifyAndFlush,incrementAndFlush,decrementAndFlush를 사용합니다. flush()와close()는 큐가 빌 때까지 drain을 시도합니다.- 전송 실패 시 이벤트는 SDK 큐 앞쪽으로 되돌아가며, 결과의
pending으로 남은 수를 확인할 수 있습니다.
서버리스에서 track()만 호출하고 요청을 종료하면 런타임이 타이머를 끝까지 보장하지 않을 수 있습니다.
따라서 사용자 요청, webhook, cron handler에서는 *AndFlush 계열 또는 await flush()를 사용해야 합니다.
거버넌스 레이어
수집이 끝나면 운영자는 이벤트 카탈로그에서 다음을 통제합니다.
- 전환 정의
- 태그
- 상태
- 카테고리
- naming validation
- metadata history
- property explorer
- code snippet
데이터 거버넌스는 파이프라인 내부 규칙만으로 끝나지 않습니다. 제품 화면에서 이벤트의 의미를 관리해야 분석 결과가 안정적으로 해석됩니다. 수집 파이프라인은 원본과 분석 반영을 안정적으로 보장하고, 카탈로그는 팀이 그 이벤트를 어떤 의미로 해석할지 관리합니다.
Retention 대상
현재 retention cleanup이 다루는 주요 테이블:
raw_eventsraw_event_materializationseventsprofilessessions
raw event 보존 기간은 replay 가능 기간을 의미합니다. 분석 이벤트 보존 기간과 raw event 보존 기간을 다르게 운영할 수 있으므로, 장애 복구 요구사항을 기준으로 raw event retention을 정합니다.
운영 시 주의점
- 수집이 된다고 해서 바로 분석 가능한 상태는 아닙니다.
accepted,duplicates, queue 상태, materialization 상태를 같이 확인해야 합니다.- 서버리스에서는
trackAndFlush계열을 사용해 요청 생명주기 안에서 전송을 끝냅니다. - 카탈로그 정의와 retention policy까지 포함해야 운영 가능한 데이터 플랫폼이 됩니다.
- 장애 대응 문맥에서는
requestId,eventId,rawEventId를 함께 기록합니다.
다음 단계
- SDK를 처음 붙이려면 추적 설정
- 이벤트 의미를 정리하려면 이벤트 카탈로그
- 서비스 경계를 보려면 서비스 Plane과 엔드포인트