요약
monotonic은 "한 방향으로만(단조) 증가"하는 성질. monotonic clock은 절대 뒤로 가지 않는 시계다.time.time()(wall clock)은 NTP 동기화·수동 변경으로 뒤로 점프할 수 있어 "얼마나 지났나" 측정에 쓰면 경과 시간이 음수가 될 수 있다.- 타임아웃·침묵 감지처럼 구간(경과) 측정에는
time.monotonic()을 쓴다.
본문
monotonic = 단조(單調), 즉 한 방향으로만 변하는 성질이다.
- 단조 증가: 값이 같거나 커지기만 함
- 단조 감소: 값이 같거나 작아지기만 함
프로그래밍에서 "monotonic clock"은 이 성질을 시간에 적용한 것 — 절대 뒤로 가지 않고 증가만 하는 시계다.
두 종류의 시계
| 구분 | time.time() (wall clock) |
time.monotonic() (단조 시계) |
|---|---|---|
| 기준점 | 1970-01-01 UTC (Unix epoch) | 명세되지 않은 임의 시작점 |
| 뒤로 갈 수 있나 | 있음 — NTP 보정, 수동 시각 변경, 서머타임 등으로 점프/역행 | 없음 — 항상 단조 증가 |
| 절대값 의미 | 있음 ("지금 몇 시") | 없음 (차이만 의미 있음) |
| 용도 | 타임스탬프, 로그, 달력 계산 | 경과 시간, 타임아웃, 벤치마크 |
time.monotonic()은 Python 3.3에서 도입됐고, 반환값의 절대 크기는 의미가 없다. 두 시점의 차이(경과)만 유효하다.
왜 경과 측정에 wall clock을 쓰면 안 되나
# ❌ 위험: NTP가 시계를 뒤로 당기면 elapsed가 음수가 될 수 있음
start = time.time()
...
elapsed = time.time() - start # 시스템 시각이 보정되면 < 0 가능
# ✅ 안전: 항상 증가하므로 구간 측정에 적합
start = time.monotonic()
...
elapsed = time.monotonic() - start
서버 프로세스는 보통 NTP 데몬으로 시각을 계속 보정한다. 이때 wall clock은 앞뒤로 튈 수 있다. "N초 동안 조건이 유지됐는가"(침묵 감지, 타임아웃, 재시도 백오프, 벤치마크) 같은 판정을 wall clock으로 하면, 시각 보정 순간에 경과 시간이 왜곡되거나 음수가 되어 로직이 어긋난다.
물리 시계 vs 논리 시계와의 구분
monotonic clock은 어디까지나 한 머신 안의 물리적 경과를 재는 시계다. 분산 시스템에서 여러 노드 간 이벤트 순서를 매기는 문제는 별개이며, 그건 Lamport/Vector Clock 같은 논리 시계가 담당한다. "뒤로 안 간다"는 성질이 겹쳐 보여도 푸는 문제가 다르다.
관련 노트
- Network Time Protocol은 다양한 방식으로 클럭 동기화를 한다
- threading.Timer는 one-shot non-daemon 스레드라 반복과 종료를 직접 다뤄야 한다
- Python datetime 의 localize 는 라벨링이고 astimezone 이 진짜 변환이다
- 램포트 시계 (Lamport Clock)는 분산시스템에서 이벤트 순서를 결정하기 위한 논리적 시계다.