2009-07-07 20 views
17

कृपया शब्दावली में किसी भी गलती से क्षमा करें। विशेष रूप से, मैं संबंधपरक डेटाबेस शर्तों का उपयोग कर रहा हूँ।कुंजी-मूल्य स्टोर में परमाणु लेनदेन

कई अन्य परियोजनाओं के साथ CouchDB और Cassandra सहित कई लगातार कुंजी-मूल्य स्टोर हैं।

उनके खिलाफ एक आम तर्क यह है कि वे आम तौर पर एकाधिक पंक्तियों या तालिकाओं में परमाणु लेनदेन की अनुमति नहीं देते हैं। मुझे आश्चर्य है कि क्या कोई सामान्य दृष्टिकोण इस मुद्दे को हल करेगा।

उदाहरण के लिए बैंक खातों के एक सेट की स्थिति लें। हम एक बैंक खाते से दूसरे पैसे कैसे लेते हैं? यदि प्रत्येक बैंक खाता एक पंक्ति है, तो हम एक ही लेनदेन के हिस्से के रूप में दो पंक्तियों को अपडेट करना चाहते हैं, एक में मूल्य को कम करना और दूसरे में मूल्य बढ़ाना चाहते हैं।

एक स्पष्ट दृष्टिकोण है कि लेनदेन का वर्णन करने वाली एक अलग तालिका हो। फिर, एक बैंक खाते से दूसरे पैसे में पैसे लेना इस तालिका में बस एक नई पंक्ति डालने के होते हैं। हम दोनों बैंक खातों में से किसी एक के मौजूदा शेष राशि को स्टोर नहीं करते हैं और इसके बजाय लेनदेन तालिका में सभी उचित पंक्तियों को संक्षेप में भरोसा करते हैं। कल्पना करना आसान है कि यह बहुत अधिक काम होगा, हालांकि; एक बैंक में एक दिन में लाखों लेन-देन हो सकते हैं और एक व्यक्तिगत बैंक खाते में इसके साथ जुड़े कई हजार लेनदेन हो सकते हैं।

कुंजी-मूल्य स्टोरों की एक संख्या (सभी?) एक क्रिया को वापस ले जाएगी यदि अंतर्निहित डेटा बदल गया है क्योंकि आपने इसे अंतिम बार पकड़ लिया है। संभवतः इसका उपयोग परमाणु लेनदेन अनुकरण करने के लिए किया जा सकता है, क्योंकि आप तब संकेत दे सकते हैं कि एक विशेष फ़ील्ड लॉक है। इस दृष्टिकोण के साथ कुछ स्पष्ट मुद्दे हैं।

कोई अन्य विचार? यह पूरी तरह से संभव है कि मेरा दृष्टिकोण बस गलत है और मैंने अभी तक अपने दिमाग को सोचने के नए तरीके से लपेटा नहीं है।

+0

CouchDB, की-वैल्यू नहीं है, यह एक दस्तावेज की दुकान है। – OrangeDog

उत्तर

10

यदि आपका उदाहरण लेना है, तो आप एकल दस्तावेज़ (संबंधपरक शब्दावली में पंक्ति) में मूल्य को मूल रूप से अपडेट करना चाहते हैं, तो आप इसे CouchDB में कर सकते हैं। जब आप परिवर्तन करने का प्रयास करते हैं तो आपको एक संघर्ष त्रुटि मिल जाएगी यदि किसी अन्य प्रतिस्पर्धी ग्राहक ने इसे पढ़ने के बाद उसी दस्तावेज़ को अपडेट किया है। इसके बाद आपको नया मान पढ़ना होगा, अपडेट करें और फिर से प्रयास करें। एक अनिश्चित (संभावित रूप से अनंत है यदि बहुत विवाद की संख्या है) आपको इस प्रक्रिया को दोहराने के लिए कितनी बार हो सकता है, लेकिन यदि आपकी प्रतिबद्धता कभी सफल हो जाती है तो आपको डेटाबेस में एक असामान्य रूप से अपडेट की गई शेष राशि के साथ एक दस्तावेज़ होने की गारंटी है।

यदि आपको दो शेष (यानी एक खाते से दूसरे खाते में स्थानांतरण) अपडेट करने की आवश्यकता है, तो आपको एक अलग लेनदेन दस्तावेज़ (प्रभावी ढंग से एक और तालिका जहां पंक्तियां लेनदेन होती हैं) का उपयोग करने की आवश्यकता होती है जो राशि और दो खातों को संग्रहीत करती है (अंदर और बाहर)। यह एक आम बहीखाता अभ्यास है, वैसे भी। चूंकि कॉच डीबी केवल आवश्यकतानुसार विचारों की गणना करता है, इसलिए वास्तव में अभी भी उस खाते की सूची वाले लेनदेन से खाते में वर्तमान राशि की गणना करने के लिए बहुत ही कुशल है। कॉच डीबी में, आप एक नक्शा फ़ंक्शन का उपयोग करेंगे जो खाता संख्या को कुंजी और लेनदेन की राशि (आने वाले के लिए सकारात्मक, आउटगोइंग के लिए नकारात्मक) के रूप में उत्सर्जित करता है। आपका कम फ़ंक्शन केवल एक ही कुंजी और कुल योग को उत्सर्जित करने, प्रत्येक कुंजी के मानों को जोड़ देगा। इसके बाद आप खाता संख्या द्वारा की गई खाता शेष राशि प्राप्त करने के लिए समूह = ट्रू के साथ एक दृश्य का उपयोग कर सकते हैं।

+0

इसे समझाने के लिए धन्यवाद। आप कहते हैं कि यह समूह करने के लिए "अभी भी बहुत ही कुशल" है। क्या आप इस पर थोड़ा सा विस्तार कर सकते हैं? उच्च-यातायात संबंधपरक डेटाबेस के लिए, कॉलम को कम करने के लिए यह सामान्य प्रथा है। मैं कल्पना कर सकता हूं कि कॉच डीबी और अन्य डेटा को काफी अलग तरीके से स्टोर करते हैं और इसका मतलब है कि लेनदेन का समूह अधिक कुशल हो सकता है। लेकिन क्या आप समूह में 10 लेनदेन के साथ ऐसा करेंगे? 100? 100,000? – ChrisInEdmonton

+0

CouchDB डेटाबेस में दस्तावेज़ देखने के लिए मानचित्र/घटाव प्रतिमान का उपयोग करता है। चूंकि नक्शा केवल बदले गए दस्तावेज़ों पर लागू होता है, इसलिए इसकी (समय) दक्षता दस्तावेजों की कुल संख्या में अनिवार्य रूप से ओ (1) है, लेकिन ओ (एन) बदले गए दस्तावेज़ों की संख्या में है। कम मूल्यों की गणना बी-पेड़ में की जाती है और संग्रहीत की जाती है। स्पष्ट रूप से सभी नोड्स जिनके पास एक बाल दस्तावेज़ है जो बदले गए हैं उन्हें पुन: संकुचित करने की आवश्यकता होगी। इस प्रकार यह कमी को चलाने के लिए अधिक समय ले सकता है। कॉच डीबी को लाखों दस्तावेजों के साथ उत्पादन में प्रदर्शित किया गया है, इसलिए मुझे नहीं लगता कि इस मामले में यह एक मुद्दा होगा। –

+0

धन्यवाद। वैसे, मैं एक सोशल नेटवर्क साइट के लिए काम करता हूं। हम मध्यम भविष्य में लगातार कुंजी-मूल्य स्टोर में स्विच करने की योजना नहीं बना रहे हैं। हम निश्चित रूप से sharded MySQL डेटाबेस सर्वर और memcache का उपयोग करें। ऐसा लगता है कि हमारी व्यस्त तालिकाओं में सैकड़ों लाखों पंक्तियां देखी गई हैं लेकिन इससे परे कुछ भी नहीं है। आपके उत्तरों से, ऐसा लगता है कि कम से कम, कॉच डीबी, विशेष रूप से उन मुद्दों के संचालन के लिए डिज़ाइन किया गया है जो मुझे उम्मीद है कि हमारी तरह की साइट के लिए आएगा। बहुत आश्चर्यजनक नहीं है लेकिन सुनने के लिए अभी भी अच्छा है। मुझे यकीन है कि कॉच डीबी और अन्य कुछ चीजें बेहतर करेंगे और कभी-कभी चीज खराब होती है। – ChrisInEdmonton

5

कॉच डीबी लेनदेन प्रणाली के लिए उपयुक्त नहीं है क्योंकि यह लॉकिंग और परमाणु संचालन का समर्थन नहीं करता है।

एक बैंक हस्तांतरण आप कुछ बातें करना चाहिए पूरा करने के लिए: कि दोनों खातों खुला, बंद नहीं कर रहे हैं यह सुनिश्चित करने के स्रोत खाते में पर्याप्त धनराशि है,

  1. मान्य लेन-देन, और अच्छे में खड़ा है, और इतने
  2. पर स्रोत खाता
  3. का संतुलन घटाएँ गंतव्य खाते की शेष राशि को बढ़ाने के

से कोई भी परिवर्तन के बीच में बना रहे हैं, तो इन चरणों में खातों की शेष राशि या स्थिति, लेनदेन जमा होने के बाद अमान्य हो सकता है जो इस तरह की प्रणाली में एक बड़ी समस्या है।

भले ही आप दृष्टिकोण है जहाँ आप एक "स्थानांतरण" रिकॉर्ड सम्मिलित और एक मानचित्र का उपयोग ऊपर सुझाव का उपयोग/खाते की रकम की गणना करने के दृश्य को कम करने, आप सुनिश्चित करना है कि आप स्रोत खाते क्योंकि अधिक निकालना नहीं है का कोई तरीका नहीं स्रोत खाते की शेष राशि की जांच करने और लेनदेन को सम्मिलित करने के बीच अभी भी एक रेस स्थिति है जहां संतुलन की जांच के बाद दो लेनदेन एक साथ जोड़े जा सकते हैं।

तो ... यह नौकरी के लिए गलत उपकरण है। कॉच डीबी शायद बहुत सी चीजों पर अच्छा है, लेकिन यह ऐसा कुछ है जो वास्तव में नहीं कर सकता है।

संपादित करें: शायद यह ध्यान देने योग्य है कि वास्तविक दुनिया में वास्तविक बैंक अंतिम स्थिरता का उपयोग करते हैं। यदि आप लंबे समय तक अपने बैंक खाते को ओवरड्रा करते हैं तो आपको ओवरड्राफ्ट शुल्क मिलता है। यदि आप बहुत अच्छे थे तो आप लगभग एक ही समय में दो अलग-अलग एटीएम से पैसे वापस लेने में सक्षम हो सकते हैं और अपने खाते को ओवरड्रा कर सकते हैं क्योंकि शेष राशि की जांच करने, पैसे जारी करने और लेनदेन रिकॉर्ड करने के लिए दौड़ की स्थिति है। जब आप अपने खाते में चेक जमा करते हैं तो वे शेष राशि को टक्कर देते हैं लेकिन असल में उन फंडों को "समय के मामले में" रखते हैं, स्रोत खाते में वास्तव में पर्याप्त धन नहीं होता है।

+1

यह दानव रूप से झूठा है: https://gist.github.com/wolever/1940301d4f7f530c0791 - यह बस * अलग * (यद्यपि काफी जटिल) यद्यपि लेनदेन मॉडल का उपयोग करता है। जबकि "बैंक हस्तांतरण" प्रकार के अनुप्रयोग पर परमाणु संचालन की आवश्यकता होती है (जो सोफे में है: संस्करण संस्करण के साथ दस्तावेज़ अपडेट), लॉकिंग की आवश्यकता नहीं है। –

2

एक ठोस उदाहरण प्रदान करने के लिए (सही उदाहरण के एक आश्चर्य की बात कमी ऑनलाइन न होने के कारण): यहाँ कैसे लागू करने के लिए एक "atomic bank balance transfer" CouchDB (मोटे तौर पर एक ही विषय पर अपने ब्लॉग पोस्ट से नकल: http://blog.codekills.net/2014/03/13/atomic-bank-balance-transfer-with-couchdb/) में

सबसे पहले, समस्या का संक्षिप्त विवरण: बैंकिंग सिस्टम कैसे खातों को खातों के बीच स्थानांतरित करने की अनुमति देता है ताकि डिज़ाइन किया जा सके ताकि ऐसी स्थितियां न हों जो अमान्य या गैरकानूनी शेष छोड़ सकें?

पहले:: लेन-देन लॉग

इस समस्या का कुछ भागों रहे हैं। एक खाते की शेष राशि को रिकॉर्ड या दस्तावेज़ में संग्रहीत करने के बजाय - {"account": "Dave", "balance": 100} - खाते का शेष राशि उस खाते में सभी क्रेडिट और डेबिट को सारांशित करके गणना की जाती है। ये क्रेडिट और डेबिट एक सौदे लॉग, जो कुछ ऐसी दिखाई दे सकती में जमा हो जाती:

{"from": "Dave", "to": "Alex", "amount": 50} 
{"from": "Alex", "to": "Jane", "amount": 25} 

और CouchDB नक्शा को कम कार्यों शेष राशि की गणना कुछ ऐसा दिखाई दे सकता:

POST /transactions/balances 
{ 
    "map": function(txn) { 
     emit(txn.from, txn.amount * -1); 
     emit(txn.to, txn.amount); 
    }, 
    "reduce": function(keys, values) { 
     return sum(values); 
    } 
} 

GET /transactions/balances 
{ 
    "rows": [ 
     { 
      "key" : "Alex", 
      "value" : 25 
     }, 
     { 
      "key" : "Dave", 
      "value" : -50 
     }, 
     { 
      "key" : "Jane", 
      "value" : 25 
     } 
    ], 
    ... 
} 

लेकिन इस छुट्टी:

पूर्णता के लिए, यहाँ की शेष राशि की सूची है स्पष्ट प्रश्न है: त्रुटियों को कैसे नियंत्रित किया जाता है? क्या होता है यदि कोई अपनी शेष राशि से अधिक स्थानांतरण करने का प्रयास करता है?

कॉच डीबी (और इसी तरह के डेटाबेस) के साथ इस प्रकार के व्यावसायिक तर्क और त्रुटि हैंडलिंग आवेदन स्तर पर लागू की जानी चाहिए। भोलेपन से, इस तरह के एक समारोह इस प्रकार दिखाई देंगे:

def transfer(from_acct, to_acct, amount): 
    txn_id = db.post("transactions", {"from": from_acct, "to": to_acct, "amount": amount}) 
    if db.get("transactions/balances") < 0: 
     db.delete("transactions/" + txn_id) 
     raise InsufficientFunds() 

लेकिन लगता है कि लेन-देन डालने और अद्यतन शेष राशि डेटाबेस एक असंगत राज्य में छोड़ दिया जाएगा जाँच के बीच अनुप्रयोग क्रैश हो जाता है, तो: इस किया जा सकता है

// Initial balances: Alex: 25, Jane: 25 
db.post("transactions", {"from": "Alex", "To": "Jane", "amount": 50} 
// Current balances: Alex: -25, Jane: 75 

यह कैसे तय किया जा सकता है: एक नकारात्मक शेष है, और पैसा है कि पहले नहीं था मौजूद साथ प्राप्तकर्ता के साथ छोड़ दिया?

  1. समय लेनदेन बनाया गया था (यह सुनिश्चित करने के है कि वहाँ एक strict total ordering:

    यकीन है कि प्रणाली एक असंगत स्थिति में नहीं है बनाने के लिए, जानकारी के दो टुकड़े प्रत्येक लेन-देन के लिए जोड़ा जा करने की जरूरत है लेनदेन का), और

  2. एक स्थिति - लेनदेन सफल हुआ या नहीं।

वहाँ भी दो बार देखा होने की आवश्यकता होगी - एक जो किसी खाते का उपलब्ध संतुलन (यानी, सब "सफल" लेन-देन का योग) देता है, और एक अन्य जो रिटर्न सबसे पुराने "लंबित" लेन-देन:

POST /transactions/balance-available 
{ 
    "map": function(txn) { 
     if (txn.status == "successful") { 
      emit(txn.from, txn.amount * -1); 
      emit(txn.to, txn.amount); 
     } 
    }, 
    "reduce": function(keys, values) { 
     return sum(values); 
    } 
} 

POST /transactions/oldest-pending 
{ 
    "map": function(txn) { 
     if (txn.status == "pending") { 
      emit(txn._id, txn); 
     } 
    }, 
    "reduce": function(keys, values) { 
     var oldest = values[0]; 
     values.forEach(function(txn) { 
      if (txn.timestamp < oldest) { 
       oldest = txn; 
      } 
     }); 
     return oldest; 
    } 

} 

स्थानान्तरण की सूची अब कुछ इस तरह दिख सकता है:

{"from": "Alex", "to": "Dave", "amount": 100, "timestamp": 50, "status": "successful"} 
{"from": "Dave", "to": "Jane", "amount": 200, "timestamp": 60, "status": "pending"} 

इसके बाद, आवेदन की आवश्यकता होगी सत्यापित करने के लिए एक समारोह जो क्रम में प्रत्येक लंबित लेनदेन की जाँच करके लेनदेन हल कर सकते हैं कि यह मान्य है, तो इसकी स्थिति को अपडेट करने से "लंबित" या तो "सफल" या करने के लिए "को अस्वीकार कर दिया":

def resolve_transactions(target_timestamp): 
    """ Resolves all transactions up to and including the transaction 
     with timestamp `target_timestamp`. """ 
    while True: 
     # Get the oldest transaction which is still pending 
     txn = db.get("transactions/oldest-pending") 
     if txn.timestamp > target_timestamp: 
      # Stop once all of the transactions up until the one we're 
      # interested in have been resolved. 
      break 

     # Then check to see if that transaction is valid 
     if db.get("transactions/available-balance", id=txn.from) >= txn.amount: 
      status = "successful" 
     else: 
      status = "rejected" 

     # Then update the status of that transaction. Note that CouchDB 
     # will check the "_rev" field, only performing the update if the 
     # transaction hasn't already been updated. 
     txn.status = status 
     couch.put(txn) 

अंत में, के लिए सही ढंग से आवेदन कोड एक हस्तांतरण प्रदर्शन:

def transfer(from_acct, to_acct, amount): 
    timestamp = time.time() 
    txn = db.post("transactions", { 
     "from": from_acct, 
     "to": to_acct, 
     "amount": amount, 
     "status": "pending", 
     "timestamp": timestamp, 
    }) 
    resolve_transactions(timestamp) 
    txn = couch.get("transactions/" + txn._id) 
    if txn_status == "rejected": 
     raise InsufficientFunds() 

कुछ नोट:

  • ब्रेवटी के लिए, यह विशिष्ट कार्यान्वयन कुछ हद तक कोचडीबी के मानचित्र-कम करने में परमाणुता मानता है। कोड अपडेट कर रहा है, इसलिए यह पर निर्भर नहीं है कि धारणा पाठक के लिए एक अभ्यास के रूप में छोड़ी गई है।

  • मास्टर/मास्टर प्रतिकृति या कॉच डीबी के दस्तावेज़ सिंक को विचार में नहीं लिया गया है। मास्टर/मास्टर प्रतिकृति और सिंक इस समस्या को को और अधिक कठिन बनाते हैं।

  • वास्तविक प्रणाली में, time() का उपयोग करके टकराव हो सकता है, इसलिए का उपयोग थोड़ा और एन्ट्रॉपी के साथ कुछ अच्छा विचार हो सकता है; शायद "%s-%s" %(time(), uuid()), या ऑर्डरिंग में दस्तावेज़ के _id का उपयोग कर। समय सहित सख्ती से जरूरी नहीं है, लेकिन यह एक लॉजिकल को बनाए रखने में मदद करता है यदि एकाधिक अनुरोध एक ही समय में आते हैं।

1

बर्कले डीबी और एलएमडीबी एसीआईडी ​​लेनदेन के लिए समर्थन के साथ दोनों प्रमुख मूल्य वाले स्टोर हैं। बीडीबी टीएक्सएन में वैकल्पिक हैं जबकि एलएमडीबी केवल लेनदेन संचालित करता है।

1

उनके खिलाफ एक सामान्य तर्क यह है कि वे आम तौर पर एकाधिक पंक्तियों या तालिकाओं में परमाणु लेनदेन की अनुमति नहीं देते हैं। मुझे आश्चर्य है कि क्या कोई सामान्य दृष्टिकोण इस मुद्दे को हल करेगा।

बहुत सारे आधुनिक डेटा स्टोर बॉक्स के बाहर परमाणु बहु-कुंजी अपडेट (लेनदेन) का समर्थन नहीं करते हैं, लेकिन उनमें से अधिकतर प्राइमेटिव प्रदान करते हैं जो आपको एसीआईडी ​​क्लाइंट-साइड लेनदेन बनाने की अनुमति देते हैं।

यदि कोई डेटा स्टोर प्रति कुंजी रैखिकता और तुलना-और-स्वैप या परीक्षण-और-सेट ऑपरेशन का समर्थन करता है तो यह धारावाहिक लेनदेन को लागू करने के लिए पर्याप्त है। उदाहरण के लिए, इस दृष्टिकोण का उपयोग Google's Percolator और CockroachDB डेटाबेस में किया जाता है।

मेरे ब्लॉग में मैंने step-by-step visualization of serializable cross shard client-side transactions बनाया, प्रमुख उपयोग मामलों का वर्णन किया और एल्गोरिदम के रूपों के लिंक प्रदान किए। मुझे उम्मीद है कि यह आपको समझने में मदद करेगा कि उन्हें डेटा स्टोर के लिए कैसे कार्यान्वित किया जाए।

जो कुंजी linearizability और कैस प्रति समर्थन डेटा भंडार में से हैं: हल्के लेनदेन के साथ

  • कैसेंड्रा
  • Riak संगत बाल्टी के साथ
  • RethinkDB
  • चिड़ियाघर संचालक
  • Etdc
  • HBase
  • डायनेमो डीबी
  • MongoDB

वैसे, अगर आप पढ़ें प्रतिबद्ध अलगाव स्तर के साथ ठीक हो तो यह समझ में आता है पीटर Bailis द्वारा RAMP transactions पर एक नज़र डालना। उन्हें डेटा स्टोर्स के उसी सेट के लिए भी कार्यान्वित किया जा सकता है।

संबंधित मुद्दे