هل يمكنني إجراء المعاملات والأقفال في CouchDB؟

StackOverflow https://stackoverflow.com/questions/299723

  •  08-07-2019
  •  | 
  •  

سؤال

أحتاج إلى إجراء معاملات (البدء أو الالتزام أو التراجع) أو الأقفال (حدد للتحديث).كيف يمكنني القيام بذلك في نموذج مستند ديسيبل؟

يحرر:

الحال هو هذا:

  • أريد تشغيل موقع مزادات.
  • وأعتقد أن كيفية الشراء المباشر كذلك.
  • في الشراء المباشر، يجب علي تقليل حقل الكمية في سجل العنصر، ولكن فقط إذا كانت الكمية أكبر من الصفر.لهذا السبب أحتاج إلى الأقفال والمعاملات.
  • لا أعرف كيفية معالجة ذلك بدون أقفال و/أو معاملات.

هل يمكنني حل هذه المشكلة باستخدام CouchDB؟

هل كانت مفيدة؟

المحلول

لا.يستخدم CouchDB نموذج "التزامن المتفائل".في أبسط العبارات، هذا يعني فقط أنك ترسل نسخة مستند مع التحديث الخاص بك، ويرفض CouchDB التغيير إذا كان إصدار المستند الحالي لا يتطابق مع ما أرسلته.

انها بسيطة مخادعة، حقا.يمكنك إعادة صياغة العديد من السيناريوهات العادية القائمة على المعاملات لـ CouchDB.أنت بحاجة إلى التخلص من معرفتك بمجال RDBMS عند تعلم CouchDB.من المفيد التعامل مع المشكلات من مستوى أعلى، بدلاً من محاولة تحويل Couch إلى عالم يعتمد على SQL.

تتبع المخزون

المشكلة التي حددتها هي في المقام الأول مشكلة المخزون.إذا كان لديك مستند يصف أحد العناصر، ويحتوي على حقل "الكمية المتاحة"، فيمكنك التعامل مع مشكلات التزامن مثل هذا:

  1. استرجع الوثيقة، ولاحظ _rev الخاصية التي يرسلها CouchDB
  2. قم بتقليل حقل الكمية، إذا كان أكبر من الصفر
  3. قم بإرسال المستند المحدث مرة أخرى باستخدام _rev ملكية
  4. إذا _rev يطابق الرقم المخزن حاليا، ليتم ذلك!
  5. إذا كان هناك صراع (متى _rev غير متطابق)، قم باسترداد أحدث إصدار من المستند

في هذه الحالة، هناك نوعان من سيناريوهات الفشل المحتملة التي يجب التفكير فيها.إذا كان أحدث إصدار للمستند يحتوي على كمية 0، فيمكنك التعامل معه تمامًا كما تفعل في نظام RDBMS وتنبيه المستخدم بأنه لا يمكنه فعليًا شراء ما يريد شراءه.إذا كان أحدث إصدار للمستند يحتوي على كمية أكبر من 0، فما عليك سوى تكرار العملية مع البيانات المحدثة والبدء من البداية.يفرض عليك هذا القيام بعمل أكثر قليلاً مما يفعله نظام RDBMS، وقد يصبح مزعجًا بعض الشيء إذا كانت هناك تحديثات متكررة ومتضاربة.

الآن، الإجابة التي قدمتها للتو تفترض مسبقًا أنك ستقوم بالأشياء في CouchDB بنفس الطريقة التي تفعلها في نظام RDBMS.قد أتعامل مع هذه المشكلة بشكل مختلف قليلاً:

سأبدأ بمستند "المنتج الرئيسي" الذي يتضمن جميع البيانات الوصفية (الاسم والصورة والوصف والسعر وما إلى ذلك).ثم سأضيف مستند "تذكرة المخزون" لكل حالة محددة، مع حقول لـ product_key و claimed_by.إذا كنت تبيع نموذجًا للمطرقة، ولديك 20 منها للبيع، فقد يكون لديك مستندات تحتوي على مفاتيح مثل hammer-1, hammer-2, ، وما إلى ذلك، لتمثيل كل مطرقة متاحة.

بعد ذلك، أقوم بإنشاء عرض يمنحني قائمة بالمطارق المتاحة، مع وظيفة تقليل تتيح لي رؤية "الإجمالي".هذه الأمور غير متوفرة تمامًا، ولكنها يجب أن تعطيك فكرة عن الشكل الذي سيبدو عليه عرض العمل.

خريطة

function(doc) 
{ 
    if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
        emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
    } 
}

هذا يعطيني قائمة "التذاكر" المتاحة حسب مفتاح المنتج.يمكنني الحصول على مجموعة من هذه الأشياء عندما يريد شخص ما شراء مطرقة، ثم التكرار من خلال إرسال التحديثات (باستخدام id و _rev) حتى أتمكن من المطالبة بواحدة بنجاح (ستؤدي التذاكر التي تمت المطالبة بها مسبقًا إلى حدوث خطأ في التحديث).

يقلل

function (keys, values, combine) {
    return values.length;
}

تقوم دالة التخفيض هذه ببساطة بإرجاع العدد الإجمالي للأشياء التي لم تتم المطالبة بها inventory_ticket العناصر، حتى تتمكن من معرفة عدد "المطارق" المتاحة للشراء.

تحفظات

يمثل هذا الحل ما يقرب من 3.5 دقيقة من التفكير الإجمالي للمشكلة المحددة التي قدمتها.قد تكون هناك طرق أفضل للقيام بذلك!ومع ذلك، فهو يقلل بشكل كبير من التحديثات المتعارضة، ويقلل من الحاجة إلى الاستجابة للتعارض بتحديث جديد.بموجب هذا النموذج، لن يكون لديك عدة مستخدمين يحاولون تغيير البيانات في إدخال المنتج الأساسي.في أسوأ الأحوال، سيكون لديك عدة مستخدمين يحاولون المطالبة بتذكرة واحدة، وإذا حصلت على العديد منها من وجهة نظرك، فما عليك سوى الانتقال إلى التذكرة التالية والمحاولة مرة أخرى.

مرجع: https://wiki.Apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F

نصائح أخرى

التوسع في إجابة MrKurt.بالنسبة للكثير من السيناريوهات، لا تحتاج إلى استرداد تذاكر الأسهم بالترتيب.بدلاً من اختيار التذكرة الأولى، يمكنك الاختيار عشوائياً من التذاكر المتبقية.نظرًا لوجود عدد كبير من التذاكر وعدد كبير من الطلبات المتزامنة، فسوف تحصل على منافسة أقل بكثير على تلك التذاكر، مقابل كل من يحاول الحصول على التذكرة الأولى.

نمط التصميم للمعاملات المريحة هو خلق "توتر" في النظام.بالنسبة لمثال الاستخدام الشائع لمعاملة حساب مصرفي، يجب عليك التأكد من تحديث الإجمالي لكلا الحسابين المعنيين:

  • إنشاء مستند معاملة "تحويل 10 دولارات من الحساب 11223 إلى الحساب 88733".وهذا يخلق التوتر في النظام.
  • لحل أي مشكلة في فحص التوتر لجميع مستندات المعاملات و
    • إذا لم يتم تحديث حساب المصدر بعد، قم بتحديث حساب المصدر (-10 دولار أمريكي)
    • إذا تم تحديث الحساب المصدر ولكن مستند المعاملة لا يظهر ذلك، فقم بتحديث مستند المعاملة (على سبيل المثال.تعيين علامة "sourcedone" في المستند)
    • إذا لم يتم تحديث الحساب المستهدف بعد، قم بتحديث الحساب المستهدف (+10 دولار أمريكي)
    • إذا تم تحديث الحساب المستهدف ولكن مستند المعاملة لا يظهر ذلك، فقم بتحديث مستند المعاملة
    • إذا تم تحديث كلا الحسابين، فيمكنك حذف مستند المعاملة أو الاحتفاظ به للتدقيق.

يجب إجراء المسح بحثًا عن التوتر في عملية خلفية لجميع "مستندات التوتر" للحفاظ على أوقات التوتر في النظام قصيرة.في المثال أعلاه، سيكون هناك عدم اتساق متوقع لفترة قصيرة عندما يتم تحديث الحساب الأول ولكن لم يتم تحديث الحساب الثاني بعد.يجب أن يؤخذ ذلك في الاعتبار بنفس الطريقة التي ستتعامل بها مع الاتساق النهائي إذا تم توزيع Couchdb الخاص بك.

تطبيق آخر محتمل يتجنب الحاجة إلى المعاملات تمامًا:ما عليك سوى تخزين مستندات التوتر وتقييم حالة نظامك من خلال تقييم كل مستند توتر معني.في المثال أعلاه، قد يعني هذا أن إجمالي الحساب يتم تحديده فقط كمجموع القيم في مستندات المعاملة حيث يكون هذا الحساب متضمنًا.في Couchdb يمكنك تصميم هذا بشكل جيد جدًا كعرض خريطة/تصغير.

لا، CouchDB ليس مناسبًا بشكل عام لتطبيقات المعاملات لأنه لا يدعم العمليات الذرية في بيئة مجمعة/مكررة.

ضحى CouchDB بالقدرة على المعاملات لصالح قابلية التوسع.لكي يكون لديك عمليات ذرية، فأنت بحاجة إلى نظام تنسيق مركزي، مما يحد من قابلية التوسع لديك.

إذا كان بإمكانك ضمان أن لديك مثيل CouchDB واحد فقط أو أن كل شخص يقوم بتعديل مستند معين يتصل بنفس مثيل CouchDB، فيمكنك استخدام نظام اكتشاف التعارض لإنشاء نوع من الذرية باستخدام الطرق الموضحة أعلاه ولكن إذا قمت لاحقًا بالارتقاء إلى مجموعة أو استخدم خدمة مستضافة مثل Cloudant، فسوف يتعطل وسيتعين عليك إعادة هذا الجزء من النظام.

لذا، اقتراحي هو استخدام شيء آخر غير CouchDB لأرصدة حسابك، وسيكون الأمر أسهل بكثير بهذه الطريقة.

كرد فعل على مشكلة OP، ربما لا يكون Couch هو الخيار الأفضل هنا.يعد استخدام طرق العرض طريقة رائعة لتتبع المخزون، ولكن التثبيت على 0 أمر مستحيل إلى حد ما.تكمن المشكلة في حالة السباق عندما تقرأ نتيجة العرض، وتقرر أنك موافق على استخدام عنصر "المطرقة 1"، ثم تكتب مستندًا لاستخدامه.المشكلة هي أنه لا توجد طريقة ذرية لكتابة المستند لاستخدام المطرقة فقط إذا كانت نتيجة العرض هي أن هناك> 0 مطرقة -1.إذا قام 100 مستخدم بالاستعلام عن العرض في نفس الوقت وشاهدوا 1 Hammer-1، فيمكنهم جميعًا كتابة مستند لاستخدام Hammer 1، مما يؤدي إلى -99 Hammer-1.من الناحية العملية، ستكون حالة السباق صغيرة إلى حد ما - صغيرة جدًا إذا كانت قاعدة بياناتك تقوم بتشغيل مضيف محلي.ولكن بمجرد التوسع، والحصول على خادم أو مجموعة قاعدة بيانات خارج الموقع، ستصبح المشكلة أكثر وضوحًا.بغض النظر، من غير المقبول أن تكون هناك حالة سباق من هذا النوع في نظام حرج يتعلق بالمال.

تحديث لاستجابة MrKurt (ربما يكون قديمًا، أو ربما لم يكن على علم ببعض ميزات CouchDB)

يعد العرض طريقة جيدة للتعامل مع أشياء مثل الأرصدة/المخزونات في CouchDB.

لا تحتاج إلى إصدار المستند والمراجعة في طريقة العرض.يمكنك الحصول على كلاهما مجانًا عند استرداد نتائج العرض.سيؤدي إصدارها - خاصة بتنسيق مطول مثل القاموس - إلى زيادة حجم العرض لديك بشكل غير ضروري.

يجب أن تبدو طريقة العرض البسيطة لتتبع أرصدة المخزون بهذا الشكل (أيضًا من أعلى رأسي)

function( doc )
{
    if( doc.InventoryChange != undefined ) {
        for( product_key in doc.InventoryChange ) {
            emit( product_key, 1 );
        }
    }
}

ووظيفة التخفيض أكثر بساطة

_sum

يستخدم هذا أ وظيفة تقليل مدمجة يقوم فقط بجمع قيم جميع الصفوف ذات المفاتيح المطابقة.

في طريقة العرض هذه، يمكن أن يحتوي أي مستند على عضو "InventoryChange" الذي يقوم بتعيين مفتاح_المنتج لتغيير في المخزون الإجمالي لها.أي.

{
    "_id": "abc123",
    "InventoryChange": {
         "hammer_1234": 10,
         "saw_4321": 25
     }
}

سيضيف 10 مطرقة 1234 و 25 منشارًا 4321.

{
    "_id": "def456",
    "InventoryChange": {
        "hammer_1234": -5
    }
}

سوف يحرق 5 مطارق من المخزون.

باستخدام هذا النموذج، لن تقوم مطلقًا بتحديث أي بيانات، بل تقوم فقط بالإلحاق.هذا يعني أنه لا توجد فرصة لحدوث تعارضات في التحديث.تختفي جميع مشكلات المعاملات المتعلقة بتحديث البيانات :)

شيء جميل آخر في هذا النموذج هو أن أي مستند في قاعدة البيانات يمكنه إضافة وطرح عناصر من المخزون.يمكن أن تحتوي هذه المستندات على جميع أنواع البيانات الأخرى.قد يكون لديك مستند "شحنة" يحتوي على مجموعة من البيانات حول تاريخ ووقت الاستلام والمستودع والموظف المستلم وما إلى ذلك.وطالما أن هذا المستند يحدد InventoryChange، فسوف يقوم بتحديث المخزون.كما هو الحال مع مستند "البيع" ومستند "العنصر التالف" وما إلى ذلك.عند النظر إلى كل وثيقة، يقرأون بوضوح شديد.والمنظر يتعامل مع كل العمل الشاق.

في الواقع، يمكنك بطريقة ما.الق نظرة على واجهة برمجة تطبيقات مستند HTTP ثم قم بالتمرير لأسفل إلى العنوان "تعديل مستندات متعددة بطلب واحد".

يمكنك بشكل أساسي إنشاء/تحديث/حذف مجموعة من المستندات في طلب نشر واحد URI /{dbname}/_bulk_docs وسوف ينجحون جميعًا أو يفشلون جميعًا.ومع ذلك، تحذر الوثيقة من أن هذا السلوك قد يتغير في المستقبل.

يحرر:كما كان متوقعًا، بدءًا من الإصدار 0.9، لم تعد المستندات المجمعة تعمل بهذه الطريقة.

ما عليك سوى استخدام نوع SQlite من الحلول خفيفة الوزن للمعاملات، وعند اكتمال المعاملة، قم بتكرارها بنجاح، ووضع علامة عليها منسوخة في SQLite

جدول سكليتي

txn_id    , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1   x                    y               added/replicated

يمكنك أيضًا حذف المعاملات التي تم نسخها بنجاح.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top