리옵트 데이터 문서
플랫폼과 개발

데이터 수집과 거버넌스

`/api/track` 수집, raw event 저장, materialization, 이벤트 거버넌스, retention 대상을 설명합니다.

이 문서는 이벤트가 수집 API에 들어온 뒤 분석 화면에 반영되기까지의 흐름을 설명합니다. 운영 중 장애를 볼 때는 accepted, duplicates, queue 상태, materialization 상태를 함께 확인하세요.

수집 진입점

  • POST /api/track
  • OPTIONS /api/track

/api/track는 SDK와 직접 HTTP 수집을 모두 받는 단일 ingest entrypoint입니다. 브라우저 공개 환경은 reopt-write-key, 서버 환경은 reopt-client-idreopt-client-secret으로 인증합니다.

v2 수집 계약

현재 수집 API는 SDK가 생성하는 v2 계약을 기준으로 검증합니다. 모든 이벤트에는 다음 공통 필드가 필요합니다.

필드설명
typetrack, identify, increment, decrement 중 하나
eventIdUUID. 프로젝트 범위의 클라이언트 멱등성 키
timestamp클라이언트 이벤트 생성 시각, millisecond timestamp
payload이벤트 타입별 payload

alias 타입은 아직 지원하지 않으며, 요청에 포함되면 400으로 거절됩니다. 단일 이벤트 object와 비어 있지 않은 이벤트 배열을 모두 받을 수 있습니다. 요청 body는 512KB를 넘으면 거절됩니다.

처리 흐름

  1. 요청 body와 header를 읽습니다.
  2. writeKey 또는 clientId + clientSecret으로 client를 인증합니다.
  3. project rate limit과 organization quota를 확인합니다.
  4. v2 event contract를 검증합니다.
  5. 같은 프로젝트에 eventId가 이미 저장되어 있는지 확인합니다.
  6. 새 raw event라면 서버가 별도의 전역 고유 rawEventId를 만들고, browser mode에서만 short-window deduplication을 수행합니다.
  7. 두 ID를 함께 PostgreSQL raw_events에 적재합니다.
  8. raw event id만 담은 materialize task를 background queue에 enqueue합니다.
  9. worker 또는 Vercel queue consumer가 raw event를 다시 읽어 event, session, profile을 갱신합니다.
  10. usage counter와 realtime publish를 처리합니다.

큐 메시지에는 전체 payload를 복사하지 않고 raw event id만 넣습니다. 이 구조 덕분에 queue retry, DLQ replay, 수동 replay가 모두 같은 raw event 원본을 기준으로 동작합니다.

Deduplication

deduplication은 두 층으로 동작합니다.

기준동작
client event dedupprojectId + eventId이미 저장된 raw event는 새로 저장하지 않고 duplicate로 응답합니다.
short-window dedupproject, 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로 저장된 이벤트 수
duplicatesdeduplication으로 새 raw event 저장이 생략된 이벤트 수
queuedmaterialize 또는 usage background task가 생성되었는지 여부
requestId로그와 장애 추적에 사용하는 요청 ID

accepted가 0이어도 duplicates가 1 이상이면 요청 자체는 정상적으로 처리된 것입니다. 반대로 queuedfalse이면 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를 시도합니다.
  • 페이지가 숨겨지거나 언로드되면 keepalive flush를 시도합니다.
  • 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_events
  • raw_event_materializations
  • events
  • profiles
  • sessions

raw event 보존 기간은 replay 가능 기간을 의미합니다. 분석 이벤트 보존 기간과 raw event 보존 기간을 다르게 운영할 수 있으므로, 장애 복구 요구사항을 기준으로 raw event retention을 정합니다.

운영 시 주의점

  • 수집이 된다고 해서 바로 분석 가능한 상태는 아닙니다.
  • accepted, duplicates, queue 상태, materialization 상태를 같이 확인해야 합니다.
  • 서버리스에서는 trackAndFlush 계열을 사용해 요청 생명주기 안에서 전송을 끝냅니다.
  • 카탈로그 정의와 retention policy까지 포함해야 운영 가능한 데이터 플랫폼이 됩니다.
  • 장애 대응 문맥에서는 requestId, eventId, rawEventId를 함께 기록합니다.

다음 단계