PostgreSQL `now()`와 `clock_timestamp()`의 차이점을 아십니까?
PostgreSQL에서 `now()` 함수는 트랜잭션 시작 시간을 반환하며, `clock_timestamp()`는 실제 시간을 반환함
`now()`를 사용하여 시간 경과를 확인하는 경우, 단일 트랜잭션 내에서 시간 변화가 감지되지 않는 문제 발생
분산 락킹(Distributed Locking) 시스템에서, `now()` 오용으로 인해 장애 복구(Failover)가 제대로 이루어지지 않는 버그 발생
테스트 코드(Test Code)의 부재로 인해 버그가 장기간 발견되지 않았으며, 실제 호출 방식(Real Caller)을 반영한 테스트의 중요성 강조
`now()`와 `clock_timestamp()`의 근본적인 차이
PostgreSQL에서 `now()` 함수는 트랜잭션 시작 시점을 기준으로 고정된 값을 반환하는 반면, `clock_timestamp()`는 매 호출 시점의 실제 시간을 반환한다. 기술적으로 보면, `now()`는 트랜잭션 타임스탬프(Transaction Timestamp)를, `clock_timestamp()`는 월 클락(Wall Clock)을 참조한다. 이러한 차이점은 동일 트랜잭션 내에서 시간 비교 로직의 동작 방식에 영향을 미치며, 특히 분산 락킹(Distributed Locking)과 같은 시스템에서 치명적인 버그를 유발할 수 있다.
분산 락킹 시스템에서의 버그 발생 원인
저자는 분산 락킹 시스템에서 `now()` 함수를 사용하여 마지막 업데이트 시간(last_updated)을 기록하고, 일정 시간 이상 갱신되지 않은 경우 락을 해제하는 로직을 구현했다. 하지만, `now()`의 특성상 동일 트랜잭션 내에서는 시간 변화가 감지되지 않아, 장애 복구(Failover) 시 락 획득에 실패하는 문제가 발생했다. 특히, 재시도 정책(Retry Policy)이 적용된 경우, 모든 재시도가 동일한 트랜잭션 내에서 실행되어 문제가 더욱 악화되었다.
버그 해결 및 테스트 전략의 중요성
버그 해결을 위해 `now()` 대신 `clock_timestamp()`를 사용하여 문제를 해결했다. 또한, 저자는 테스트 코드(Test Code)의 중요성을 강조하며, 실제 시스템의 호출 방식을 반영한 테스트를 작성해야 함을 역설했다. 특히, 단위 테스트(Unit Test)와 종단간 테스트(End-to-End Test) 사이의 간극을 메우는 테스트가 중요하며, 실제 호출 환경과 유사한 테스트 환경을 구축해야 한다고 강조했다. 즉, 재현 가능한 테스트 케이스(Reproducible Test Case)를 만드는 것이 핵심이다.
테스트 코드의 허점과 개선 방향
저자는 기존 테스트 코드의 허점을 지적하며, 실제 시스템의 동작 방식을 정확히 반영하지 못했음을 밝혔다. 특히, 재시도 정책(Retry Policy)과 장애 상황(Crash Scenario)을 함께 고려한 테스트 케이스가 부재했다. 개선을 위해, 실제 시스템과 유사한 환경에서 테스트를 수행하고, 다양한 시나리오(Various Scenarios)를 커버하는 테스트를 추가해야 한다고 제안했다. 즉, 테스트 커버리지(Test Coverage)를 높이는 것이 중요하다.