이 글 목차

Amplitude Export API의 타임존 처리 방식
Amplitude Export API가 이벤트 데이터 내보내기에서 타임존과 시간 경계를 처리하는 방식
Amplitude export 작업이 01:00 KST에 돌고 사용자가 전부 한국에 있다면, KST 기준 시간을 가져와야 할까요? 사용자에 관한 질문처럼 들리지만, 사실은 Amplitude 프로젝트의 설정 하나에 관한 질문이에요. 그리고 그 답은 API 문서에 없어요.
혼란의 원인
매일 01:00 KST(16:00 UTC)에 전날치 Amplitude 이벤트 데이터를 가져오는 export 작업을 떠올려 보세요. 사용자가 한국에 있으니 export도 KST 기준 시간을 물어봐야 할 것 같다는 게 자연스러운 직관이에요. 그 가정이 어느 쪽으로든 틀리면 엉뚱한 24시간 구간을 가져와서 이벤트가 빠지거나 두 번 들어와요.
Amplitude Export API 문서는 start/end 파라미터의 타임존 동작을 바로 알기 어렵게 되어 있어요. 그 틈 때문에 파이프라인이 그동안 맞는 구간을 당겨온 게 맞나 하는 의심이 충분히 생길 수 있어요.
생각보다 까다로웠던 이유
답만 보면 간단한데, 세 가지가 과정을 더디게 만들었어요.
정작 중요한 지점에서 문서가 모호해요. 문서는 내보낸 이벤트가 UTC로 타임스탬프된다는 것, 그리고 날짜 범위가 server_upload_time 기준으로 걸러진다는 것까지는 말해줘요. 하지만 start/end 자체가 프로젝트 타임존을 따르는지 항상 UTC인지는 짚어주지 않아요. 그래서 내보낸 이벤트의 server_upload_time 접미사를 확인하는 방식으로 직접 검증할 수밖에 없었어요.
프로젝트 타임존은 API에서 보이지 않아요. export 응답 어디에도 프로젝트가 어떻게 설정돼 있는지가 나오지 않아요. 확인하려면 Amplitude Console 설정을 열어야 하는데, 지금 디버깅하고 있는 화면과는 다른 곳이에요.
KST 시나리오는 재보는 게 아니라 따져봐야 해요. 프로젝트 타임존은 동작을 보려고 가볍게 바꿔볼 수 있는 설정이 아니에요. 바꾸는 순간 프로젝트의 기존 차트가 전부 다르게 읽히거든요. 그래서 아래의 “프로젝트가 KST였다면” 절은 관측한 게 아니라 종이 위에서 따져본 내용이에요.
정답은 프로젝트 타임존에 달려 있어요
질문은 이거였어요. “작업이 01:00 KST(16:00 UTC)에 돌면서 전날 데이터를 가져오는데, 사용자가 한국에 있으니 KST 기준 시간을 가져와야 하지 않나요?”
답은 아니에요, 프로젝트 타임존이 UTC라면요. Export API는 start/end 파라미터를 UTC로 해석해요. 프로젝트 타임존 설정은 Amplitude가 대시보드에 데이터를 어떻게 보여줄지를 정할 뿐, API가 대신 변환해 주지는 않아요.
UTC로 설정된 프로젝트에서 제가 확인한 내용이에요:
| 설정 | 값 | 영향 |
|---|---|---|
| Amplitude 프로젝트 타임존 | UTC | 모든 시간 경계가 UTC 기준 |
Export API start 파라미터 | UTC 시간 | start=20260126T00 = UTC 0시 (KST 0시 아님) |
| 이벤트 타임스탬프 | UTC | server_upload_time 필드에 .000Z 접미사 |
| 작업 실행 시간 | 16:00 UTC = 다음 날 01:00 KST | 전날 UTC 날짜를 처리 |
| 가져오는 시간 범위 | 0-23 UTC | UTC 기준 완전한 하루 |
Export API 요청이 동작하는 방식
요청 형식은 간단해요:
start=YYYYMMDDTHH
end=YYYYMMDDTHH 타임존은 프로젝트 타임존 설정과 상관없이 항상 UTC예요.
양쪽 경계가 모두 포함(inclusive)이라는 점은 놓치기 쉬워요. 문서는 start를 “first hour included”, end를 “last hour included”라고 설명해요. 그래서 start=20260126T00&end=20260126T01은 0시 하나가 아니라 0시와 1시, 두 시간치를 돌려줘요. 딱 한 시간만 가져오려면 start와 end를 같은 값으로 두면 돼요. start=20260126T00&end=20260126T00처럼요. 하루치가 T00~T24가 아니라 T00~T23인 것도 같은 이유예요.
이건 무심코 배포하기 쉬운 결함이에요. 다른 range API들은 대개 상한이 exclusive라서 end = hour + 1이 자연스러워 보이고, 한 시간 더 들어온 이벤트도 다운스트림에서는 평범한 데이터처럼 보이거든요. 그래서 조용한 double-fetch는 누군가 row 수를 세어보기 전까지 오래 돌 수 있어요.
타임존은 프로젝트 설정과 무관하게 그대로 적용돼요. 프로젝트가 KST로 설정돼 있다고 쳐도, UTC 0시를 가리키는 요청은 여전히 UTC 0시를 가져와요. KST와 UTC 사이의 9시간 오프셋 탓에 원하는 것과 다른 데이터를 받게 되는 거예요.
하루치 구간 전체 따라가기
전체 타임라인을 보면 논리가 딱 맞아떨어져요. 이런 스케줄로 도는 일간 작업을 생각해 볼게요:
Schedule: 0 16 * * * (16:00 UTC = 다음 날 01:00 KST)
Window: 직전 UTC 날짜, 0-23시 구체적인 예시예요:
| 시간 (UTC) | 시간 (KST) | 동작 |
|---|---|---|
| 2026-01-26 00:00 | 2026-01-26 09:00 | 이벤트 발생 시작 |
| 2026-01-26 15:00 | 2026-01-27 00:00 | 이벤트 계속 (KST 자정 지남) |
| 2026-01-27 16:00 | 2026-01-28 01:00 | 작업 실행, 2026-01-26 UTC (24시간 전체) 가져옴 |
UTC 날짜 2026-01-26에 대해 0시부터 23시까지 가져와요:
실행 날짜: 2026-01-26 (UTC)
가져오는 시간: 0-23 (UTC)
Hour 0: 2026-01-26 00:00-00:59 UTC = 2026-01-26 09:00-09:59 KST
Hour 1: 2026-01-26 01:00-01:59 UTC = 2026-01-26 10:00-10:59 KST
...
Hour 23: 2026-01-26 23:00-23:59 UTC = 2026-01-27 08:00-08:59 KST 결과는 UTC 기준 하루 전체, 24시간치 이벤트예요. KST로는 두 날짜에 걸쳐 있지만 UTC로는 완전한 영업일 하루에 해당해요. 빠지는 데이터도, 중복되는 데이터도 없어요.
직접 설정 확인하기
이걸 프로젝트에 믿고 적용하기 전에, Amplitude 프로젝트가 어떤 타임존을 쓰는지부터 확인하세요.
Amplitude Console에서
- Amplitude에 로그인
- Settings > Projects > [프로젝트] > General로 이동
- “Timezone” 설정 찾기
- “UTC”로 표시되는지 확인(Asia/Seoul 같은 로컬 타임존이 아니라)
API 응답에서
내보낸 이벤트의 server_upload_time 필드를 확인하세요:
{
"server_upload_time": "2026-01-26T00:00:00.000Z",
...
} .000Z 접미사가 UTC 타임존임을 확인해줘요.
흔한 오해들
“Export API는 프로젝트 타임존을 사용한다.” 아니에요. start/end 파라미터는 프로젝트 타임존 설정과 상관없이 항상 UTC예요.
“시간을 KST로 변환해야 한다.” 프로젝트 타임존이 UTC라면 변환이 필요 없어요. UTC 0-23시를 가져오면 하루가 채워져요.
“영업일은 KST 기준 하루다.” 프로젝트 타임존이 UTC일 때 영업일은 UTC 기준 하루예요. KST는 Amplitude 대시보드의 표시 설정일 뿐, API 계약이 아니에요.
“예전 export 구간을 다시 돌리면 늦게 들어온 이벤트를 되찾는다.” 그렇지 않아요. export 구간은 server_upload_time 기준으로 걸러내는데, 늦게 업로드된 이벤트는 그만큼 늦은 upload time을 갖게 돼서 원래 구간이 아니라 이후 구간에 잡혀요. 원래 구간을 다시 가져오는 건 빠졌거나 일부만 전달된 export 전달(delivery)을 메우는 것뿐이지, 늦게 올라온 client 업로드까지 끌어오지는 못해요. 그래서 reconciliation을 다시 봐야 해요. 재실행은 전달 복구로만 여기고, 늦은 업로드는 앞단 ingestion 경로에서 처리하는 거예요. 늦게 들어온 이벤트는 더 오래된 event_time 파티션에 떨어지기 때문에, 다운스트림 집계는 재처리하는 그날 하루만이 아니라 영향받은 모든 파티션으로 퍼져나가야 해요.
프로젝트가 KST였다면
Amplitude 프로젝트가 KST 타임존으로 설정돼 있었다면 계산이 복잡해져요:
| UTC 시간 | KST 시간 | 가져올 내용 |
|---|---|---|
| 2026-01-25 15:00-23:59 | 2026-01-26 00:00-08:59 | 전날, 15-23시 |
| 2026-01-26 00:00-14:59 | 2026-01-26 09:00-23:59 | 당일, 0-14시 |
KST 기준 영업일 하루를 맞추려면 두 개의 UTC 날짜에서 가져와야 해요. UTC로 설정된 프로젝트는 이걸 통째로 피해가요. UTC 하루가 곧 영업일 하루, 깔끔한 1:1 매핑이에요.
코드: 타임존 변환이 필요 없어요
프로젝트가 UTC라서 요청을 만드는 코드가 심심할 만큼 단순해져요:
def hour_export_url(date: str, hour: int) -> str:
"""date는 '2026-01-26' 같은 UTC 날짜, hour는 0-23 UTC."""
date_compact = date.replace("-", "") # "20260126"
stamp = f"{date_compact}T{hour:02d}" # "20260126T00"
# 양쪽 경계가 포함이라, 한 시간만 가져오려면 end가 start와 같아야 해요.
# 여기서 `hour + 1`을 쓰면 조용히 두 시간을 가져와요.
# 타임존 변환 불필요 - 파라미터가 그대로 UTC
return f"{EXPORT_API_URL}?start={stamp}&end={stamp}" 완전성 체크도 그만큼 간단해요:
def missing_hours(exported_hours: set[int]) -> set[int]:
"""UTC 기준 하루는 0-23시. 타임존 계산이 필요 없어요."""
return set(range(24)) - exported_hours 변환 함수도, 오프셋 계산도, 월 경계 예외 처리도 없어요. UTC 프로젝트 타임존 덕분에 코드가 단순하게 유지돼요.
실무에서의 한계
fetch를 언제, 얼마나 크게 돌릴지는 몇 가지 운영 제약이 좌우해요. 아래 셋은 모두 2026년 8월 기준 Export API 문서 페이지에 적힌 내용이에요.
내보낸 데이터는 한 시간이 닫히는 순간 바로 조회되지 않아요. 문서는 서버가 데이터를 받은 뒤 두 시간 안에 export할 수 있게 된다고 하고, 저녁 8시에서 9시 사이에 들어온 데이터가 11시에 나간다는 예시를 들어요. 그 지연을 감안해서 스케줄을 잡지 않으면, 너무 이른 실행은 덜 찬 시간대를 가져와요.
요청에는 크기 상한이 있어요. 문서에 적힌 한도는 4GB이고, 넘기면 400이 떨어져요. 시간 범위가 너무 길어 타임아웃이 나면 504가 떨어지고요. 해법은 시간 단위로 쪼개는 건데, 그보다 잘게 나눌 방법은 없어요. 그래서 한 시간짜리인데도 용량이 넘치면 API에서는 더 나눌 수가 없어요. 문서가 이 경우에 제시하는 답이 Amazon S3 export예요.
Rate limit은 export 문서 페이지에 나와 있지 않아요. 200, 400, 404, 504와 4GB 한도까지는 적혀 있지만 요청 빈도에 대한 언급은 없어요. API 위에 재시도가 많은 reconciliation을 얹기 전에, 여유가 있으리라 넘겨짚지 말고 Amplitude 지원팀에 한계를 확인하세요.
정리
Export API의 타임존 동작은 문서에 또렷이 적혀 있지 않지만, 어디를 봐야 하는지만 알면 답은 예측 가능해요. Amplitude 프로젝트 타임존부터 확인하세요. 그게 전부를 좌우해요. 프로젝트가 UTC라면 export 작업은 어떤 UTC 날짜든 0-23시를 변환 없이 가져오면 돼요. KST 같은 로컬 타임존이라면 날짜를 넘나드는 fetch 로직이 필요한데, 이쪽이 더 불안정하고 디버깅하기 어려워요.
이건 Amplitude의 배치 Export API에만 해당해요. Mixpanel이나 GA4 같은 다른 분석 플랫폼은 저마다 타임존을 다르게 처리해요. Amplitude라도 실시간 API나 Cohort API를 쓴다면 타임스탬프 처리가 Export API와 다를 수 있어요.
기억에 남은 교훈이 있어요. 누군가 “로컬 시간으로 변환해야 하지 않나요?”라고 물을 때, 답은 보통 “소스 시스템이 어떤 타임존으로 설정돼 있죠?”에서 출발한다는 거예요.
참고 자료
- Amplitude Export API 문서: https://amplitude.com/docs/apis/analytics/export
- Amplitude 타임존 설정: Amplitude Console > Settings > Projects > General
- Amplitude Batch Event Upload API (서버 업로드 시각 정의): https://amplitude.com/docs/apis/analytics/batch-event-upload