كيف يعمل محرك المزامنة
تصف هذه الصفحة محرك المزامنة المُقدَّم في WCPOS 1.10.0. أما الإصدارات الأقدم فتستخدم نموذج نسخ متماثل مختلفًا لكل شاشة على حدة — راجع ما الذي تغيّر في الإصدار 1.10.0 في نهاية هذه الصفحة.
يعمل WCPOS محليًا أولًا: فكل شاشة تقرأ وتكتب في قاعدة بيانات موجودة على الجهاز، ويتكفّل محرك المزامنة بإبقاء تلك القاعدة ومتجر WooCommerce لديك متقاربَين في الخلفية. تشرح هذه الصفحة كيف يقرر المحرك ما الذي يجلبه، ومتى يجلبه، وكيف تعود مبيعاتك إلى الخادم — بمستوى من التفصيل يفيد المطورين والمكاملين وأصحاب المتاجر الراغبين في فهم ما تفعله نقطة البيع باستضافتهم.
محرك واحد لكل متجر وأمين صندوق
تشغّل نقطة البيع محرك مزامنة واحدًا لكل تركيبة من الموقع + المتجر + أمين الصندوق على كل جهاز. ويمتلك ذلك المحرك قاعدة بياناته المحلية الخاصة، لذا فإن تبديل المتجر أو أمين الصندوق يبدّل مستوى البيانات بالكامل بدلًا من تصفية كومة سجلات واحدة مشتركة. والعزل لكل أمين صندوق مقصود: فأمينا صندوق على الجهاز نفسه لا يتشاركان البيانات المحلية أبدًا.
وعند الترقية، لا تُرحَّل قواعد البيانات المحلية في مكانها إطلاقًا — إذ يبدأ التطبيق بقاعدة بيانات جديدة ويعيد التنزيل من الخادم الذي يحتفظ دائمًا بالنسخة الموثوقة (راجع الترقية من الإصدار 1.9).
وفي تثبيتات Pro متعددة المتاجر، يحدّد كل طلب مزامنة متجره، فيؤدي تعديل سعر عند الصندوق إلى تحديث سعر ذلك المتجر بدلًا من سعر المتجر الإلكتروني.
ما الذي يعمل في الخلفية
كل ما يفعله المحرك وفق جدول زمني هو مسار — وحدة عمل خلفية مسمّاة ومحدودة. وتنقسم المسارات إلى ثلاث مجموعات:
| المجموعة | المسارات | الوتيرة الافتراضية |
|---|---|---|
| السحب | فحص التغييرات (إشارة التغيير) | من 10 ثوانٍ إلى 5 دقائق، بحسب الإعداد المسبق للمزامنة |
| الطلبات الحديثة، وبذرة كتالوج المنتجات، وبذرة البيانات المرجعية | نحو 5 دقائق | |
| تدفّق العملاء البطيء (في أوقات الخمول فقط) | نحو 5 دقائق | |
| الدفع | تفريغ الكتابة — يرسل التغييرات المحلية المنتظرة | نحو 10 ثوانٍ |
| الصيانة | تدقيقات السلامة والحذف، وتحديث إجماليات الخادم | من بضع دقائق إلى نحو 17 دقيقة |
وتنطبق خاصيتان على كل مسار:
- يعلن كل مسار سقفًا لعدد الطلبات في كل تشغيلة. فلا يجوز لأي مسار أن يتفرّع إلى عدد غير محدود من الطلبات في تشغيلة واحدة؛ أما المهام الأكبر فتستخدم دفعات محدودة مع مؤشرات قابلة للاستئناف. وهذا ثابت تصميمي جوهري، يُتناول بالتفصيل في أداء المزامنة.
- الصيانة تفسح المجال لأمين الصندوق. فالتدقيقات والتحضيرات الخلفية تعمل بعد العمل التفاعلي الذي يجعل الصندوق جاهزًا للبيع، لا قبله أبدًا، وهي أول ما يُوقَف مؤقتًا حين يُظهر خادمك علامات ضغط.
والوتائر أعلاه هي القيم الافتراضية. أما فترة الفحص وعدد السجلات في الطلب الواحد فقابلان للضبط من قِبل التاجر لكل جهاز من صحة المتجر → الأداء داخل نقطة البيع — راجع صحة المتجر.
كيف تعرف نقطة البيع بالتغييرات
لا يعيد المحرك تنزيل البيانات ليعرف ما إذا كانت قد تغيّرت. فالخادم يحتفظ بسجل تغييرات، وتستطلع نقطة البيع فحص تغييرات خفيفًا يجيب عن سؤال واحد: هل تحرّك أي شيء منذ موضعي الأخير؟
- فحص واحد يغطي ثماني مجموعات — المنتجات، والتباينات، ومعدلات الضريبة، والعملاء، والكوبونات، والفئات، والعلامات التجارية، والوسوم. أما الطلبات فهي خارج فحص التغييرات عن قصد؛ إذ تأتي حداثة الطلبات من مسار الطلبات الحديثة الخاص بها، ولهذا قد تصل تحديثات المنتجات والطلبات بإيقاعات مختلفة.
- الصندوق الخامل لا يكلّف شيئًا يُذكر. يرسل المحرك طلبات شرطية: فحين لا يتغيّر شيء، يجيب الخادم باستجابة واحدة بلا محتوى
304 Not Modified. ويستقر المتجر الهادئ عند استجابة واحدة ضئيلة لكل فحص. - تُوزَّع الفحوص عشوائيًا بنسبة ±20% كي تتباعد عدة صناديق على موقع واحد بدلًا من الضغط على الخادم في دفعات متزامنة.
- تباطؤ الخمول: بعد 10 دقائق دون تفاعل، تتباعد الفحوص (حتى حدّ أدنى قدره 60 ثانية). وأي نشاط حقيقي — لمسة، أو ضغطة مفتاح، أو مسح باركود مقبول — يعيدها فورًا إلى وتيرتها الكاملة ويُطلق فحص لحاق فوريًا. والتباطؤ لا يعمل إلا على إطالة الفترة؛ فهو لا يستطلع أسرع من الإعداد المضبوط أبدًا.
- الصندوق الذي ظل مغلقًا أيامًا يعيد ضبط خط أساسه بدلًا من إعادة تشغيل التاريخ. فإذا كان سجل التغييرات قد تجاوز موضع الصندوق الأخير بمسافة كبيرة، لكلّف إعادة تشغيل كل صف مئات الطلبات. وبدلًا من ذلك يقفز المحرك بمؤشره إلى المقدمة ويعيد فحص حالة الخادم الحالية لما يملكه الجهاز فعلًا — فتتناسب الكلفة مع حجم النسخة المحلية، لا مع طول غياب الصندوق.
كيف تحصل الشاشات على بياناتها
في الإصدار 1.10.0، لا تشغّل الشاشات مزامنتها الخاصة. بل تعلن الشاشة عمّا تعرضه — مصطلح البحث، والمرشّحات، والترتيب، والصفحة — ويقرر المحرك ما إذا كان ذلك يستدعي طلبًا أصلًا. ويُحسم كل إعلان بإحدى ثلاث طرق:
- مُنجَز عبر الشبكة — أدّى المحرك عملًا على الشبكة لتلبيته.
- مُخدَّم محليًا — كان الجهاز يملك الإجابة سلفًا، أو أن جلبًا حديثًا مطابقًا يغطيه.
- مُستبدَل — انتقلت الشاشة إلى غيره (مررت الصفحة، أو غيّرت مرشّحًا) وحلّ إعلان أحدث محله. وهذا أمر اعتيادي، لا خطأ.
والمرشّح الذي لا يمكن الإجابة عنه إلا محليًا لا يسافر إلى الخادم إطلاقًا. كما أن نجاح الإعلان لا يعني أن المجموعة قد نُزّلت بالكامل — إذ تُتتبَّع الاكتمالية على حدة (في القسم التالي).
وليس كل شيء يُنزَّل بشغف، وما يوجد على الجهاز عند الإقلاع يختلف بحسب المجموعة:
- مُبذَّر — تمتلئ المنتجات عبر بذرة كتالوج محدودة؛ وتُسحب معدلات الضريبة عند الإقلاع (فنقطة البيع لا تستطيع حساب السلة من دونها).
- عند الطلب، مع تدفّق بطيء أثناء الخمول — ليس للعملاء بذرة مسبقة. فيُنزّل تدفّق العملاء دفعة صغيرة في كل فترة خمول، ويتخطّاها كليًا كلما كان أمين الصندوق نشطًا، ويصل العملاء الجدد أو المعدَّلون عبر فحص التغييرات.
- يُجلب عند أول فتح — تُسحب الفئات والوسوم والعلامات التجارية والكوبونات حين يفتحها أمين الصندوق أول مرة. أما المجموعة التي لا يفتحها أحد فلا تولّد أي طلب إطلاقًا.
وتحدّث منتقيات التباينات إضافةً إلى ذلك السعر والمخزون مرة واحدة عند كل فتح، فيُعرض التباين المقيم فورًا من دون أن يُظهر مخزونًا قديمًا من أيام مضت.
«هل نُزِّل كل شيء؟» — تغطية صادقة
قراءة مثل «1,240 من أصل 5,000 منتج» تحتاج إلى إجمالي من جانب الخادم ليكون المقام. ويحتفظ المحرك بإجمالي واحد لكل مجموعة (يُحدَّث كل 15 دقيقة تقريبًا، ومجانًا في العادة — إذ تحمل استجابات المزامنة الحقيقية الإجمالي سلفًا، فنادرًا ما يلزم طلب مخصص) ويلتزم بميثاق صدق صارم:
- يُعرض الإجمالي القديم أو المفقود على أنه جارٍ التحقق… — ولا يُستبدل بعدّ محلي بصمت أبدًا. فالمقام المحلي سيقرأ دائمًا 100% ويخفي بالضبط الفجوة التي وُجد الرقم ليكشفها.
- وحين يتعذّر على المحرك ضمان الاكتمالية، يكون الحكم غير معروف وتصف الواجهة الرقم بأنه عدّ محلي.
- ويقيس شريط تغطية الطلبات مقابل تاريخ الطلبات الكامل على الخادم، بينما يحتفظ الصندوق عن قصد بالطلبات المفتوحة والحديثة فقط — لذا يقرأ الصندوق السليم كجزئي هناك بحكم التصميم.
وتظهر هذه الأرقام في صحة المتجر → قاعدة البيانات، إلى جانب مَعلَم جاهز للبيع الذي ينقلب بمجرد وجود أول منتج على الجهاز — فكونك دون اتصال لا يمنعه، لأن البيع دون اتصال هو المقصود. راجع صحة المتجر.
كيف تعود التغييرات إلى متجرك
كل كتابة محلية — بيع، أو تعديل منتج، أو تحديث عميل — تحطّ أولًا في قائمة انتظار صادرة دائمة على الجهاز، ويدفع مسار التفريغ القائمة إلى WooCommerce كل بضع ثوانٍ. وهذا ما يجعل البيع دون اتصال آمنًا: فالبيع المسجَّل بلا اتصال يجلس في القائمة ويُفرَّغ عند عودة الاتصال.
تفاصيل مهمة:
- تُسلسَل كتابات السلة لكل طلب. فتُطبَّق دفعات الماسح السريعة واحدة تلو الأخرى، ويندمج الإضافة المكررة في السطر الذي تكرره بدلًا من إدراج سطر ثانٍ في القائمة.
- إقرارات الطلبات تتبنّى نسخة الخادم. إذ يخصص WooCommerce معرّفات لبنود الطلب عند الإنشاء؛ ويتبنّى المحرك الطلب المُقَر ليطابق التحديثات اللاحقة تلك البنود بدلًا من إلحاق نسخ مكررة. والتبنّي متحفّظ — فهو لا يستبدل أبدًا تعديلًا محليًا لم يره الخادم، ولا ينطبق إلا على الطلبات.
- الكتابة التي يرفضها الخادم رفضًا دائمًا لا يُعاد محاولتها بصمت أبدًا. بل تُركن مع السبب الذي ذكره الخادم نفسه وتظهر في صحة المتجر → قاعدة البيانات تحت «تغييرات لم تصل إلى خادمك قط»، مع إجراءين صريحين: إرسال مجددًا (يعيد بناء الطلب من السجل بصورته الحالية، فتُطبَّق التصحيحات اللاحقة) وتجاهل. ولا توجد حلقة إعادة محاولة تلقائية — فالاسترداد دائمًا إجراء ظاهر ومقصود. راجع صحة المتجر للشرح الموجَّه للتاجر.
علامات تبويب متعددة في المتصفح
تشغيل نقطة بيع الويب في عدة علامات تبويب للمتجر نفسه مدعوم. ويمكن لكل علامة تبويب تسجيل بيع — إذ تُلحَق الكتابات بقائمة الانتظار المشتركة — لكن علامة تبويب واحدة منتخَبة هي التي تتولى الإرسال لكل نطاق متجر + أمين صندوق. وإذا أُغلقت تلك العلامة، يرقّي المتصفح التالية تلقائيًا. أما علامتا التبويب المسجّلتان بأمينَي صندوق مختلفين فهما نطاقان منفصلان، وتدير كلٌّ منهما قائمة انتظارها الخاصة.
التخزين المحلي
يخزّن تطبيقا الويب وسطح المكتب البيانات عبر عامل OPFS (نظام ملفات خاص بالأصل)؛ بينما يستخدم تطبيقا iOS وAndroid التنسيق نفسه على القرص عبر محرك نظام ملفات. وتتشارك المنصات الأربع تنسيق تخزين واحدًا وأدوات التعافي من التلف نفسها. (كانت الإصدارات الأقدم تستخدم IndexedDB على الويب وSQLite على المنصات الأصلية.)
وتُنفَّذ الاستعلامات داخل طبقة قاعدة البيانات — المحدِّد والترتيب والصفحة — فلا يعبر إلى التطبيق سوى صفحة الصفوف المرئية. وعلى عيّنة اصطناعية من 10,000 طلب، خفّض هذا الدفع إلى الأسفل كلفة التحديث لكل عملية كتابة تحت اشتراك حي من نحو 27 مللي ثانية إلى نحو 0.06 مللي ثانية.
الترقية من الإصدار 1.9
لا يرحّل الإصدار 1.10.0 قواعد البيانات المحلية — بل ينفّذ إعادة مزامنة باردة:
- عند أول تشغيل، يفتح التطبيق قاعدة بيانات محلية جديدة ويعيد التنزيل من متجرك. فتوقّع إعادة تنزيل كاملة لمرة واحدة على كل جهاز.
- لا يُفقد شيء: فخادم WooCommerce لديك هو النسخة الموثوقة لجميع البيانات المتزامنة. وتُفرَّغ التغييرات المعلّقة غير المرسَلة أو تُعرض قبل تنظيف البيانات القديمة — فالترقية لا يمكن أن تُتلف بيعًا لم يُرسَل بعد.
- يصدر التطبيق والإضافة معًا في تزامن تام. فعملاء الإصدار 1.10.0 يتحدثون واجهة مزامنة الإضافة من الإصدار v2. وإذا رُقّي أحد الطرفين فقط، تفشل الطلبات بأخطاء
rest_no_route— وهذه البصمة تعني «حدِّث النصف الآخر»، لا أن التثبيت معطوب.
ما الذي تغيّر في الإصدار 1.10.0
| الإصدار 1.9.x | الإصدار 1.10.0 | |
|---|---|---|
| وحدة المزامنة | تدفّق نسخ متماثل واحد لكل استعلام شاشة مُركَّبة | محرك واحد لكل موقع + متجر + أمين صندوق |
| ما الذي يقود الجلب | تركيب الشاشة | إعلان الشاشة عمّا تحتاج إليه |
| اكتشاف التغييرات | استطلاعات دورية + تدقيق كامل كل ساعة | مؤشر سجل التغييرات مع استجابات 304 شرطية |
| إجماليات الخادم | غير متتبَّعة | إجماليات لكل مجموعة مع حالات جارٍ التحقق… الصادقة |
| الكتابات الفاشلة | يُعاد محاولتها بصورة معتمة | قائمة انتظار دائمة، ولوحة استرداد ظاهرة، وبلا إعادة محاولة صامتة |
| ويب متعدد علامات التبويب | غير منسَّق | مُرسِل واحد منتخَب لكل نطاق |
| البحث المحلي | مطابقة بادئة الكلمة | مطابقة السلاسل الجزئية (بحد أدنى 3 أحرف) |
| التخزين | IndexedDB (الويب)، وSQLite (المنصات الأصلية) | تخزين بتنسيق OPFS على جميع المنصات |
| ضبط المزامنة | ثابت | إعدادات مسبقة ومقابض لكل جهاز في صحة المتجر |
ولمعرفة فلسفة الأداء وراء المحرك — سقوف الطلبات، والتراجع تحت ضغط الخادم، والأرقام المقيسة — راجع أداء المزامنة.