시계 어긋남
고친 사람 github-actions[bot]
시계 어긋남은 같은 순간에 서버마다 시계가 서로 다른 시각을 가리키는 일입니다. 이 차이가 크면 한 서버가 적은 시각을 다른 서버가 견주는 판정이 틀어집니다. 막 발급한 토큰을 다른 서버가 아직 쓸 수 없다고 거절하는 것이 흔한 모습입니다. 시계를 맞춰도 이 차이는 0이 되지 않습니다. 칩 설계에서는 같은 이름이 다른 뜻으로 쓰입니다.
쉽고 빠른 이해
시계 어긋남은 같은 순간에 두 서버가 서로 다른 시각을 가리키는 일입니다. 한 서버는 10:00:00 이라고 믿습니다. 같은 순간 다른 서버는 09:59:30 이라고 믿습니다.
이 차이는 한 서버가 적은 시각을 다른 서버가 읽을 때 말썽이 됩니다. 토큰의 유효 기간 검사, 여러 서버 로그의 순서, 같은 값을 두 서버가 고쳤을 때 어느 쪽이 남나가 모두 여기에 걸립니다.
- 서버마다 시계가 조금씩 다른 속도로 갑니다
- 시계를 맞추는 프로그램이 차이를 줄이지만 0으로는 못 만듭니다
- 한 서버가 적은 시각을 다른 서버가 자기 시계로 견주면 남은 차이만큼 판정이 틀어집니다
흔히 쓰는 대책은 시각을 견줄 때 몇 분쯤 여유를 두는 것입니다. 대가는 그 여유만큼 만료된 토큰도 더 받아 준다는 것입니다.
상세
이 절은 서버 두 대의 시계가 벌어지는 모습에서 출발합니다. 어긋남이 판정을 틀리게 만드는 조건과 그것을 견디는 방법까지 봅니다.
컴퓨터마다 지금 몇 시인지 알려 주는 시계가 하나씩 들어 있습니다. 사람이 읽는 날짜와 시각을 주는 이 시계를 벽시계라고 부릅니다. 로그에 찍히는 시각도, 토큰에 적는 만료 시각도 이 시계에서 나옵니다.
같은 순간에 읽은 서버 가의 벽시계는 10:00:00 을, 서버 나의 벽시계는 09:59:30 을 가리킵니다. 두 값의 차이인 30초가 이 순간의 시계 어긋남입니다. 영어 이름은 clock skew 입니다. 아래에서는 줄여서 어긋남이라고 부릅니다.
어긋남 · 오프셋 · 드리프트
시계가 틀어진 정도를 말하는 낱말은 셋입니다. 셋은 무엇과 무엇을 견주는지가 다릅니다.
그중 둘은 기준 시각과 견줍니다. 기준 시각은 모든 서버가 맞추려는 공통 시각입니다. 세계 표준으로 쓰는 UTC(Coordinated Universal Time, 협정 세계시)가 흔한 기준입니다.
| 견주는 것 | 무엇으로 재나 | 예 | |
|---|---|---|---|
| 어긋남 | 같은 순간 두 서버의 시계 | 시간 | 가와 나가 30초 벌어져 있다 |
| 오프셋 | 한 서버의 시계와 기준 시각 | 시간 | 가가 기준보다 20초 빠르다 |
| 드리프트 | 한 서버의 시계가 가는 속도와 기준 속도 | 시간마다 벌어지는 양 | 가가 하루에 1초씩 빨라진다 |
어긋남은 두 서버의 오프셋 차이와 같습니다. 가가 기준보다 20초 빠르고 나가 10초 느리면 둘의 어긋남은 30초입니다. 드리프트는 이 값들이 시간이 갈수록 커지는 까닭입니다.
skew 라는 영어 낱말이 두 시계가 가는 속도의 차이를 가리킬 때도 있습니다. 그 뜻은 위 표의 드리프트에 가깝습니다. 이 항목의 어긋남은 늘 같은 순간 두 시계 값의 차이입니다.
시계가 벌어지는 까닭
컴퓨터 시계의 박자는 수정 진동자에서 나옵니다. 작은 수정 조각에 전기를 주면 일정한 횟수로 떨립니다. 컴퓨터는 그 떨림을 세어 시간이 얼마나 흘렀는지 압니다.
떨리는 횟수는 부품마다 조금씩 다릅니다. 온도가 바뀌어도 흔들립니다. 같은 시각에서 출발한 두 서버도 며칠이 지나면 서로 다른 시각을 가리킵니다.
시계가 한꺼번에 크게 벌어지는 때도 있습니다. 멈춰 두었던 가상 머신을 되살리면 멈춘 시간만큼 뒤처진 채 깨어납니다. 시계를 맞추는 프로그램이 죽어 있으면 차이가 소리 없이 계속 커집니다.
시계 동기화는 이 차이를 줄이는 일입니다. 서버가 기준 시각에 맞춰 둔 서버에 때때로 지금 몇 시인지 묻습니다. 그리고 어긋난 만큼 자기 시계를 고칩니다. 흔히 NTP(Network Time Protocol, 네트워크 시간 프로토콜)라는 프로토콜로 합니다.
그래도 차이는 0이 되지 않습니다. 시각을 알려 주는 답은 네트워크를 건너오는 동안 낡습니다. 묻고 답을 받기까지 걸린 왕복 시간은 잴 수 있습니다. 시계를 고칠 때는 그 절반이 답이 오는 데 걸렸다고 봅니다.
가는 길과 오는 길에 걸린 시간은 다를 수 있습니다. 둘을 따로 잴 방법이 없어서 가는 길과 오는 길의 차이만큼 오차가 남습니다. 잘 관리된 서버들 사이에도 작은 어긋남이 늘 있는 까닭입니다.
판정이 틀어지는 조건
시계가 어긋나 있다고 늘 사고가 나지는 않습니다. 이 소절은 어긋남이 판정을 틀리게 만드는 조건 셋을 세웁니다. 셋이 다 맞을 때 판정이 틀어집니다.
- 한 서버가 자기 벽시계로 시각을 적습니다. 토큰의 만료 시각이나 기록에 붙이는 타임스탬프가 그렇습니다
- 다른 서버가 그 시각을 자기 벽시계와 견줍니다
- 두 시계의 어긋남이 그 판정의 여유보다 큽니다. 여유는 검사하는 쪽이 미리 봐주기로 한 차이입니다(예: 1분)
하나라도 빠지면 판정은 맞게 나옵니다. 적는 서버와 읽는 서버가 같으면 어긋남이 끼어들 틈이 없습니다. 어긋남이 여유 안에 머물러도 결과는 바뀌지 않습니다.
유효 기간 검사
첫 장면은 유효 기간이 적힌 증명을 다른 서버가 검사하는 경우입니다. 증명을 만든 서버와 검사하는 서버가 다르므로 조건 1과 2가 늘 맞습니다.
토큰은 로그인한 사용자가 누구인지 증명하는 값입니다. 로그인을 처리한 서버가 만들어 줍니다. 사용자는 다른 서버에 요청할 때마다 이 값을 내밉니다. 발급한 서버는 토큰에 언제부터 언제까지 쓸 수 있는지를 적습니다.
JWT(JSON Web Token)는 토큰을 담는 흔한 형식입니다. 시작 시각과 끝 시각을 nbf(not before) 와 exp(expiration) 라는 이름으로 담습니다. nbf 는 이 시각 전에는 쓸 수 없다는 뜻입니다. exp 는 만료 시각입니다.
SAML(Security Assertion Markup Language)로 로그인할 때도 같은 판정이 일어납니다. 로그인을 맡은 서버가 서명한 증명을 만들어 줍니다. 그 증명을 받은 서버는 거기 적힌 시작 시각과 끝 시각을 자기 시계와 견줍니다.
발급 서버의 시계가 검사 서버보다 30초 빠릅니다. 발급 서버는 10:00:00 에 토큰을 만듭니다. 쓸 수 있는 시작 시각도 10:00:00 으로 적습니다.
검사 서버의 시계는 이때 09:59:30 입니다. 검사 서버는 막 만든 토큰을 아직 쓸 수 없다고 거절합니다.
sequenceDiagram
participant 발급 as 발급 서버
participant 사용자
participant 검사 as 검사 서버
Note over 발급: 시계 10:00:00
발급->>사용자: 토큰 · 시작 시각 10:00:00
사용자->>검사: 토큰을 내민다
Note over 검사: 시계 09:59:30
검사-->>사용자: 아직 쓸 수 없다며 거절
사용자는 방금 로그인했습니다. 그런데도 거절을 받습니다. 몇 초 뒤 다시 시도하면 통과하므로 원인을 찾기 어렵습니다.
반대로 발급 서버가 느리면 끝 시각이 말썽입니다. 시계가 2분 뒤처진 서버가 수명이 1분인 토큰을 발급합니다. 이 토큰의 끝 시각은 검사 서버의 시계로 1분 전입니다. 검사 서버가 보기에는 받은 순간 이미 만료된 토큰입니다.
수명이 짧은 토큰일수록 어긋남에 약합니다. 같은 30초라도 수명이 한 시간인 토큰에는 작은 몫입니다. 수명이 1분인 토큰에는 절반입니다.
같은 모양의 판정은 더 있습니다. 인증서의 유효 기간 검사와 시각으로 만드는 일회용 비밀번호 검사가 조건 셋을 그대로 갖습니다.
재전송 공격을 막는 검사도 같습니다. 재전송 공격은 가로챈 메시지를 나중에 다시 보내는 공격입니다. 받는 서버는 메시지에 적힌 시각이 너무 오래됐으면 버려서 막습니다.
쓰기 충돌과 사건 순서
둘째 장면은 분산 시스템에서 사건의 앞뒤를 시각으로 가리는 경우입니다. 여러 서버가 각자 적은 타임스탬프를 한데 모아 순서를 매기면 어긋남이 순서를 뒤집습니다.
마지막 쓰기 승리는 같은 값을 여러 서버가 고쳤을 때 타임스탬프가 가장 늦은 쓰기를 남기는 규칙입니다. 여러 복제본이 따로 쓰기를 받는 저장소가 충돌을 간단히 풀려고 씁니다.
서버 나의 시계가 서버 가보다 30초 느립니다. 가가 먼저 값을 A 로 고칩니다. 타임스탬프는 10:00:00 입니다.
5초 뒤 나가 같은 값을 B 로 고칩니다. 나의 시계는 이때 09:59:35 입니다. B 에는 A 보다 이른 타임스탬프가 붙습니다.
sequenceDiagram
participant 가 as 서버 가
participant 나 as 서버 나
participant 저장소
가->>저장소: A 쓰기 · 10:00:00
Note over 나: 5초 뒤 · 시계 09:59:35
나->>저장소: B 쓰기 · 09:59:35
Note over 저장소: 타임스탬프가 늦은 A 를 남긴다
저장소는 타임스탬프만 보고 A 를 더 늦은 쓰기로 여깁니다. B 는 버려집니다. 아무 오류도 나지 않습니다.
B 를 쓴 사용자는 저장됐다는 응답까지 받았습니다. 그런데 값이 사라집니다.
여러 서버의 로그를 시각 순으로 합칠 때도 같습니다. 서버 가가 요청을 받아 서버 나를 부릅니다. 가의 시계는 30초 빠르고 나의 시계는 맞습니다.
가의 로그에는 10:00:30 이 붙습니다. 1초 뒤 찍힌 나의 로그에는 10:00:01 이 붙습니다. 합친 로그에서는 나의 기록이 가의 기록보다 앞에 섭니다. 장애를 쫓는 사람은 결과가 원인보다 먼저 일어난 기록을 읽게 됩니다.
어긋남을 견디는 방법
어긋남을 없앨 수는 없습니다. 시스템은 어긋남이 있어도 판정이 맞게 나오도록 짓습니다. 방법은 판정의 모양에 따라 갈립니다.
| 방법 | 하는 일 | 맞는 판정 |
|---|---|---|
| 여유 두기 | 시각을 견줄 때 몇 분쯤 넉넉히 봐준다 | 유효 기간 검사(시작·끝 시각) |
| 한 서버의 시계만 쓰기 | 수명을 시각 대신 길이로 주고 읽는 서버가 센다 | 유효 기간 검사(끝 시각) |
| 논리 시계 | 시각 대신 사건에 번호를 매겨 앞뒤를 가린다 | 사건 순서 |
| 어긋남 감시 | 서버마다 기준과의 차이를 재고 크면 알린다 | 전부 |
여유 두기가 가장 흔합니다. 검사 서버가 자기 시각에 여유를 더하거나 빼서 견줍니다. 아래는 앞의 토큰 장면에 1분 여유를 준 계산입니다.
nbf = 10:00:00
now = 09:59:30 // 검사 서버
now < nbf // 참 → 거절
now + 60초 < nbf // 거짓 → 통과
여유 없이 견준 셋째 줄은 거절입니다. 여유 1분을 더한 넷째 줄은 통과입니다.
여유는 양쪽으로 열립니다. 만료 시각이 지난 토큰도 여유만큼 더 통과합니다. 그래서 여유는 대개 몇 분 안으로 작게 잡습니다. 여유보다 큰 어긋남은 여전히 거절로 이어집니다.
한 서버의 시계만 쓰는 방법은 조건 2를 없앱니다. 「10:05 까지」 대신 「받은 때부터 300초」로 주면 읽는 서버가 자기 시계로만 셉니다. 대신 값이 건너오는 데 걸린 시간만큼 수명이 늘어납니다.
논리 시계는 시각을 버리고 사건마다 번호를 매깁니다. 서버는 메시지를 보낼 때 자기 번호를 실어 보냅니다. 받은 쪽은 자기 번호를 받은 번호보다 크게 올립니다. 이렇게 하면 원인은 늘 결과보다 작은 번호를 받습니다.
벽시계가 얼마나 어긋났든 원인과 결과의 앞뒤는 뒤집히지 않습니다. 다만 서로 연락 없이 따로 난 두 사건은 번호의 크기가 앞뒤를 말해 주지 않습니다.
어긋남은 오류 없이 커집니다. 서버마다 기준 시각과의 차이를 재서 알림을 겁니다. 시계를 맞추는 프로그램이 멈춘 서버를 이렇게 찾습니다.
칩 설계의 클록 스큐
디지털 회로 설계에서는 같은 영어 이름 clock skew 가 다른 일을 가리킵니다. 칩 안의 부품들은 클록 신호라는 공통 박자에 맞춰 움직입니다. 이 신호가 선을 따라 퍼지는 동안 부품마다 닿는 때가 조금씩 다릅니다.
이 차이가 회로 설계의 클록 스큐입니다. 한 칩 안에서 신호가 닿는 때의 차이라서 서버 시계 이야기와는 따로 다루는 주제입니다.
관련 항목
시계 어긋남을 만드는 원인
수정 진동자 · 클록 드리프트 · 오프셋 · 가상 머신 · 실시간 시계 · 단방향 지연
시계 어긋남을 줄이는 프로토콜과 도구
시계 동기화 · NTP · PTP · SNTP · chrony · UTC · 윤초
시계 어긋남에 흔들리는 판정
토큰 · JWT · SAML · 단언 · 신원 공급자 · 서비스 공급자 · 만료 · 인증서 · 일회용 비밀번호 · 재전송 공격 · 리스 · 분산 락 · Kerberos · 세션
시계 어긋남이 순서를 뒤집는 기록과 복제
타임스탬프 · Last Write Wins · 사건 순서 · 로그 · 분산 추적 · 복제 · 결과적 일관성
어긋나는 벽시계와 맞세워지는 시계
벽시계 · 단조 시계 · 논리 시계 · 램포트 시계 · 벡터 시계 · 하이브리드 논리 시계 · TrueTime · happens-before
시계 어긋남과 나란히 분산 시스템을 어렵게 만드는 원인
부분 실패 · 네트워크 분단 · 비동기 네트워크 · 비잔틴 장애 · FLP 불가능성
칩 설계에서 클록 스큐가 걸리는 개념
클록 신호 · 동기 회로 · 플립플롭 · 타이밍 분석
시계 어긋남이 속하는 상위 분류
다른 이름: clock skew · 클록 스큐 · 클럭 스큐 · 시각 어긋남