diff --git a/solution/backend/common/database/model/models.py b/solution/backend/common/database/model/models.py index 62e7bd4..2f03146 100644 --- a/solution/backend/common/database/model/models.py +++ b/solution/backend/common/database/model/models.py @@ -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: