본문 바로가기
C.W.K.
Stream
Lesson 01 of 05 · published

그 날짜, 누구 달력이야?

~12 min · timezone, bug, war-story, provenance

Level 0원석
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"스냅샷에 오늘 날짜를 찍지 마."

멀쩡해 보이는 그 한 줄

데이터를 모으는 시스템이면 어디든 사실상 data_date = date.today() 라고 적힌 줄이 하나 있어. 리뷰를 통과해. 타입 검사를 통과해. 딱 봐도 맞아 보여. 오늘 값을 저장하니까 오늘 날짜를 저장하는 거지.

기계 달력이랑 대상 달력이 다른 물건일 때만 틀려. 그리고 그렇게 말해놓고 보면, 놀라운 건 둘이 같을 거라고 기대한다는 쪽이야.

구체적인 충돌

일일 루프는 07:11 Asia/Seoul 에 발화해. 그 시각은 좋은 이유로 골랐어. 여름이든 겨울이든 미국 마감 뒤로 안전하게 떨어지고, 한국 개장 전에 여유 있게 들어가니까. 그리고 그건 전날 22:11 UTC 이기도 해.

그래서 서울 8 월 6 일 아침에 루프가 미국 종가를 — 뉴욕에서는 8 월 5 일 장인 — 가져와서 로컬 날짜를 찍었어. 8 월 6 일. 저장소의 모든 미국 게이지 행이 아직 안 일어난 장을 설명한다고 주장하게 됐지.

아무것도 안 죽었어. 테스트도 안 떨어졌고. 값도 맞고, 출처 문자열도 맞고, 가져온 시각도 맞아. 정확히 필드 하나가 틀렸고, 그게 읽기 모델 전체가 정렬 기준으로 쓰는 필드였어.

결판낸 측정
수정은 말로 관철된 게 아니라 측정으로 관철됐어. 2026-08-06 의 한 순간에, 미국 ETF 프록시는 장 날짜가 2026-08-05 로 나왔고 도쿄 상장 ETF 는 2026-08-06 으로 나왔어. 종목 둘, 한 순간, 서로 다른 정답 둘. 이 관측 하나가 여러 시장 패스에 날짜 하나를 찍는 설계를 전부 죽여. 이 불일치가 예외 사례가 아니라 거래소가 둘 이상인 세상의 정상 상태라는 걸 보여주니까.

이 부류 버그가 그렇게 조용한 이유

성질 셋이 겹쳐. 네 시스템에서도 알아볼 값어치가 있어.

첫째, 틀린 값이 잘 생겼어. 유효한 날짜고, 범위 안이고, 형식도 맞아. 네가 쓸 생각을 할 만한 검증은 전부 통과해.

둘째, 행마다 틀린 양이 다르고, 절대 균일하지 않아. 서울 날짜가 한국이랑 일본 게이지한테는 맞았겠거니 싶지. 아니야. 그 게이지들은 도쿄 상장이랑 미국 상장 ETF 프록시로 지어졌고, 루프가 도는 시각엔 두 시장 다 안 열렸으니까 걔들 최신 완료 장도 전날이야. 살아 있는 엔진에서 재보면, 22:11 UTC — 서울 기준 다음 날 아침 07:11 — 에 끝난 패스가 미국이랑 한국이랑 일본 행을 전부 같은 이전 장 소속으로 썼어. 로컬 날짜는 그중 아무한테도 안 맞았어. 어디서나 틀리는데 틀린 양이 행마다 다른 결함은 결함처럼 안 보여. 데이터 소스가 이상하게 구는 것처럼 보이지.

셋째, 관찰자가 결함을 공유해. 대시보드를 보는 개발자도 서울에 있고, 오늘이 8 월 6 일이라고 생각하고 있고, 8 월 6 일이라고 붙은 숫자가 딱 맞아 보여. 찾을 줄 이미 알고 있지 않으면 봐서는 못 잡아.

타임스탬프는 서로 다른 두 가지를 가리키고, 뭘 저장하는지 정해야 해. 내 프로세스가 움직인 순간이 있고, 대상이 속한 순간이 있어. 주인이 따로 있는 다른 질문이고, 시간대나 스케줄이나 배치 경계를 건너는 시스템이면 언젠가 둘이 어긋나. 둘 다 저장하고, 하나가 다른 하나 행세를 하게 절대 두지 마.

Code

틀린 판이랑 맞는 판, 단일 시장 세상과 다중 시장 세상에서·python
# WRONG -- and it looks completely reasonable:
row = {
    "symbol": symbol,
    "value": quote["close"],
    "data_date": datetime.date.today().isoformat(),  # WHOSE today?
    "source": f"provider:{symbol}",
}

# At 07:11 Asia/Seoul on 2026-08-06 this labels the 2026-08-05
# New York close as 2026-08-06. Valid date. Right format. Wrong day.


# RIGHT -- the target shape, not a quote of the shipped caller.
# (The engine did the first half and not the second until
#  2026-08-31; the next lesson shows where the disclosure went
#  missing and what closing it looked like.)
session = market.listing_close_date(info)   # from the provider
row = {
    "symbol": symbol,
    "value": quote["close"],
    "data_date": session or utc_today(),    # and SAY SO when guessed
    "source": f"provider:{symbol}",
    "fetched_at": db.utcnow(),              # our clock, kept separate
}

# Two fields, two questions:
#   data_date  -> which session does this describe?   (theirs)
#   fetched_at -> when did we ask?                    (ours)

External links

Exercise

네 코드베이스에서 today() 나 now() 가 저장 레코드의 업무 의미 날짜 필드에 쓰이는 자리를 grep 해봐. 감사용 타임스탬프 말고, 이 레코드가 어느 날에 관한 건지 말하는 그 필드. 걸리는 자리마다 물어. 이 코드가 대상이랑 달력 날짜가 다른 기계에서 돌 수 있나? 예약 작업이든, 자정 전에 시작해서 자정 후에 끝나는 배치든, 다른 지역의 큐 소비자든, 하나면 충분해.
Hint
시스템이 아니라 대상을 설명하는 이름의 날짜 필드를 찾아. data_date, business_date, session_date, as_of, effective_date. 그중 하나라도 로컬 시계에서 대입되면 이 버그가 잠복해 있는 거고, 작업이 옮겨가거나 스케줄이 밀리는 첫날 드러나.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 피파— warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.