انتقل للمحتوى
علي هيثم·TECH·يردّ خلال أقل من ساعة
  • الرئيسيةابدأ من هنا
  • الأعمالدراسات حالة، مشاريع
  • الخدماتما الذي سأبنيه لك
  • المدونةمقالات، سلاسل
  • التدريبدورات، مختبرات، دورات
  • عنّيالمهندس وراء هذا الموقع
تسجيل الدخولابدأ مشروع
علي هيثم · TECH
  • الرئيسية↗
  • الأعمال↗
  • الخدمات↗
  • المدونة↗
  • التدريب↗
  • عنّي↗
تسجيل الدخولابدأ مشروع
متاح
علي هيثم·TECH

استوديو هندسي. دمشق، GMT+3.

aliyosef.online

الاستوديو

  • الأعمال
  • المدونة
  • التدريب
  • عنّي
  • تواصل معي

البوابة

  • تسجيل الدخول
  • فتح تذكرة دعم
  • متابعة مشروع
  • حسابي

مصادر

  • الوثائق
  • حالة الأنظمة
  • سجلّ التغييرات
  • حزمة العلامة
  • الخصوصية
  • الشروط

النشرة

ملاحظات تقنية وأخبار، أسبوعياً. بدون حشو.

مجاني. إلغاء الاشتراك متى ما حبيت.

تابعني على
© 2026 علي هيثم يوسف. جميع الحقوق محفوظة.مبنيّ يدويًا بـ React 19.آخر نشر · 2026-05-08كل الأنظمة تعمل
  1. الرئيسية/
  2. المدوّنة/
  3. محرّكات المزامنة
محرّكات المزامنة

حلّ التعارضات في تطبيقات offline-first

Last-write-wins غالباً غلط. هاد اللي بيشتغل لما اثنين clients يختلفوا.

٢٨ نيسان ٢٠٢٦·10 دقائق·بقلم علي يوسف

جاري تحميل المقال…

شاركXLinkedInRSS
استلم المقال التالي في بريدك.مقال جديد كل أحد. هندسة بلغة واضحة. مجاني، يمكنك إلغاء الاشتراك في أي وقت.اشترك→
AY
علي هيثم يوسف

مهندس وكاتب. أبني منصات متعدّدة المستأجرين، محرّكات مزامنة، وأنظمة موثوقة بهدوء. أكثر من ٦٠٠ مشروع مُسلَّم منذ ٢٠١٥.

اشتركابدأ مشروعاً→

اقرأ بعدها

  • PB
    ٥ أيار ٢٠٢٦·محرّكات المزامنة

    متى الـ polling أفضل من الـ websockets

    سنة من الـ trade-offs بمحاسب. ليش الجواب الواضح طلع غلط على ظروف الشبكة عندنا.

    8 دقائق
  • SE
    Series
    ٢٠ نيسان ٢٠٢٦·محرّكات المزامنة

    بناء محرّك مزامنة، الجزء الأول: الـ queue

    افتتاح السلسلة. شكل الـ queue الصادر، ليش الترتيب مهم، والـ test harness اللي بيمسك كل شي.

    14 دقيقة
  • RS
    ١٢ نيسان ٢٠٢٦·محرّكات المزامنة

    مشكلة الـ reconnect storm

    لما 800 موبايل يرجعوا متّصلين بنفس الوقت، شو بصير للسيرفر. الأنماط اللي بتصمد.

    7 دقائق
←العودة إلى كل المقالات

«آخر كاتب يفوز» (Last-write-wins) خاطئ في كلّ الأحيان تقريباً. هو الافتراضيّ الذي يلجأ إليه الناس لأنّه سطر برمجيّ واحد ويعمل في العرض. العرض يعمل دائماً. الشكوى تأتي من العميل الذي حرّر فاتورة على هاتفه، انتقل إلى اللابتوب، وشاهد تغييره من الهاتف يُكتَب فوقه بصمت. لا خطأ، لا toast، لا سجلّ. فقط غياب.

إن كنت تبني أيّ شيء يُعدِّل حالة مشتركة، أنت مدين لمستخدميك بإجابة أصدق. هذه التي استقررنا عليها.

ماذا يعني «التعارض» فعلاً#

التعارض هو كتابتان على نفس الصفّ المنطقيّ كانتا، لحظة تأليفهما، تعتقدان أنّهما تستهدفان نفس حالة الأب، ولكن بحلول وصول الثانية، يكون الأب قد تحرّك.

هذا التعريف يهمّ لأنّ معظم ما يسمّيه الناس تعارضات ليس كذلك. مستخدم يحرّر حقلين مختلفين من نفس الصفّ من جهازين مختلفين ليس تعارضاً، هما ديفان متوافقان. مستخدم ينشئ ابنين لنفس الأب ليس تعارضاً أبداً. التعارض الحقيقيّ، النوع الذي يفقد البيانات حين يُعالج بشكل خاطئ، هو حين تستهدف كلتا الكتابتين نفس حقل نفس الصفّ وتختلفان.

المسارات الأربعة الصادقة#

حين تكتشف تعارضاً حقيقيّاً، هناك أربعة مسارات وكلّ منها مناسب في مكان ما:

١. دمج على مستوى الحقل#

إن استهدف كلا التعديلين مجموعات منفصلة من الحقول على نفس الصفّ، يصل كلاهما. هذا هو المسار الذي يتعامل بصمت مع معظم «التعارضات» في التطبيقات الحقيقيّة: المستخدم غيّر notes على الهاتف، الزميل غيّر due_date على سطح المكتب، كلاهما ينجح.

الفخّ: عليك حساب «أيّ الحقول تغيّرت» مقارنة بإصدار سابق معروف، لا مقارنة بأحدث صفّ على الخادم. وإلّا تعامل الحقول التي لم يلمسها المستخدم كأنّه فعل.

٢. آخر كاتب يفوز، مع إيصال#

للحقول التي يكون فيها الدمج غير آمن بنيوياً (مثل بنود الفاتورة، حيث لا يمكنك دمج إعادتَي ترتيب مختلفتين بشكل مفهوم)، تفوز الكتابة الأحدث. العميل الخاسر يحصل على إشعار inline صغير:

«تعديلك على بنود الفاتورة استُبدل بتغيير أحدث من سطح المكتب. افتح السجلّ لرؤية الإصدار السابق.»

الإيصال هو الفارق بين LWW يحترم المستخدمين و LWW يفاجئهم.

٣. مصالحة يدويّة#

بعض التغييرات مهمّة جدّاً ليُدمَج بصمت أو ليُهمَل بصمت. تحديداً: أيّ شيء كتب فيه المستخدم فقرة أو أيّ شيء ماليّ. اعرض على المستخدم «نسختك» و «النسخة الحاليّة» جنباً إلى جنب، زرّان. اجعل الاختيار له.

هذا هو المسار الوحيد الذي يجب أن تحجب فيه المستخدم. اجعله سريعاً: واجهة الدمج أهمّ من منطق الدمج. معظم التطبيقات تفشل في هذه الخطوة لأنّ فريق الهندسة يعامل الواجهة كفكرة لاحقة.

٤. التشعّب#

لبعض الكيانات (المسوّدات، التعليقات، الملاحظات الشخصيّة)، الإجابة الصحيحة هي إبقاء كلتا الكتابتين كصفَّين منفصلَين. التعارض ليس حقيقيّاً؛ المستخدم يفكّر فيهما كأشياء مختلفة ويريد الاثنين. هذا نادر، لكن حين يكون صحيحاً يكون صحيحاً تماماً.

اختيار المسار حسب الكيان، لا حسب الكتابة#

الخطأ الذي ترتكبه معظم المحرّكات هو محاولة اختيار استراتيجيّة الحلّ ديناميكيّاً لكلّ كتابة، بفحص الـ diff. جرّبنا هذا؛ أنتج حالات حدّيّة لا يستطيع أحد تفسيرها. بدلاً من ذلك، تُعلَن الاستراتيجيّة مرّة لكلّ نوع كيان:

type Strategy =
  | 'field-merge'      // حقول منفصلة → كلاهما يصل
  | 'last-write-wins'  // الأحدث يفوز، الخاسر يحصل على إيصال
  | 'manual'           // اعرض على المستخدم
  | 'diverge';         // أبقِ الاثنين كصفَّين منفصلَين

const STRATEGY: Record<string, Strategy> = {
  contact:        'field-merge',
  invoice:        'manual',
  invoice_item:   'last-write-wins',
  payment:        'manual',
  comment:        'diverge',
  user_settings:  'field-merge',
};

مملّ. متوقّع. حين يبلّغ عميل عن شيء غريب تذهب مباشرة إلى الصفّ في الجدول.

القاعدة غير المعلنة

استراتيجيّة الحلّ تعود إلى المنتج، لا إلى المحرّك. الهندسة تختار الجدول؛ المنتج يملك أيّ استراتيجيّة يحصل عليها كلّ كيان. تلك المحادثة هي المكان الصحيح لإظهار الافتراضات: «نستخدم آخر-كاتب-يفوز على بنود الفاتورة، هل أنت موافق على ذلك؟»

ما يراه المستخدم#

للمسارات الصامتة (دمج الحقول و LWW)، الشيء الوحيد الذي يراه المستخدم هو إشعار صغير في تغذية النشاط:

اليوم، ٢:١٤ مساءً — فاتورة رقم ٤٧٢٩
  الملاحظات حُدِّثت من الهاتف، تاريخ الاستحقاق حُدِّث من سطح المكتب، دُمج.

اليوم، ٢:٠٩ مساءً — فاتورة رقم ٤٧٢٩
  البنود استُبدلت بتعديل سطح المكتب. النسخة السابقة متوفّرة في السجلّ.

للمسار اليدويّ، bottom-sheet صغيرة على أيّ جهاز يدفع ثانياً: «تعديلك يتعارض مع تغيير أحدث. أبقِ تعديلك، أو استخدم تعديلهم؟» زرّان، لا تمرير، لا إغراق بالسجلّ.

للتشعّب، لا شيء مرئيّ — الصفّان موجودان فقط.

ما لا نفعله عمداً#

  • لا vector clocks للحالة المهيمنة. عدد صحيح version مع الجدول أعلاه يتعامل مع ٩٩٪ من الكتابات. نستخدم vector clocks فقط على العدد الصغير من الكيانات حيث التحرير التعاونيّ هو ميزة المنتج الفعليّة (وعند تلك النقطة نستخدم CRDT، لا ساعة مصنوعة يدويّاً).
  • لا مسار «حلّ آليّ بـ AI». يعمل في العروض وينتج اعتذارات في الإنتاج.
  • لا حجب كتابة افتراضيّ. التعارض في الـ queue لا يمنع الكتابة التالية من المرور؛ يجلس، يُحلّ، ويستمرّ الـ queue.

الملخّص الصادق#

«آخر كاتب يفوز» افتراضيّ يعمل كعرض ويفشل كعقد. استبدله بجدول لكلّ كيان يختار الدمج أو LWW أو يدويّ أو التشعّب حسب المناسب. ادفع كلفة واجهة الإيصالات. قاوم إغراء الذكاء. العميل الذي يشعر أنّ التطبيق يحترم عمله سيغفر الكثير. العميل الذي تختفي بياناته بصمت لن يغفر شيئاً.

إن كانت محرّكات المزامنة تثير اهتمامك، سلسلة محرّك المزامنة تستكمل من حيث ينتهي هذا.