YourWorldTime

2038년 문제, 제대로 설명하기

· 약 4분

시각을 부호 있는 32비트 초 계수로 저장하는 시스템은 2038년 1월에 자리가 바닥나 1901년 12월로 되감깁니다. 현대의 주요 플랫폼은 여러 해 전에 해결했고, 지금 남은 위험은 내장 기기와 오래된 자산, 그리고 직렬화된 데이터에 있습니다.

2038년 1월 19일 03:14:07 UTC에, 1970년 1월 1일 이후 흐른 초의 수가 2,147,483,647에 이릅니다. 부호 있는 32비트 정수가 담을 수 있는 가장 큰 값입니다. 그 1초 뒤에는 −2,147,483,648로 감기고, 그런 시스템은 이를 1901년 12월 13일로 읽습니다.

날짜가 확정된 진짜 문제이며, 그 점이 소프트웨어 문제 가운데 이것을 유별나게 만듭니다.

왜 32비트였나

유닉스 시간은 1970년대 초에 설계되었습니다. 그때는 32비트 정수가 자연스러운 낱말 크기였고 68년의 여유는 넉넉해 보였습니다. 그 시절 time_t 형은 부호 있는 32비트 정수였고, 그 선택은 C로, C를 감싼 모든 언어로, 파일 형식으로, 통신 규약으로, 데이터베이스 구조로 퍼졌습니다.

넘침에 그 특유의 증상을 주는 것은 부호입니다. 부호 없는 32비트 계수라면 2106년까지 버티지만, 부호 있는 계수는 자기 범위를 1970년 앞뒤로 나누므로 넘친 자리가 0이 아니라 1901년이 됩니다.

이미 벌어진 일

2038년 문제는 여러 해째 실제 운영에서 장애를 내고 있습니다. 소프트웨어는 미래의 날짜를 일상적으로 계산하기 때문입니다.

  • 삼십 년짜리 주택담보대출 계산은 2008년부터 경계를 넘기 시작했습니다.
  • 인증서 만료일, 캐시 보관 시간, 예약 작업이 여러 차례 이 경계에 부딪혔습니다.
  • 2006년 AOLserver의 기본 데이터베이스 대기 시간 결함 — 10억 초로 설정되어 있었고, 문제가 되기 전까지는 문제가 아니었습니다 — 은 계산된 만료 시각이 넘치는 순간 그 제품을 멈춰 세웠습니다.

양상은 한결같습니다. 장애는 날짜보다 한참 앞서, 가장 멀리 내다보는 코드에서 찾아옵니다.

이미 고쳐진 것

상황은 떠들썩한 분위기가 암시하는 것보다 훨씬 낫습니다.

  • 64비트 시스템: time_t는 사실상 모든 64비트 플랫폼에서 64비트입니다. 대략 2920억 년 뒤에 바닥납니다.
  • 32비트 위의 리눅스: 커널 5.6(2020년 3월)이 32비트 구조에 64비트 time_t를 들여왔고, 주요 배포판이 그에 맞는 사용자 공간을 내놓았습니다.
  • 자바스크립트: 밀리초를 배정밀도 부동소수점으로 다루며 1970년 양쪽으로 약 27만 5천 년까지 유효합니다. 이 문제를 겪은 적이 없습니다.
  • 자바: Instant는 64비트 초 계수에 나노초를 더해 씁니다.
  • 파이썬: 정수가 임의 정밀도이며, 제약은 플랫폼의 C 라이브러리에 있습니다.
  • PostgreSQL, MySQL 8.0.28 이상, SQL Server: 시각을 64비트로 저장합니다.

위험이 남은 곳

내장 시스템. 산업용 제어기, 의료 기기, 차량 전장, 계량기와 감지기. 다수가 이십 년에서 삼십 년의 사용 기간을 전제로 설계되었고, 다수는 갱신 경로가 없으며, 다수는 이미 설치되어 있습니다. 실제 위험의 대부분이 여기에 있고, 이를 한데 모아 추적하는 곳도 없습니다.

8.0.28 이전 MySQL의 TIMESTAMP 형. 32비트 초 계수이며 문서상 최댓값은 2038-01-19 03:14:07입니다. DATETIME은 한 번도 영향을 받지 않았습니다. 엄청난 양의 운영 데이터가 바로 그 취약한 열 형에 담겨 있습니다.

직렬화된 데이터와 파일 형식. 32비트 시각 항목을 써 넣은 것은 읽는 쪽이 아무리 새것이어도 여전히 32비트 시각 항목입니다. 오래된 보관 형식, 이진 규약, 디스크 위의 구조가 그 제약을 앞으로 실어 나릅니다.

아직 현역인 32비트 빌드. 일부 경로 배정기, 방송 수신 단말, 수명이 긴 장비가 손보지 않은 커널 위에서 32비트 사용자 공간을 돌립니다.

무엇을 해야 하나

저장 형을 점검하십시오. 시각을 담은 int(11) 열, 옛 판본의 MySQL TIMESTAMP, 32비트 시각 항목을 지닌 모든 이진 형식을 찾으십시오. 데이터가 코드보다 오래 사는 자리가 거기입니다.

2038년 이후 날짜로 시험하십시오. 시험용 설정을 2040년으로 두고 전체를 돌려 보십시오. 2038년 결함은 대부분 누군가 시도하기만 하면 손쉽게 재현됩니다.

64비트 정수나 ISO 8601 문자열을 택하십시오. 밀리초를 담은 BIGINTTIMESTAMPTZ 열에는 고민할 만한 한계가 없습니다. ISO 8601 문자열에는 아예 없고, 대신 크기와 비교 속도를 치릅니다.

다시는 날짜를 32비트짜리 무엇에도 넣지 마십시오. 현대 장비에서 저장 공간 절약은 의미가 없고, 한계는 이제 평범한 소프트웨어의 사용 기간 안쪽에 들어와 있습니다.

다른 시한들

2038년은 가장 가까울 뿐, 유일하지 않습니다.

  • 2036년: NTP의 32비트 기원 항목이 2월 7일에 한 바퀴 돕니다. NTPv4는 이를 감당하지만 설치 상태는 제각각입니다.
  • 2106년: 부호 없는 32비트 초 계수가 되감깁니다.
  • 2262년: 64비트 나노초 계수가 넘칩니다. Go 언어의 time.Time이 나노초 표현일 때가 그렇고, pandas의 기본 datetime64[ns]도 그렇습니다.

마지막 것은 데이터 처리 코드를 쓰는 사람에게는 들리는 것보다 가깝고, 규모만 다를 뿐 같은 실수입니다. 쓸모 있을 만큼 잘게 나눈 단위를, 그때는 넉넉해 보이던 폭에 담은 것입니다.

내 시스템을 점검하는 법

품이 적게 드는 순서로 네 가지입니다.

데이터베이스 열 형을 확인하십시오. PostgreSQL에서는 SELECT column_name, data_type FROM information_schema.columns WHERE data_type LIKE '%timestamp%'가 무엇을 쓰고 있는지 알려 줍니다. timestamp with time zone은 64비트이고 안전합니다. MySQL에서는 8.0.28 이전 서버의 TIMESTAMP 열을 찾으십시오. 위험한 것은 그쪽이고 DATETIME은 한 번도 그런 적이 없습니다.

시계를 앞당기십시오. 버려도 되는 컨테이너에서 시스템 날짜를 2040년으로 맞추고 시험 전체를 돌리십시오. 거친 방법이지만 놀랄 만큼 많이 잡아냅니다. 경계를 넘고 나면 2038년 결함 가운데 미묘한 것은 거의 없기 때문입니다.

32비트 시각 형을 훑으십시오. C와 C++에서는 32비트 대상에서 시각 값을 담은 모든 명시적 intlong. 직렬화 정의에서는 4바이트 시각 항목 전부. 응용 코드에서는 저장이나 전송을 위해 시각을 32비트 정수로 밀어 넣는 모든 지점.

시험 설정에서 가까운 미래만이 아니라 먼 미래도 다루십시오. 「지금에 하루를 더한」 값을 쓰는 시험은 2038년까지 통과하다가 어느 화요일에 떨어집니다. 2040년이라는 명시적 날짜를 쓰는 시험은 오늘 떨어집니다. 고칠 사람이 아직 곁에 있을 때 말입니다.

하지 말아야 할 것

부호 없는 32비트 정수로 바꿔서 「해결」하지 마십시오. 68년을 벌지만 1970년 이전의 어떤 날짜도 나타낼 수 없게 되고, 같은 대화를 2106년에 이 시스템을 맡을 사람에게 떠넘깁니다. 4바이트와 8바이트의 차이는 두 번째 이전 작업에 값하지 않습니다.

현대적인 언어가 지켜 주리라고 넘겨짚지도 마십시오. 응용 코드는 완벽히 안전한데 그것이 읽는 데이터는 안전하지 않은 무언가가 썼을 수 있습니다. 파일 형식과 통신 규약은 몇 번을 다시 써도 그 제약을 함께 실어 나릅니다.