서버 시간: 초 단위로만 보이는 시계를 밀리초로 재는 법
서버가 시계를 보여주는 통로는 초 단위 헤더 하나뿐이고, 그마저 반올림이 아니라 버림입니다. 그래서 한 번 읽으면 평균 0.5초 뒤처지고, 그것도 매번 같은 방향으로 틀립니다. 이 도구가 하는 일의 대부분은 그 편향을 없애는 것입니다.
티켓 오픈이 정확히 20시 00분 00초라면, 중요한 것은 내 컴퓨터가 몇 시라고 생각하는지가 아닙니다. 서버가 몇 시라고 생각하는지, 그리고 그것이 내 시계와 얼마나 벌어져 있는지입니다. 기기는 NTP로 시각을 맞추지만 그 값은 흘러갑니다. 절전에서 깨어난 뒤, 혼잡한 회선에서, 하루 종일 데이터로만 쓴 휴대폰에서 특히 그렇습니다.
문제는 웹 서버가 자기 시계를 정밀하게 공개하지 않는다는 데 있습니다. 얻을 수 있는 것은 응답의 Date 헤더뿐이고, 규격상 이 값은 초 단위입니다. 밀리초 자리가 아예 전송되지 않습니다.
헤더를 한 번 읽으면 반드시 틀리는 이유
이 헤더는 반올림이 아니라 버림입니다. 실제 시각이 09:30:00.900인 서버도 09:30:00이라고 적습니다. 소수점 아래를 가까운 쪽으로 올리는 게 아니라 그냥 버립니다.
그래서 한 번 읽은 값은 부정확한 정도가 아니라 편향되어 있습니다. 항상 0에서 1000밀리초 사이만큼 뒤처져 있고, 평균 500밀리초이며, 언제나 같은 방향입니다. 다시 요청해도 소용없습니다. 두 번째 값도 같은 방향으로 치우쳐 있습니다. 백 번을 평균 내면 0.5초 느린 값에 아주 정확하게 수렴합니다.
서버 시간을 보여주는 페이지를 볼 때 이 실패를 의심해 보면 됩니다. 오차 범위에 대해 아무 말도 없이 서버 시계를 숫자로만 띄우고 있다면, 헤더를 그대로 출력한 것일 가능성이 높습니다.
응답마다 답 대신 한계를 받아내기
해법은 응답에서 답을 얻으려 하지 않고 한계를 얻는 것입니다. 요청이 내 시각 s에 나갔고 응답이 r에 도착했으며 헤더 값이 D였다고 합시다. 서버는 s와 r 사이 어느 순간에 그 헤더를 찍었습니다. 서버 시계가 내 시계보다 θ만큼 앞서 있다면, D로 버림되었다는 사실은 그 순간 서버의 진짜 시각이 D 이상 D+1000 미만이었다는 뜻입니다.
정리하면 이 응답 하나가 알려주는 것은 θ가 D − r 이상 D + 1000 − s 이하라는 범위입니다. 답이 아니라 울타리입니다. 응답마다 울타리가 하나씩 생기고, 진짜 값은 모든 울타리 안에 있어야 하므로, 답은 그것들이 겹치는 자리입니다.
여기서 두 가지가 공짜로 따라옵니다. 초가 바뀌기 직전에 잡힌 표본과 직후에 잡힌 표본은 반대 방향에서 울타리를 치기 때문에 겹치는 구간이 아주 좁아집니다. 초가 넘어가는 순간을 특별히 찾아낼 필요 없이 저절로 정밀해집니다. 그리고 빨리 돌아온 응답일수록 울타리가 좁아서 자동으로 더 큰 힘을 갖습니다. 이상치를 골라 버리는 단계가 따로 없습니다. 느린 표본은 그냥 덜 제약할 뿐입니다.
이 도구는 약 70밀리초 간격으로 최대 22개를 표본으로 잡습니다. 1초가 넘는 구간을 덮으므로 버림 구간의 양쪽 끝을 반드시 관측하게 됩니다.
상대가 한 대가 아닐 때
큰 티켓 사이트는 서버 한 대로 응답하지 않습니다. 요청은 로드밸런서가 고른 장비로 들어가고, 그 장비들의 시계가 서로 맞는다는 보장은 없습니다.
여기서 특유의 거짓말이 생깁니다. 한 번의 측정 안에서 표본들은 같은 연결을 타고 같은 장비에 도달하는 경우가 많아서 서로 아주 잘 맞습니다. 그러면 도구는 좁은 오차 범위를 보고하는데, 그것은 장비 한 대의 정밀도일 뿐 장비들 사이의 벌어짐이 아닙니다. 실제로 국내 티켓 사이트 두 곳이 ±33~99밀리초라고 자신 있게 보고하는 동안, 각각 따로 측정한 결과들끼리는 400~500밀리초씩 어긋났습니다.
라운드로빈 방식은 연속된 요청을 다른 장비로 보내므로, 표본을 홀수·짝수로 갈라 두 쪽의 결론을 비교하면 그 불일치가 그대로 드러납니다. 그렇게 벌어진 간격이 보고하는 오차의 하한이 됩니다. 이 상황에서 더 나쁜 잘못은 정밀도를 과장하는 쪽입니다. 결제 시점을 재는 사람에게 필요한 것은 여유를 얼마나 둘지에 대한 정보이고, 자신 있게 틀린 숫자는 정직하게 넓은 숫자보다 위험합니다.
알고리즘보다 거리가 먼저입니다
표본의 울타리는 그 왕복 시간만큼만 좁아집니다. 즉 네트워크 거리가 정확도의 천장을 정하고, 어떤 알고리즘도 그 천장을 뚫지 못합니다.
이건 이론이 아닙니다. 이 기능은 원래 미국 버지니아에서 실행되고 있었는데 방문자는 서울로 들어왔습니다. 그래서 국내 호스트를 찔러 볼 때마다 태평양을 두 번 건넜습니다. 한국 안에서 20~50밀리초면 되는 왕복이 700~790밀리초가 됐고, 보고되는 오차는 ±451~625밀리초로 부풀었으며, 정해진 시간 예산이 22개를 다 채우기 전에 6~7개에서 바닥나 버림 구간을 온전히 덮지도 못했습니다. 실행 위치를 서울로 옮긴 것이 그 위의 모든 정교한 처리보다 큰 차이를 만들었습니다.
같은 논리가 쓰는 쪽에도 적용됩니다. 국내 티켓 사이트를 잰다면 측정도 국내에서 이뤄지는 편이 좋고, 위성 회선이나 다른 대륙을 경유하는 VPN을 쓰고 있다면 정직한 오차 범위는 그만큼 넓어집니다.
티켓 오픈 때 민폐가 되지 않기
측정 한 번에 상대 서버로 최대 22개의 요청이 나갑니다. 한 사람이면 아무것도 아니지만, 수백 명이 같은 몇 초 안에 같은 호스트를 확인하면 얘기가 다릅니다. 그리고 그 순간이 정확히 이 도구를 쓰는 때입니다.
그래서 측정 결과는 호스트별로 10초 동안 재사용하고, 같은 호스트에 대한 동시 요청은 하나의 측정으로 합칩니다. 시계 오차는 백만분율 단위로 흘러가므로 10초의 나이는 1밀리초에도 한참 못 미치고, 이미 보고하는 오차 범위 안에 충분히 들어갑니다. 잃는 신선도는 무시할 만하지만, 정작 도구가 필요한 순간에 요청 차단을 당하는 것은 그렇지 않습니다. 실제로 프리셋 목록을 점검하다가 국내 티켓 호스트 한 곳이 거부하기 시작했고, 요청 간격을 벌리자 회복됐습니다.
당일에 쓰는 법
- •몇 분 전에 미리 열어 오차를 확인하세요. 필요한 것은 내 시계와 서버 시계의 차이입니다.
- •마감 직전에 예매 페이지를 새로 고치지 마세요. 로딩에 쓸 시간이 없습니다. 미리 열어 두고 누르세요.
- •오차 범위가 넓게 나오면 상대가 여러 대로 응답하고 있을 가능성이 큽니다. 정확한 초를 노리기보다 여유를 더 두세요.
- •실제로 쓸 회선에서 확인하세요. VPN을 켜면 결과가 달라집니다.
- •요청한 호스트 대신 상위 도메인을 쟀다고 표시되면 다른 장비라고 보세요. 대개 같은 회사의 가까운 시계지만 같은 시계는 아닙니다.
자주 묻는 질문
얼마나 정확한가요?
호스트와 회선에 따라 다릅니다. 그래서 약속하는 대신 측정된 값을 그대로 보여줍니다. 가깝고 장비가 한 대인 호스트라면 보통 수십 밀리초 수준입니다. 여러 대로 응답하는 티켓 호스트라면 수백 밀리초까지 갈 수 있는데, 그 숫자는 측정이 부실해서가 아니라 그 호스트에 대한 실제 정보입니다.
확인할 때마다 오차 범위가 달라지는데요?
가정한 값이 아니라 잰 값이기 때문입니다. 왕복 시간이 매번 다르고, 여러 대로 응답하는 호스트라면 어느 장비가 답했는지도 매번 다릅니다. 늘 똑같은 숫자가 나온다면 그것은 재고 있지 않다는 뜻입니다.
예매 시스템 내부 시계를 보여주는 건가요?
아닙니다. 바깥에서는 어떤 방법으로도 볼 수 없습니다. 여기서 보는 것은 HTTP 요청에 응답하는 웹 서버의 시계입니다. 보통은 나머지 인프라와 맞춰져 있지만, 누가 티켓을 잡는지 결정하는 대기열은 별개의 시스템이고 외부 도구가 직접 볼 수는 없습니다.
그냥 NTP를 쓰면 안 되나요?
상대가 NTP로 말해 준다면 그쪽이 낫습니다. 하지만 티켓 사이트는 NTP 서비스를 열어 두지 않습니다. 여기서 하는 일은 웹 서버가 실제로 열어 둔 단 하나의 통로, 즉 1초 해상도의 헤더에서 최대한의 정밀도를 되찾는 것입니다.
사용 기록이 남나요?
아니요. 기기에서 나가는 요청은 시간 측정을 요청하는 것 하나뿐입니다.
