Monotonic Clock이란? 시스템 시간과 다른 이유

Monotonic Clock의 정의

개발을 하다 보면 두 시점 사이의 ‘경과 시간’을 측정해야 할 때가 많다. 이럴 때 흔히 System.currentTimeMillis()나 new Date() 같은 벽시계(wall clock) 시간을 쓰기 쉽지만, 이는 함정이 될 수 있다. 벽시계 시간은 NTP 동기화, 사용자의 수동 변경, 서머타임 적용 등으로 인해 갑자기 앞으로 튀거나 뒤로 되돌아갈 수 있기 때문이다. 반면 Monotonic Clock(단조 시계)은 오직 ‘증가’만 하도록 설계된 시계다. 어떤 이유로도 시간이 거꾸로 흐르지 않는다는 것을 보장하며, 절대적인 날짜나 시각(예: 2024년 1월 1일 오후 3시)을 알려주는 것이 아니라 ‘시스템이 부팅된 이후 흐른 시간’ 같은 상대적인 값을 제공한다.

동작 원리와 예시

예를 들어 마라톤 경기에서 결승선까지 걸린 시간을 잴 때, 손목시계의 ‘현재 시각’을 보고 계산하기보다 스톱워치를 누르는 것이 훨씬 안전하다. 스톱워치는 중간에 누가 시계를 조작해도 영향을 받지 않고 오직 흐른 시간만 정확히 기록한다. Monotonic Clock이 바로 이 스톱워치 역할을 한다.

실제 코드에서도 이 차이를 확인할 수 있다. 자바에서는 System.nanoTime()이 단조 시계 기반이며, 두 시점의 값을 빼서 경과 시간을 구하는 용도로만 사용해야 한다. 파이썬에서는 time.monotonic()이 이에 해당하고, 리눅스 시스템에서는 clock_gettime(CLOCK_MONOTONIC) 함수를 통해 접근할 수 있다. 이 값들은 절대 시각과 매핑되지 않으며, 프로세스나 재부팅 시점마다 기준점이 달라질 수 있으므로 다른 시스템의 값과 비교하는 용도로는 쓸 수 없다.

실무에서 왜 중요한가

실무에서 Monotonic Clock은 다음과 같은 상황에서 필수적으로 사용된다.

  • API 응답 시간이나 함수 실행 시간 측정: 시간 역행으로 인해 음수 값이 나오는 버그를 방지한다.
  • 타임아웃 및 재시도 로직 구현: 정확한 대기 시간을 계산해야 하는 로직에서 안정성을 확보한다.
  • 분산 락(distributed lock)의 유효 시간 관리: 락 만료 시점을 계산할 때 시스템 시간 변경에 영향받지 않도록 한다.

만약 벽시계 시간으로 경과 시간을 측정한다면, NTP 동기화로 시간이 몇 초 뒤로 조정되는 순간 ‘음수 시간’이라는 논리적으로 불가능한 값이 계산될 수 있다. 이런 문제는 디버깅하기도 어렵고 프로덕션 환경에서 예기치 못한 장애로 이어질 수 있다. 따라서 ‘지금이 몇 시인가’를 알고 싶을 때는 벽시계 시간을, ‘얼마나 걸렸는가’를 알고 싶을 때는 반드시 Monotonic Clock을 사용하는 습관을 들이는 것이 좋다.

댓글 남기기