Проблема 2038 года, объяснённая по существу
· 4 мин чтения
Системы, хранящие время как знаковый 32-битный счётчик секунд, исчерпают запас в январе 2038 года и перескочат в декабрь 1901-го. Почти все современные платформы решили это годами раньше; сегодня уязвимы встраиваемые устройства, унаследованный код и сериализованные данные.
В 03:14:07 UTC 19 января 2038 года число секунд, прошедших с 1 января 1970 года, достигнет 2 147 483 647. Это наибольшее значение, которое вмещает знаковое 32-битное целое. Секундой позже оно обернётся в −2 147 483 648, что такие системы прочитают как 13 декабря 1901 года.
Это настоящая проблема с твёрдо назначенной датой, чем она и необычна среди программных проблем.
Почему 32 бита
Время Unix проектировали в начале 1970-х, когда 32-битное целое было естественным размером машинного слова, а 68 лет запаса казались щедростью. Тип time_t в тех системах был знаковым 32-битным целым, и этот выбор разошёлся по языку C, по каждому языку, обернувшему C, по форматам файлов, сетевым протоколам и схемам баз данных.
Именно знак придаёт переполнению его характерный симптом. Беззнаковый 32-битный счётчик прослужил бы до 2106 года; знаковый делит свой диапазон вокруг 1970-го, поэтому переполнение приземляется в 1901-м, а не в нуле.
Что уже произошло
Проблема 2038 года годами приводит к сбоям в промышленной эксплуатации, потому что программы регулярно вычисляют будущие даты.
- Расчёты тридцатилетней ипотеки начали пересекать границу в 2008 году.
- Сроки действия сертификатов, время жизни кэша и запланированные задания натыкались на неё неоднократно.
- В 2006 году дефект в стандартном тайм-ауте базы данных в AOLserver — заданном в миллиард секунд, что было приемлемо ровно до тех пор, пока не перестало быть — уронил программу, когда вычисленный срок окончания переполнился.
Закономерность в том, что отказ приходит задолго до самой даты, в том коде, который заглядывает дальше всех вперёд.
Что уже исправлено
Положение заметно лучше, чем внушает паника.
- 64-битные системы:
time_tзанимает 64 бита практически на любой 64-битной платформе. Запас исчерпается примерно через 292 миллиарда лет. - Linux на 32 битах: ядро 5.6 (март 2020 года) ввело 64-битный
time_tна 32-битных архитектурах, а крупные дистрибутивы выпустили соответствующее пользовательское окружение. - JavaScript: использует число с плавающей точкой двойной точности для миллисекунд, что верно примерно на 275 000 лет в обе стороны от 1970 года. У него этой проблемы никогда не было.
- Java:
Instantхранит 64-битный счётчик секунд плюс наносекунды. - Python: целые числа произвольной точности; ограничением служит библиотека C самой платформы.
- PostgreSQL, MySQL 8.0.28+, SQL Server: 64-битное хранение меток времени.
Где уязвимость остаётся
Встраиваемые системы. Промышленные контроллеры, медицинские приборы, автомобильная электроника, счётчики и датчики — многие рассчитаны на двадцать или тридцать лет службы, многие не имеют пути обновления, многие уже установлены. Здесь сосредоточен основной реальный риск, и централизованного учёта ему нет.
Тип TIMESTAMP в MySQL до версии 8.0.28. Это 32-битный счётчик секунд, и его документированный максимум — 2038-01-19 03:14:07. DATETIME не затронут никогда. Огромный объём рабочих данных лежит именно в уязвимом типе столбца.
Сериализованные данные и форматы файлов. Всё, что однажды записало 32-битное поле времени, остаётся 32-битным полем времени, каким бы новым ни был читатель. Старые архивные форматы, двоичные протоколы и дисковые структуры несут это ограничение дальше.
32-битные сборки, всё ещё в строю. Некоторые маршрутизаторы, приставки и долгоживущие устройства работают с 32-битным пользовательским окружением поверх необновлённого ядра.
Что с этим делать
Проверьте типы хранения. Ищите столбцы int(11) с метками времени, TIMESTAMP в MySQL старых версий и любой двоичный формат с 32-битным полем времени. Именно там данные переживают код.
Испытайте на датах после 2038 года. Задайте тестовой фикстуре 2040 год и прогоните набор тестов. Почти все ошибки 2038 года воспроизводятся тривиально, стоит кому-нибудь попробовать.
Предпочитайте 64-битные целые или строки ISO 8601. У BIGINT миллисекунд или столбца TIMESTAMPTZ нет горизонта, о котором стоит думать. У строки ISO 8601 его нет вовсе — ценой размера и скорости сравнения.
Больше никогда не храните даты ни в чём 32-битном. Экономия на хранении бессмысленна на современном оборудовании, а горизонт теперь лежит внутри срока службы обычной программы.
Прочие временные горизонты
2038 год — ближайший, но не единственный.
- 2036: 32-битное поле эпохи в NTP перевернётся 7 февраля. NTPv4 с этим справляется, но развёртывания разнятся.
- 2106: переполнятся беззнаковые 32-битные счётчики секунд.
- 2262: переполнятся 64-битные счётчики наносекунд — это
time.Timeв языке Go в наносекундном представлении и datetime64[ns] по умолчанию в pandas.
Последний ближе, чем кажется, для всякого, кто пишет код обработки данных, и это та же самая ошибка в другом масштабе: единица, достаточно мелкая, чтобы быть полезной, в разрядности, казавшейся тогда щедрой.
Как проверить собственные системы
Четыре проверки в порядке возрастания усилий.
Посмотрите типы столбцов базы данных. В PostgreSQL запрос SELECT column_name, data_type FROM information_schema.columns WHERE data_type LIKE '%timestamp%' покажет, что у вас есть; timestamp with time zone — 64-битный и безопасный. В MySQL ищите столбцы TIMESTAMP на серверах до 8.0.28 — уязвимы именно они, а DATETIME не был уязвим никогда.
Переведите часы вперёд. В одноразовом контейнере поставьте системную дату на 2040 год и прогоните набор тестов. Способ грубый и находит удивительно много, потому что почти ни одна ошибка 2038 года не бывает тонкой после пересечения границы.
Поищите 32-битные типы времени. В C и C++ — любой явный int или long, хранящий значение времени на 32-битной цели. В схемах сериализации — любое четырёхбайтовое поле времени. В прикладном коде — каждое место, где метку времени приводят к 32-битному целому для хранения или передачи.
Проверяйте в фикстурах далёкое будущее, а не только ближайшее. Тест, использующий «сейчас плюс сутки», будет проходить до 2038 года, а затем упадёт во вторник. Тест с явной датой в 2040 году падает сегодня, пока есть кому его починить.
Чего делать не стоит
Не «решайте» это переходом на беззнаковые 32-битные целые. Это покупает 68 лет, лишает возможности представить любую дату до 1970 года и перекладывает тот же разговор на того, кто будет сопровождать систему в 2106-м. Разница между 4 и 8 байтами не стоит второй миграции.
Не полагайте, что современный язык вас защитит. Прикладной код может быть совершенно безопасен, тогда как читаемые им данные записало нечто небезопасное — форматы файлов и сетевые протоколы проносят ограничение сквозь любую переписку кода.