كلاود
بنية تحتية

توقيت السيرفر ومزامنته: أخطاء صغيرة بنتائج كبيرة

كلاود|٢٧ أكتوبر ٢٠٢٦|6 دقائق قراءة|٨٧ مشاهدة
توقيت السيرفر ومزامنته: أخطاء صغيرة بنتائج كبيرة

سيرفر جديد بتوقيت UTC يؤخر كروناتك 3 ساعات ويربك NOW() في قاعدة البيانات، وساعة بلا مزامنة تنحرف ثوانيَ كل يوم. اضبط المنطقة بـtimedatectl، وزامن بـchrony، وخزّن الوقت بـUTC واعرضه بتوقيت الرياض.

أغلب صور أنظمة التشغيل الجاهزة تأتي بتوقيت UTC، والسعودية على UTC+3 طوال السنة بلا توقيت صيفي. إذا لم تنتبه لهذا الفرق في سيرفر جديد، فكل مهمة cron جدولتها على الساعة 2:00 فجراً ستعمل فعلياً الساعة 5:00 فجراً بتوقيت الرياض، والدالة NOW() في قاعدة البيانات ستعيد وقتاً متأخراً 3 ساعات عن ساعة الحائط، وطوابع الرسائل والسجلات ستبدو مضللة لكل من يقرأها. المشكلة الثانية أخطر وأقل وضوحاً: انحراف الساعة. ساعة السيرفر بلا مزامنة قد تنحرف عدة ثوانٍ في اليوم، فتفشل رموز التحقق الثنائي، وتُرفض شهادات TLS، ويصبح تتبع حادثة عبر سجلات عدة سيرفرات شبه مستحيل. الحل في ثلاث خطوات: اضبط توقيت النظام بالأمر timedatectl set-timezone Asia/Riyadh إن كانت كروناتك وأدواتك تفترض توقيت الرياض، وفعّل المزامنة عبر chrony حتى يبقى الانحراف في حدود أجزاء من الملّي ثانية، ثم افحص أن النظام وقاعدة البيانات والتطبيق متفقون، مع قاعدة ذهبية للتطبيقات: خزّن الوقت بـUTC في قاعدة البيانات، واعرضه للمستخدم بتوقيت الرياض عند العرض فقط. في هذا المقال الأوامر الكاملة لكل خطوة، وجدول فحص سريع، والأخطاء الأكثر تكراراً.

ماذا يحدث فعلاً حين يأتي السيرفر بتوقيت UTC

السيناريو يتكرر كثيراً: سيرفر جديد، فريق ينقل نظامه من سيرفر قديم كان مضبوطاً على توقيت الرياض، وكل شيء يبدو سليماً في اليوم الأول. بعد أيام تبدأ الأعراض:

  • الكرونات تتأخر 3 ساعات. تقرير المبيعات المجدول الساعة 7:00 صباحاً يصل الساعة 10:00 بعد بدء الدوام، والنسخ الاحتياطي المجدول الساعة 1:00 فجراً يبدأ الساعة 4:00 فيتداخل مع أول حركة صباحية.
  • قاعدة البيانات تكتب وقتاً مختلفاً. كل سجل يستخدم NOW() أو CURRENT_TIMESTAMP يُحفظ بفارق 3 ساعات عن السجلات القديمة المنقولة، فتختلط الترتيبات الزمنية في التقارير.
  • رسائل البريد بطوابع مضللة. بعض الأنظمة تكتب الوقت في متن الرسالة من ساعة السيرفر دون منطقة زمنية، فيقرأ العميل أن فاتورته صدرت الساعة 6:00 بينما صدرت الساعة 9:00.
  • نهاية اليوم في المكان الخطأ. تقرير "مبيعات اليوم" الذي يحسب من منتصف الليل يتجاهل آخر 3 ساعات من اليوم الفعلي ويضيف أول 3 ساعات من اليوم التالي.

كل عرض من هذه يبدو خطأً صغيراً منفصلاً، لكن المصدر واحد، وتشخيصه يبدأ بأمر واحد.

افحص الوضع الحالي في 30 ثانية

timedatectl
date
date -u

ناتج timedatectl يعطيك ثلاث معلومات أساسية: Time zone وهي المنطقة الزمنية الحالية، وSystem clock synchronized وهي هل الساعة مزامنة فعلاً، وNTP service وهي هل خدمة المزامنة فعالة. إذا رأيت Etc/UTC (UTC, +0000) وكان فريقك يكتب الكرونات بتوقيت الرياض، فهنا أصل المشكلة. لتغيير المنطقة:

timedatectl set-timezone Asia/Riyadh
systemctl restart cron    # أو crond على عائلة RHEL

إعادة تشغيل خدمة cron مهمة، لأن بعض إصداراتها تقرأ المنطقة الزمنية عند الإقلاع فقط. إذا أردت إبقاء النظام على UTC وتشغيل كرونات محددة بتوقيت الرياض، يدعم cronie المتغير CRON_TZ=Asia/Riyadh في رأس ملف crontab، مع ملاحظة أن هذا الدعم يختلف بين التوزيعات فاختبره أولاً.

المزامنة مع chrony: لماذا لا تكفي الساعة وحدها

ساعة أي جهاز تعتمد على مذبذب كريستالي لا يطابق التردد المثالي تماماً. انحراف قدره 50 جزءاً في المليون، وهو رقم طبيعي، يعني نحو 4.3 ثانية في اليوم، أي أكثر من دقيقتين خلال 30 يوماً. الأجهزة الافتراضية أكثر عرضة لذلك لأن الساعة قد تقفز بعد ترحيل أو إيقاف مؤقت.

هذا الانحراف يكسر أشياء حقيقية:

النظامهامش التحمل التقريبيماذا يحدث عند تجاوزه
رموز التحقق الثنائي TOTPنافذة 30 ثانيةرفض رمز صحيح وشكاوى دخول
Kerberos وActive Directory5 دقائقفشل المصادقة بالكامل
شهادات TLSحسب تاريخ البدء والانتهاءرفض اتصال HTTPS لشهادة "لم تبدأ بعد"
تكرار قواعد البياناتثوانٍقراءات تأخر مضللة وترتيب أحداث خاطئ
تحليل السجلات عبر عدة سيرفراتأقل من ثانيةتسلسل حادثة يبدو مستحيلاً زمنياً

chrony هو الخيار الافتراضي في التوزيعات الحديثة. للتثبيت والتفعيل:

# أوبونتو وديبيان
apt install chrony
# AlmaLinux وRocky
dnf install chrony

systemctl enable --now chronyd   # اسم الخدمة chrony على أوبونتو

الإعداد في /etc/chrony.conf أو /etc/chrony/chrony.conf، والأسطر الأهم:

pool pool.ntp.org iburst maxsources 4
server time.google.com iburst
makestep 1.0 3
rtcsync

السطر iburst يسرّع المزامنة الأولى بعد الإقلاع إلى ثوانٍ. السطر makestep 1.0 3 يسمح بقفز الساعة مباشرة إذا كان الفرق أكبر من ثانية واحدة خلال أول 3 تحديثات، وبعدها يصحح chrony تدريجياً بتسريع الساعة أو إبطائها حتى لا يعود الوقت للخلف أثناء التشغيل، وهذا مهم لأن عودة الوقت للخلف تربك قواعد البيانات والسجلات. أما rtcsync فيحدّث ساعة العتاد حتى يقلع السيرفر بوقت قريب من الصحيح.

للتحقق:

chronyc tracking
chronyc sources -v

في ناتج tracking انظر إلى سطر System time، والمطلوب أن يكون الفرق بأجزاء من الملّي ثانية، مثل 0.000213 ثانية. في ناتج sources يجب أن يظهر مصدر واحد على الأقل بعلامة ^* وهو المصدر المعتمد حالياً.

النظام وقاعدة البيانات والتطبيق: ثلاث ساعات يجب أن تتفق

ضبط النظام وحده لا يكفي، لأن قاعدة البيانات والتطبيق لكل منهما إعداد منطقة زمنية مستقل. في MySQL وMariaDB:

SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone,
       NOW(), UTC_TIMESTAMP();

إذا كانت قيمة time_zone هي SYSTEM فقاعدة البيانات تتبع النظام، وتغيير منطقة النظام يغيّر نتيجة NOW() بعد إعادة تشغيل خدمة قاعدة البيانات. لتثبيت القيمة صراحة أضف في ملف الإعداد default-time-zone = '+03:00'، أو استخدم الاسم Asia/Riyadh بعد تحميل جداول المناطق بالأمر mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql.

انتبه للفرق بين نوعي الأعمدة في MySQL: العمود TIMESTAMP يُحفظ داخلياً بـUTC ويُحوَّل حسب منطقة الجلسة عند القراءة والكتابة، أما DATETIME فيُحفظ كما هو حرفياً بلا أي تحويل. لهذا قد تتغير قيم عمود TIMESTAMP المعروضة فجأة بعد تغيير المنطقة، بينما تبقى قيم DATETIME ثابتة ولو كانت مكتوبة بمنطقة أخرى.

في PHP يوجد الإعداد date.timezone في php.ini، وفي Laravel القيمة timezone في ملف config/app.php. في Node.js يتبع التطبيق متغير البيئة TZ أو منطقة النظام. فحص سريع يجمع الطبقات الثلاث:

date '+%F %T %Z'
php -r 'echo date("Y-m-d H:i:s T"), PHP_EOL;'
mysql -e "SELECT NOW(), @@global.time_zone;"

إذا اختلفت الأوقات الثلاثة بأي فارق غير مقصود، فأمامك خلل سيظهر في التقارير عاجلاً أو آجلاً.

القاعدة الذهبية: خزّن بـUTC واعرض بتوقيت الرياض

قد يبدو أبسط أن تضبط كل شيء على توقيت الرياض وتنتهي، وهذا مقبول لنظام داخلي صغير لن يخرج من السعودية. لكن لأي نظام جاد نوصي بالنهج التالي:

  1. قاعدة البيانات والتطبيق على UTC. في Laravel مثلاً تبقى القيمة 'timezone' => 'UTC'، وكل وقت يُحفظ بلا لبس.
  2. التحويل عند العرض فقط. عند عرض الوقت للمستخدم يُحوَّل بسطر واحد مثل $order->created_at->timezone('Asia/Riyadh').
  3. الكرونات تُكتب بوعي. إذا بقي النظام على UTC، فاكتب الكرون بالساعة 23:00 ليعمل منتصف الليل بتوقيت الرياض، ووثّق ذلك في تعليق فوق السطر.

لماذا هذا الجهد؟ لأن UTC لا يتغير ولا يحتوي توقيتاً صيفياً، فأي تكامل مع جهة خارجية أو فرع في دولة أخرى أو خدمة سحابية تعمل بـUTC يتطابق مباشرة دون تحويلات مخفية، والأهم أن نقل قاعدة البيانات إلى سيرفر آخر لن يغير معنى أي سجل فيها.

قائمة فحص لأي سيرفر جديد

  1. نفّذ timedatectl وقرر صراحة: UTC أم Asia/Riyadh، واكتب القرار في وثيقة السيرفر.
  2. فعّل chrony وتأكد من ظهور مصدر بعلامة ^* وفرق أقل من 10 ملّي ثانية.
  3. افحص NOW() وUTC_TIMESTAMP() في قاعدة البيانات، وتأكد أن الفرق بينهما صفر أو 3 ساعات حسب قرارك.
  4. افحص منطقة PHP أو Node.js وتطابقها مع القرار.
  5. راجع كل سطر في crontab واسأل: بأي توقيت كُتب هذا السطر؟
  6. بعد أول 24 ساعة، قارن أوقات تشغيل الكرونات الفعلية في السجلات مع المتوقع.

عندما تطلق خادماً سحابياً من كلاود يعمل في الرياض بزمن استجابة نحو 8ms، تبدأ هذه القائمة من أول دقيقة وتنتهي في أقل من 10 دقائق، وتوفر عليك أسابيع من تقارير مربكة. باقة أساس تبدأ بـ3,450 ريالاً سنوياً شامل الضريبة، وتفاصيل الباقات في صفحة الأسعار.

مشاركة