حلّ التعارضات في تطبيقات offline-first
Last-write-wins غالباً غلط. هاد اللي بيشتغل لما اثنين clients يختلفوا.
جاري تحميل المقال…
Last-write-wins غالباً غلط. هاد اللي بيشتغل لما اثنين clients يختلفوا.
جاري تحميل المقال…
«آخر كاتب يفوز» (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 صغيرة على أيّ جهاز يدفع ثانياً: «تعديلك يتعارض مع تغيير أحدث. أبقِ تعديلك، أو استخدم تعديلهم؟» زرّان، لا تمرير، لا إغراق بالسجلّ.
للتشعّب، لا شيء مرئيّ — الصفّان موجودان فقط.
version مع الجدول
أعلاه يتعامل مع ٩٩٪ من الكتابات. نستخدم vector clocks فقط على العدد
الصغير من الكيانات حيث التحرير التعاونيّ هو ميزة المنتج الفعليّة (وعند
تلك النقطة نستخدم CRDT، لا ساعة مصنوعة يدويّاً).«آخر كاتب يفوز» افتراضيّ يعمل كعرض ويفشل كعقد. استبدله بجدول لكلّ كيان يختار الدمج أو LWW أو يدويّ أو التشعّب حسب المناسب. ادفع كلفة واجهة الإيصالات. قاوم إغراء الذكاء. العميل الذي يشعر أنّ التطبيق يحترم عمله سيغفر الكثير. العميل الذي تختفي بياناته بصمت لن يغفر شيئاً.
إن كانت محرّكات المزامنة تثير اهتمامك، سلسلة محرّك المزامنة تستكمل من حيث ينتهي هذا.