2038年問題を、正しく説明する
· 約 1 分
時刻を符号付き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ビットなのか
Unix時間が設計されたのは1970年代初頭で、32ビット整数が自然な語長であり、68年の余裕は十分に思えました。当時の time_t 型は符号付き32ビット整数で、その選択はC言語へ、Cを包んだあらゆる言語へ、ファイル形式へ、通信規約へ、そしてデータベースの設計へと波及しました。
桁あふれに特徴的な症状を与えているのは符号です。符号なしの32ビットの計数なら2106年まで持ちますが、符号付きは範囲を1970年の前後に割り振るため、あふれた先が零ではなく1901年になります。
すでに起きたこと
2038年問題は何年も前から実運用で障害を起こしています。ソフトウェアは日常的に未来の日付を計算するからです。
- 三十年の住宅ローン計算は2008年に境界を越え始めました。
- 証明書の有効期限、キャッシュの保持時間、予定実行の仕事は繰り返しこれに突き当たっています。
- 2006年、AOLserver の既定のデータベース待ち時間の不具合 — 十億秒に設定されていて、問題になるまでは問題ではありませんでした — は、計算された期限があふれた瞬間にその製品を停止させました。
型は決まっています。障害は期日よりずっと早く、いちばん先まで見ているコードから訪れます。
すでに直っていること
状況は、騒がれ方が示すよりはるかに良好です。
- 64ビットの環境:
time_tは事実上すべての64ビット環境で64ビットです。尽きるのはおよそ2920億年後です。 - 32ビット上のLinux: カーネル 5.6(2020年3月)が32ビット構成に64ビットの
time_tを導入し、主要な配布物は対応する利用者空間を出荷しました。 - JavaScript: ミリ秒を倍精度の浮動小数点数で扱い、1970年の前後およそ27万5千年まで有効です。この問題は最初から存在しません。
- Java:
Instantは64ビットの秒数とナノ秒を保持します。 - Python: 整数は任意精度で、制約は環境側のCライブラリにあります。
- PostgreSQL、MySQL 8.0.28 以降、SQL Server: 時刻の格納は64ビットです。
危険が残る場所
組込み機器。 産業用制御装置、医療機器、車載電子機器、計量器や検知器。多くは二十年から三十年の運用を前提に設計され、多くは更新の手立てがなく、多くはすでに設置済みです。実際の危険の大半はここにあり、中央でまとめて把握されてもいません。
MySQL 8.0.28 より前の TIMESTAMP 型。 32ビットの秒数であり、文書化された上限は 2038-01-19 03:14:07 です。DATETIME は一度も影響を受けていません。膨大な量の実運用データが、その影響を受ける列の型に収まっています。
直列化されたデータとファイル形式。 32ビットの時刻欄を書き出したものは、読み手がどれほど新しくても32ビットの時刻欄のままです。古い書庫形式、二進の通信規約、ディスク上の構造が、その制約を先へ運びます。
現役の32ビット構成。 一部の経路制御装置、放送受信端末、長寿命の設備は、修正の当たっていない核の上で32ビットの利用者空間を走らせています。
どう対処するか
格納の型を洗い出す。 時刻を入れている int(11) の列、古い版の MySQL の TIMESTAMP、32ビットの時刻欄を持つ二進形式を探してください。データがコードより長生きするのはそこです。
2038年より後の日付で試す。 試験用の設定を2040年にして一式を走らせます。2038年の不具合はたいてい、誰かが試しさえすればあっけなく再現します。
64ビット整数か ISO 8601 の文字列を選ぶ。 ミリ秒を入れた BIGINT や TIMESTAMPTZ の列に、気にするほどの限界はありません。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ビットの対象環境で時刻の値を保持している明示的な int や long。直列化の定義では、4バイトの時刻欄すべて。応用側のコードでは、格納や送信のために時刻を32ビット整数へ落としているすべての箇所。
試験の設定で、近い将来だけでなく遠い将来も試す。 「いまから一日後」を使う試験は2038年まで通り続け、その後ある火曜日に落ちます。2040年という明示的な日付を使う試験は今日落ちます。直せる人がまだいるうちに。
やってはいけないこと
符号なし32ビット整数に切り替えて「解決」しないでください。68年を買う代わりに、1970年より前のあらゆる日付を表せなくなり、同じ会話を2106年の保守担当へ押し付けます。4バイトと8バイトの差は、二度目の移行に見合いません。
現代的な言語なら守られる、とも思わないでください。応用側のコードは完璧に安全でも、それが読むデータは安全でない何かが書いたものかもしれません。ファイル形式と通信規約は、何度書き直しても制約を運び続けます。