본문으로 건너뛰기
임베디드/실시간

EtherCAT 동기가 110초마다 무너진 이유: Jetson 기본 설정 하나를 끄자 328 µs가 3.5 µs로

Jetson AGX Orin과 IgH EtherCAT 마스터로 서보 드라이브 2대를 1 kHz로 돌릴 때, 약 110초마다 드라이브 간 시계 동기(DC) 편차가 수백 µs로 튀었습니다. 원인은 모터도 커널도 제어 코드도 아닌, NVIDIA nvpps 드라이버가 부팅 때 기본으로 켜는 PTP-TSC 하드웨어 잠금이었습니다. 디바이스 트리 속성 하나로 끄자 DC 편차 최대값이 328 µs(잠금 켬, 300초 측정)에서 3.5 µs(잠금 끔, 20분 측정)로 줄었습니다.

WRWIM Robotics Team
·
jetsonethercatdistributed-clocksnvppsptpreal-time

Jetson AGX Orin 한 대에 IgH EtherCAT 마스터를 올리고 서보 드라이브 두 대를 1 kHz로 돌리고 있었습니다. 시험 장비는 두 축을 기계적으로 맞물려 놓은 구조라서, 두 드라이브가 같은 순간에 같은 명령을 실행하지 못하면 축끼리 서로 밀어붙입니다. 23.5 rad/s 고속 운전 중에 바로 그 일이 일어났습니다. 토크가 튀었습니다.

기록을 열어 보니 드라이브 사이의 시계 동기, 즉 EtherCAT DC(Distributed Clocks) 편차가 몇 초씩 수십에서 수백 µs로 치솟는 구간이 있었습니다. 실시간 루프의 지터는 1.2 µs 이하였고 주기를 놓친 적도 없었습니다. 그런데도 드라이브 시계는 어긋났습니다. 그것도 약 110초마다.

"제어 루프는 멀쩡한데, 드라이브 시계는 왜 2분마다 어긋날까?"

결론부터 말하면: 범인은 모터도, 커널도, 저희 제어 코드도 아니었습니다. NVIDIA nvpps 드라이버가 부팅 때 기본으로 켜는 PTP-TSC 하드웨어 잠금이 Jetson의 호스트 시계 속도를 약 110초마다 최대 ±140 ppm씩 바꾸고 있었습니다. 디바이스 트리 속성 ptp_tsc_sync_dis 하나로 이 잠금을 끄자 DC 편차 최대값이 328 µs에서 3.5 µs로, 10 µs를 넘는 샘플 비율이 7.4%에서 0%로 줄었습니다(잠금 켬 300초, 잠금 끔 20분 측정). 처음 문제가 났던 고속 운전에서도 326 µs가 2.7~5.2 µs가 됐습니다. 제어 코드는 한 줄도 바꾸지 않았습니다.

이 글은 그 추적 과정입니다. 가는 길에 세 번 헛짚었고, 그중 한 번은 문제를 오히려 키웠습니다. 마지막에는 여러분의 Jetson에 같은 설정이 켜져 있는지 확인하는 방법을 정리했습니다.

시험 환경​

항목내용
호스트Jetson AGX Orin, JetPack 6.2.1 (Jetson Linux R36.4.4), 커널 5.15.148-rt-tegra (PREEMPT_RT)
EtherCAT 마스터IgH EtherCAT Master 1.6.13, 전용 NIC Intel I210 (ec_igb 네이티브 드라이버)
슬레이브Panasonic MINAS A6B 서보 드라이브 2대, 두 축을 기계적으로 맞물린 사내 시험 장비
통신 주기1 ms (1 kHz), DC 동기 사용
기록한 시계기준 드라이브의 ESC 시계, 2번 드라이브의 DC 편차(ESC 레지스터 0x092C, System Time Difference), EtherCAT NIC(I210)의 하드웨어 시계(PHC), 내장 이더넷 eno1의 PHC, 호스트 CLOCK_MONOTONIC·CLOCK_MONOTONIC_RAW

배경: DC는 무엇을 맞추고, 호스트 시계는 어디에 끼어드나​

EtherCAT 슬레이브의 통신 칩(ESC)은 각자 하드웨어 시계를 갖고 있습니다. DC는 이 시계들을 첫 번째 DC 슬레이브, 즉 기준 클록(reference clock) 에 맞춰 둡니다. 드라이브는 이 공통 시각 위에 정해진 SYNC0 신호 순간에 명령을 적용합니다. 그래서 마스터가 보낸 프레임이 조금 늦게 도착해도 두 드라이브는 같은 순간에 움직입니다.

남은 질문은 기준 클록과 마스터(호스트 PC)의 시계를 어떻게 맞추느냐 입니다. 업계에는 두 방향이 있습니다.

방식하는 일IgH API대표 사례
버스를 호스트에 맞춤매 주기 호스트 시각을 넘기고, 기준 클록을 그 시각에 맞추는 프레임을 보냄ecrt_master_application_time() + ecrt_master_sync_reference_clock()acontis EC-Master 기본값 (Bus Shift)
호스트를 버스에 맞춤호스트가 기준 클록 시각을 읽고 자기 주기를 조정ecrt_master_reference_clock_time()Beckhoff TwinCAT 방식 (마스터도 기준 클록에 동기)

저희 제어 소프트웨어 PLEM은 첫 번째 방식을 씁니다. 이 방식에는 호스트 타이머의 주파수가 일정하다는 전제가 붙습니다. acontis 문서도 "애플리케이션이 시작된 뒤에는 타이머 주파수가 절대 바뀌면 안 된다"고 적어 둡니다. 호스트 시계가 흔들리면, 그 흔들림을 매 주기 버스에 그대로 옮기게 되기 때문입니다.

얼마나 흔들려야 문제가 될까요? 시계 속도 차이(ppm)에 시간을 곱하면 어긋나는 시간이 나옵니다.

Δt=Δff×T=140×10−6×1 s=140 μs\Delta t = \frac{\Delta f}{f} \times T = 140 \times 10^{-6} \times 1\,\text{s} = 140\,\mu\text{s}

호스트 시계가 140 ppm 빨라지거나 느려지면 1초에 140 µs 가 어긋납니다. 1 ms 주기로 나누면 매 주기 0.14 µs씩입니다. 이런 속도 변화가 몇 초만 이어져도 수백 µs 편차가 나올 수 있는 크기입니다.

첫 번째 수정 뒤에도 남은 것​

토크 튐의 직접 원인 하나는 먼저 찾았습니다. SYNC0 shift(주기 시작에서 SYNC0 신호까지 두는 시간 여유, 프레임이 드라이브에 도착할 시간을 버는 값)가 0이었습니다. 이를 300 µs로 늘렸습니다.

그래도 DC 편차가 수십~수백 µs로 튀는 구간은 남았습니다. 이 과도가 오면 300 µs 여유를 잠깐씩 거의 다 먹었고, 고속 운전 한 번(326 µs 과도)에서는 그 구간 안에서 토크가 다시 튀었습니다. 여유를 늘린 것은 증상을 덮었을 뿐, 시계가 왜 흔들리는지는 아직 답하지 못한 상태였습니다.

헛짚은 곳 1: 실시간 루프와 드라이브 설정​

먼저 저희 쪽을 의심했습니다.

  • 실시간 루프: 지터 1.2 µs 이하, 주기 놓침 0. 루프는 제때 돌고 있었습니다.
  • 드라이브 설정과 운동: 모터를 돌리지 않아도, 드라이브를 운전 상태로 올리지 않은 채 버스만 돌려도 같은 과도가 났습니다. 운동이나 드라이브 설정과는 상관이 없었습니다.

헛짚은 곳 2: NTP​

다음 용의자는 NTP였습니다. 이 보드는 Wi-Fi로 인터넷 시간 서버에 맞추고 있었고, 오프셋 −108 ms, 지터 38 ms로 상태가 좋지 않았습니다. NTP는 시계를 맞추느라 시계 속도를 조금씩 바꾸기(slew) 때문에 그럴듯한 용의자였습니다.

결과는 "주범은 아니다"였습니다.

  • NTP를 완전히 끈 런에서도 과도가 났습니다.
  • NTP가 손댈 수 없는 시계인 CLOCK_MONOTONIC_RAW 로 재도 같은 속도 변화가 보였습니다.

NTP는 따로 작은 흔들림을 보탤 뿐이었습니다. 이때 하나 더 알게 된 것이 있습니다. 리눅스의 CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW 는 모두 같은 하드웨어 카운터를 셉니다. 카운터 자체가 흔들리면 어느 시계를 골라도 피할 수 없습니다. (PLEM은 CLOCK_MONOTONIC 하나로 통일돼 있습니다.)

증폭기와 방아쇠를 나눠 보다​

PLEM의 DC 방식을 진단용으로 바꿔 가며 같은 조건을 돌려 봤습니다. 모두 모터는 돌리지 않았습니다.

런DC 방식결과
기본매 주기 호스트 시각을 기준 클록에 써 넣음103초, 228초 근처에 수백 µs 과도
진단 1써 넣기를 끔300초 동안 과도 없음
진단 2호스트가 기준 클록을 따라감333초 동안 동기 유지(편차 87 ns 이하). 그사이 호스트 쪽 ±145 ppm 속도 변화 4번을 그대로 흡수

그림이 두 층으로 갈렸습니다. 써 넣는 방식은 증폭기였고, 방아쇠는 호스트 시계 쪽에 있었습니다. 방식을 바꾸면 증상은 피할 수 있었지만, 그러려면 제어 스레드의 주기 정렬과 여러 버스의 정렬까지 다시 손봐야 했습니다. 그 전에 방아쇠부터 찾기로 했습니다.

결정적 실험: 서로 모르는 두 시계가 같은 사건을 보다​

호스트 시계가 흔들리는지 확인하려면, 호스트와 무관한 시계가 필요합니다. 마침 장비 안에 서로 다른 발진기(크리스털)로 도는 시계가 둘 있었습니다.

  • 기준 드라이브의 ESC 시계 (드라이브 안의 발진기)
  • EtherCAT용 Intel I210 NIC의 하드웨어 시계, PHC (/dev/ptp0, NIC의 발진기)

여기에 내장 이더넷 eno1의 PHC와 호스트 시계(CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW)를 함께, NTP를 끈 채 630초 동안 나란히 기록했습니다. DC는 써 넣지도 따라가지도 않고 위상만 재는 모드로 두었습니다.

  • 속도가 바뀌는 사건이 "기준 드라이브 − 호스트"와 "I210 − 호스트"에 같은 시각, 같은 크기로 나타났습니다. 2초 창으로 본 상관계수 1.000, 기울기 1.000입니다.
  • 호스트를 빼고 두 시계끼리 비교하면 속도 차가 −25.91 ppm으로 일정했습니다(2초 창 표준편차 0.60 ppm).
  • NTP는 꺼져 있었고 MONOTONIC − RAW 도 매끈했습니다.

서로 아무 관계 없는 두 발진기가 같은 사건을 같은 크기로 봤다면, 움직인 쪽은 둘의 공통 분모인 호스트입니다. 그리고 RAW 까지 움직였으니, 호스트가 세는 하드웨어 카운터 자체가 속도를 바꾸고 있었습니다.

이상한 점이 하나 더 있었습니다. 호스트 카운터를 케이블도 꽂혀 있지 않은 내장 이더넷 eno1 의 PHC와 비교하자, 사건 밖에서는 둘이 0 ppm 근처로 붙어 있다가 사건 구간에만 −141.8, −142.2, −145.8, +135.4 ppm으로 미끄러졌습니다. 처음 350초 동안 큰 미끄러짐이 세 번 있었고, 그동안 쌓인 위상차는 약 −46 ms였습니다. 제어와 아무 관계 없는 이더넷 포트에 호스트 시계가 묶여 있었던 겁니다.

EtherCAT을 완전히 끄고 10분 동안 시계만 비교해도 같았습니다. 두 PHC끼리는 −26.9 ppm으로 일정했고, 호스트 카운터만 0과 ±140 ppm 사이를 오갔습니다. EtherCAT과 무관한, Jetson 호스트의 현상이었습니다.

호스트 카운터가 기준 없이 도는 eno1 시계에 묶이고, 그 흔들림이 매 주기 기준 클록으로 들어가 드라이브 쪽 DC 편차로 나타납니다. I210 PHC는 호스트와 무관한 발진기라 판정용 기준이 됩니다.

누가 호스트 시계를 이더넷 포트에 묶었나: nvpps​

부팅 로그에 단서가 있었습니다. nvpps 모듈이 올라와 있었고, 이런 줄을 남겼습니다.

primary-emac found ethernet@6800000
using ptp notifier method on emac ethernet@6800000
TSC config ptx 0x113

nvpps 는 NVIDIA의 PPS(pulse per second) 드라이버입니다. NVIDIA DRIVE OS 문서는 이 PTP-TSC 동기 기능을, 이더넷 MAC의 PPS를 이용해 TSC(Time Stamp Counter)의 증가 속도를 그 MAC의 PTP 하드웨어 시계(PHC)에 맞추는 하드웨어 보조 기구로 설명합니다. 목적은 카메라 fsync를 PTP 초 경계에 맞추고, TSC와 PTP 타임스탬프 사이의 상관에서 드리프트를 없애는 것입니다. 그리고 기준 MAC은 외부 PTP 그랜드마스터에 직접 또는 스위치를 거쳐 맞춰진 PTP client 여야 한다고 적습니다.

Jetson Linux R36.4.4의 공개 nvpps 소스를 보면 구조가 그대로 보입니다.

  • 드라이버는 ARM 일반 타이머 카운터(cntvct, 리눅스 arch counter)를 "TSC"로 읽습니다.
  • 잠금 기준 MAC은 디바이스 트리의 primary-emac 이 정합니다. AGX Orin 기본 디바이스 트리는 이것을 내장 MGBE0, 즉 eno1 로 지정합니다.
  • 약 1초 주기 타이머가 잠금 상태를 확인하고, 풀려 있으면 다시 잠금을 겁니다.
  • 디바이스 트리에 ptp_tsc_sync_dis 속성이 있으면 이 설정을 통째로 건너뜁니다.

저희 보드의 라이브 디바이스 트리에는 ptp_tsc_sync_dis 가 없었습니다. 기능이 기본값으로 켜져 있었던 것입니다. 그런데 eno1 에는 케이블도, PTP 그랜드마스터도 없었습니다. 호스트 카운터가 아무 기준 없이 도는 시계에 묶여 있었던 셈입니다. 검색하다 헷갈릴 수 있는 점 하나: DRIVE OS 6.0.7 문서는 끄는 속성을 ptp_tsc_dis 로 적어 두었지만, DRIVE OS 7.0.3 문서와 Jetson Linux R36.4.4의 nvpps 소스는 ptp_tsc_sync_dis 를 씁니다.

헛짚은 곳 3: 드라이버를 떼어 냈더니 더 나빠졌다​

하드웨어가 실제로 무엇을 하는지 보려고, nvpps 소스에 나오는 TSC 잠금 상태 레지스터를 20분 동안 0.1초 간격으로 읽기만 했습니다.

  • 상태 레지스터의 1초 카운터는 109124초(중앙값 110초)마다 1020초만 진행하고 나머지 시간에는 멈춰 있었습니다.
  • 멈춘 구간마다 호스트 카운터 속도가 0, −20, ±142 ppm 같은 몇 개 값 중 하나로 고정됐습니다.
  • 잠금 비트(LOCKED)는 속도를 예측하지 못했습니다.

처음에는 "잠금이 풀리는 순간 −141 ppm으로 미끄러진다"는 그림을 세웠는데, 전체 데이터에는 잠긴 상태로 +142 ppm인 구간도, 풀린 상태로 0 ppm인 구간도 있었습니다. 지금 데이터에 가장 맞는 설명은 하드웨어가 갱신을 멈추는 약 100초 동안 마지막 보정값이 그대로 유지된다는 것입니다.

그렇다면 드라이버의 1초 재정렬이 범인일까 싶었습니다. 하드웨어 설정은 그대로 둔 채 nvpps 장치만 드라이버에서 떼어 내고(unbind) 20분을 다시 쟀습니다.

결과는 반대였습니다. 미끄러짐이 사라지지 않고 커졌습니다. 100 ppm 넘게 어긋난 시간이 28%에서 78%로 늘었고, 쌓인 위상차는 45.7 ms가 됐습니다. 약 110초 주기는 그대로였습니다. 드라이버의 소프트웨어 재정렬은 원인이 아니었습니다. 문제는 부팅 때 걸어 둔 하드웨어 잠금 설정 그 자체였습니다.

끄자 사라졌다​

디바이스 트리의 nvpps 노드에 값 없는 속성 ptp_tsc_sync_dis 를 넣고 재부팅했습니다. 20분 동안 다시 쟀습니다.

  • 잠금 설정 레지스터 전부 0, 1초 카운터 정지, 미끄러짐 0.
  • 호스트 카운터 대 I210의 속도 차는 −26.26 ppm으로 일정. 두 발진기 사이의 원래 차이만 남았습니다.

그 상태에서 드라이브를 켜고 20분(1,170초) 동안 DC를 쟀습니다. 모터는 돌리지 않았습니다.

2번 드라이브 DC 편차잠금 켜짐 (기본값, 300초)잠금 끔 (1,170초)
p99약 93 µs0.18 µs
최대328 µs3.5 µs
10 µs 초과 비율7.4% (매 분 발생)0% (20분 내내)

남은 몇 µs짜리 튐은 두 번이었습니다. 하나(757초, 3.8 µs)는 NTP가 주파수 보정값을 바꾼 순간과 겹쳤고, 다른 하나(1,063초, −4.5 µs)는 원인을 찾지 못했습니다. 둘 다 수백 µs짜리 과도와는 자릿수가 다릅니다.

그리고 처음 문제가 났던 고속 운전으로 돌아갔습니다. 두 축을 맞물려 23.5 rad/s로 돌렸습니다. 같은 제품 빌드, SYNC0 shift 300 µs, 런마다 약 33초 운동입니다.

런잠금운동 중 DC 편차 최대비고
수정 전켜짐326 µs23~36초 구간 과도, 그 안에서 토크 튐
수정 후 1꺼짐2.7 µs보호 정지 0, 주기 놓침 0, 운전 중 WKC 불일치 0
수정 후 2꺼짐2.8 µs위와 같음
수정 후 3꺼짐5.2 µs정속 중 몇 ms짜리 한 번. 직전에 루프 지터 7.6 µs. 몇 초씩 오르던 예전 과도와는 모양이 다름

14 rad/s로 돌린 런에서도 운동 중 최대값은 2.3 µs였습니다.

EtherCAT DC 편차, nvpps PTP-TSC 잠금 켜짐과 끔 비교

(a) 모터를 돌리지 않고 1 kHz 버스만 돌린 측정에서 2번 드라이브의 DC 편차 p99와 최대값. (b) 두 축을 맞물려 23.5 rad/s로 돌린 런의 운동 중 최대값. 세로축은 로그 눈금이고, 빨간 점선이 10 µs입니다.

이 잠금, 꺼도 괜찮을까​

이 기능에는 목적이 있습니다. 끄기 전에 기대는 기능이 있는지 봤습니다.

  • 저희 구성에서 스테레오 카메라 여러 대의 동기는 디시리얼라이저 하드웨어가 하고 있었습니다.
  • 카메라 fsync 생성기는 꺼져 있었습니다.
  • 재부팅 뒤 카메라 점검도 정상이었습니다.

다만 eno1 을 실제 PTP 그랜드마스터에 연결해 PTP로 시간을 맞추는 장비라면, 이 잠금은 원래 목적대로 일하는 중이니 켜 두는 것이 맞습니다. 이 기능이 문제가 되는 것은 기준 MAC이 아무 기준 없이 돌 때입니다.

PLEM에서는 이렇게 정리했습니다.

  • 실시간 환경 설정 단계가 nvpps 노드가 있는 보드에서 이 속성을 자동으로 넣습니다. 부팅 라벨마다 오버레이를 붙이는 대신 기본 디바이스 트리 자체에 넣어서, 카메라 드라이버 설치처럼 부팅 라벨을 새로 만드는 작업 뒤에도 설정이 빠지지 않게 했습니다.
  • eno1 을 PTP로 쓰는 장비는 잠금을 유지하는 선택지를 둡니다.
  • DC 방식(호스트 시각을 버스에 써 넣기)은 그대로 둡니다. 방아쇠가 사라졌으니 방식을 바꿀 급한 이유가 없습니다.

내 Jetson도 그럴까: 확인 방법​

1. 디바이스 트리 확인

ls /sys/firmware/devicetree/base/bus@0/nvpps/

목록에 ptp_tsc_sync_dis 가 있으면 잠금이 꺼져 있습니다. 없으면 기본값, 즉 켜져 있습니다.

2. 부팅 로그 확인

sudo dmesg | grep -i "TSC config ptx"

이 줄이 나오면 부팅 때 잠금이 설정된 것입니다.

3. 직접 재 보기

호스트 카운터와 독립 발진기를 가진 NIC 의 PHC를 나란히 기록합니다. 아래 스크립트는 2초마다 두 시계의 속도 차를 ppm으로 찍습니다. PHC 장치를 읽어야 하므로 root로 실행합니다.

# phc_vs_host.py: 호스트 카운터(CLOCK_MONOTONIC_RAW)와 NIC 하드웨어 시계(PHC)의 속도 차를 2초마다 출력
import os, sys, time

dev = sys.argv[1] if len(sys.argv) > 1 else "/dev/ptp0"
fd = os.open(dev, os.O_RDONLY)
phc = ((~fd) << 3) | 3 # FD_TO_CLOCKID: PHC를 clock_gettime으로 읽기 위한 clock id

def sample():
r0 = time.clock_gettime_ns(time.CLOCK_MONOTONIC_RAW)
p = time.clock_gettime_ns(phc)
r1 = time.clock_gettime_ns(time.CLOCK_MONOTONIC_RAW)
return (r0 + r1) // 2, p

prev = sample()
while True:
time.sleep(2.0)
cur = sample()
d_host = cur[0] - prev[0]
d_phc = cur[1] - prev[1]
ppm = (d_host - d_phc) / d_phc * 1e6
print(f"{time.strftime('%H:%M:%S')} host - PHC = {ppm:+8.2f} ppm", flush=True)
prev = cur
sudo python3 phc_vs_host.py /dev/ptp0

출력이 몇 ppm에서 수십 ppm 사이 값으로 일정하면 두 발진기의 원래 차이입니다. 그 값이 약 110초마다 100 ppm 넘게 계단처럼 바뀌면 호스트 쪽이 움직이는 것입니다. 10분이면 충분히 보입니다.

비교 대상으로 내장 eno1 의 PHC는 쓰지 마세요. 잠금을 끈 뒤에도 호스트 카운터와 0.01 ppm 안으로 맞아서, 같은 SoC 발진기를 쓰는 것으로 보입니다. 별도 NIC(저희는 EtherCAT용 I210)의 PHC가 판정 기준입니다.

아직 모르는 것​

  • 약 110초라는 주기 자체가 어디서 오는지는 설명하지 못했습니다. 커널 로그에도, eno1 인터럽트에도 흔적이 없습니다. 하드웨어 쪽 동작이라 NVIDIA의 설명이 필요합니다. 같은 현상을 보신 분이 있다면 이야기를 나누고 싶습니다.
  • NVIDIA 개발자 포럼에도 JetPack 6.0의 AGX Orin에서 CLOCK_MONOTONIC_RAW 의 주파수가 뛴다는 보고가 있습니다(링크). 그 글은 원인을 특정하지 않았습니다. 같은 원인인지는 모르지만 증상은 닮았습니다.
  • 이번 측정은 JetPack 6.2.1(R36.4.4)의 Jetson AGX Orin에서 했습니다. JetPack 7은 아직 확인하지 않았습니다.

정리​

  • 제어 루프가 멀쩡한데 DC가 주기적으로 무너지면 호스트 시계를 의심하세요. 서로 무관한 두 시계를 나란히 놓으면 누가 움직이는지 바로 보입니다.
  • Jetson Orin(JetPack 6)에서 디바이스 트리에 nvpps 노드가 있고 ptp_tsc_sync_dis 가 없으면, 부팅 때 PTP-TSC 잠금이 켜집니다. 기준 이더넷 포트에 PTP 그랜드마스터가 없으면 이 잠금이 호스트 카운터를 약 110초마다 최대 ±140 ppm 흔들 수 있습니다.
  • 디바이스 트리 속성 ptp_tsc_sync_dis 하나로 DC 편차 최대값이 328 µs에서 3.5 µs로, 고속 운전 중 최대값이 326 µs에서 2.7~5.2 µs로 줄었습니다. 제어 코드는 바꾸지 않았습니다.
  • 호스트 시각을 버스에 써 넣는 DC 방식은 업계 표준 선택지지만 "호스트 시계가 일정하다"는 전제 위에 서 있습니다. 그 전제를 플랫폼 기본값이 깰 수 있다는 것이 이번 교훈입니다.

참고​