[fix] solution/backend: ORM 의 TIMESTAMPTZ 기본값을 init.sql 과 같은 now() 로 — 테스트 잡이 미래에 박히던 것

기본값이 `(now() AT TIME ZONE 'utc')` 였다. timestamptz 에 이걸 쓰면 값이 시간대 없는
벽시계로 떨어졌다가 세션 시간대로 다시 해석돼 **서버 시간대만큼 미래로 밀린다.**
실측(2026-09-10): 잡의 run_after 가 7시간 뒤로 박혀 claim(`run_after <= now()`)에 영영
안 걸렸다. 워커가 잡을 하나도 못 집어 COPY 관련 테스트가 "잡이 PENDING 인 채" 무더기로
실패했고, 원인이 코드가 아니라 스키마라 읽히지 않았다.

운영은 멀쩡했다 — 운영 DB 는 init.sql(`DEFAULT now()`)로 만들어지고, 이 기본값은 ORM 이
스키마를 만들 때만, 즉 테스트 DB(conftest)에서만 쓰인다. 스키마는 init.sql 이 단일 출처다.

- models.py: `_utc_now_sql()` → `now()`. 갈라지면 어떻게 되는지 주석에 실측과 함께 남긴다

검증: 이 한 줄로 fact·copy·faq 35건이 전부 통과로 바뀐다(그 전 4건 실패)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hbyang 2026-09-10 17:03:21 +09:00
parent 25f7e5c0c8
commit fc67962efc

View File

@ -26,7 +26,21 @@ MAIN_BASE = declarative_base()
# 공통 mixin
# DB 계약(_DBTypeMixin)과 ERD 공통 컬럼(MainTableMixin)을 분리해 둔다.
def _utc_now_sql():
return text("(now() AT TIME ZONE 'utc')")
"""TIMESTAMPTZ 컬럼의 기본값. **init.sql 과 같은 `now()` 여야 한다.**
예전 값은 `(now() AT TIME ZONE 'utc')` 였는데, 이건 timestamptz 쓰면 틀린다.
AT TIME ZONE 'utc' timestamptz **시간대 없는 벽시계 **으로 떨어뜨리고,
값이 timestamptz 컬럼에 들어가며 세션 시간대로 다시 해석된다 서버 시간대만큼
미래(또는 과거) 밀린 시각이 저장된다.
운영에서는 드러났다. 운영 DB init.sql(`DEFAULT now()`) 만들어지고, 기본값은
**ORM 스키마를 만들 때만** 쓰이기 때문이다 테스트 DB 뿐이다(conftest).
실측(2026-09-10): 테스트에서 잡의 run_after 7시간 뒤로 박혀 claim 조건
(`run_after <= now()`) 영영 걸렸다. 워커가 잡을 하나도 집어 COPY 관련 테스트가
"잡이 PENDING 인 채" 무더기 실패했고, 원인이 코드가 아니라 스키마라 읽히지 않았다.
스키마는 init.sql 단일 출처다. ORM 기본값이 그것과 다르면 이런 식으로 갈라진다.
"""
return text("now()")
class _DBTypeMixin: