مشكلة عام 2038، مشروحة كما ينبغي
· 4 دقيقة قراءة
الأنظمة التي تخزّن الزمن كعدّاد ثوانٍ موقَّع بعرض 32 بت ينفد مداها في يناير 2038 فتلتف إلى ديسمبر 1901. أصلحت معظم المنصات الحديثة ذلك منذ سنوات؛ والتعرّض الباقي اليوم في الأنظمة المضمّنة والبرمجيات القديمة والبيانات المسلسلة.
عند الساعة 03:14:07 بالتوقيت العالمي المنسق من 19 يناير 2038، يبلغ عدد الثواني المنقضية منذ 1 يناير 1970 مقدار 2,147,483,647. هذه أكبر قيمة يسع عدداً صحيحاً موقَّعاً بعرض 32 بت أن يحملها. وبعدها بثانية واحدة يلتف إلى −2,147,483,648، وهو ما تقرؤه تلك الأنظمة على أنه 13 ديسمبر 1901.
إنها مشكلة حقيقية بتاريخ محدد قاطع، وهو ما يجعلها غير مألوفة بين مشكلات البرمجيات.
لماذا 32 بت
صُمِّم زمن يونكس في مطلع السبعينيات، حين كان العدد الصحيح بعرض 32 بت هو حجم الكلمة الطبيعي، وكان هامش ثمانية وستين عاماً يبدو سخياً. وكان النوع time_t في تلك الأنظمة عدداً صحيحاً موقَّعاً بعرض 32 بت، وانتقل ذلك الاختيار إلى لغة سي، وإلى كل لغة غلَّفت سي، وإلى صيغ الملفات وبروتوكولات الشبكة ومخططات قواعد البيانات.
والإشارة هي ما يمنح الفيض عرضه المميز. فعدّاد غير موقَّع بعرض 32 بت كان ليدوم حتى 2106؛ أما الموقَّع فيقسم مداه حول 1970، فيحطّ الفيض في 1901 لا عند الصفر.
ما الذي وقع فعلاً
ظلت مشكلة 2038 تُسقِط أنظمة في التشغيل الفعلي منذ سنوات، لأن البرمجيات تحسب تواريخ مستقبلية بصورة اعتيادية.
- بدأت حسابات القروض العقارية الممتدة ثلاثين عاماً تتجاوز الحد في 2008.
- اصطدمت به مراراً تواريخ انتهاء الشهادات ومُدد بقاء الذاكرة المؤقتة والمهام المجدولة.
- في 2006 أسقط خللٌ في المهلة الافتراضية لقاعدة بيانات خادوم AOLserver — إذ ضُبطت على مليار ثانية، وكان ذلك مقبولاً إلى أن لم يعد كذلك — البرنامجَ حين فاض وقت الانتهاء المحسوب.
والنمط أن العطل يصل قبل التاريخ بزمن طويل، في أي شفرة تنظر إلى أبعد نقطة أمامها.
ما الذي أُصلح سلفاً
الوضع أفضل بكثير مما يوحي به الهلع.
- الأنظمة ذات 64 بت: النوع
time_tبعرض 64 بت في كل منصة 64 بت تقريباً. وذلك ينفد بعد نحو 292 مليار سنة. - لينكس على 32 بت: أدخلت النواة 5.6 (مارس 2020) نوع
time_tبعرض 64 بت على المعماريات ذات 32 بت، وأصدرت التوزيعات الكبرى فضاء المستخدم المقابل. - جافاسكربت: تستخدم عدداً عشرياً مزدوج الدقة للميلي ثانية، صالحاً لنحو 275,000 سنة على جانبي 1970. ولم تعانِ هذه المشكلة قط.
- جافا: يستخدم
Instantعدّاد ثوانٍ بعرض 64 بت مع النانو ثانية. - بايثون: أعدادها الصحيحة ذات دقة اعتباطية؛ والقيد هو مكتبة سي الخاصة بالمنصة.
- 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 بت. في سي وسي++، أي int أو long صريح يحمل قيمة زمن على هدف بعرض 32 بت. وفي مخططات السلسلة، أي حقل زمن بأربعة بايتات. وفي شفرة التطبيق، كل موضع يُحوَّل فيه ختم زمن إلى عدد صحيح بعرض 32 بت للتخزين أو الإرسال.
اختبر المستقبل البعيد في التجهيزات، لا القريب وحده. اختبارٌ يستعمل «الآن زائد يوم» سينجح حتى 2038 ثم يفشل يوم ثلاثاء. واختبارٌ يستعمل تاريخ 2040 صراحةً يفشل اليوم، بينما ثمة من هو متاح لإصلاحه.
ما ينبغي تجنبه
لا «تحلّ» هذا بالانتقال إلى أعداد صحيحة غير موقَّعة بعرض 32 بت. فذلك يشتري ثمانية وستين عاماً، ويفقدك القدرة على تمثيل أي تاريخ قبل 1970، ويدفع الحوار نفسه إلى من سيصون النظام في 2106. والفارق بين أربعة وثمانية بايتات لا يستحق ترحيلاً ثانياً.
ولا تفترض أن لغة حديثة تحميك. فقد تكون شفرة التطبيق آمنة تماماً بينما كتب البياناتِ التي تقرؤها شيءٌ لم يكن آمناً — وصيغ الملفات وبروتوكولات النقل تحمل القيد عبر كل إعادة كتابة.