7.1 यह क्यों आवश्यक है

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

मैं आपको लेन-देन प्रबंधक बनाने के बारे में एक विस्तृत मार्गदर्शिका नहीं देना चाहता, केवल इसलिए कि यह बहुत बड़ा और जटिल है, और मैं केवल कुछ बुनियादी तकनीकों को शामिल करना चाहता हूं। यदि हम वितरित अनुप्रयोगों के बारे में बात नहीं कर रहे हैं, तो मुझे एसीआईडी ​​​​गारंटियों की आवश्यकता होने पर आवेदन पक्ष पर पूरी तरह से एसीआईडी ​​​​को लागू करने का प्रयास करने का कोई कारण नहीं दिखता है - आखिरकार, तैयार समाधान लेने के लिए हर मायने में यह आसान और सस्ता होगा ( यानी, ACID वाला एक डेटाबेस)।

लेकिन मैं आपको कुछ तकनीकें दिखाना चाहता हूं जो आपको आवेदन पक्ष पर लेनदेन करने में मदद करेंगी। आखिरकार, इन तकनीकों को जानने से आपको विभिन्न प्रकार के परिदृश्यों में मदद मिल सकती है, यहां तक ​​​​कि उन मामलों में भी जो लेन-देन को शामिल नहीं करते हैं, और आपको एक बेहतर डेवलपर बनाते हैं (मुझे उम्मीद है)।

7.2 लेन-देन प्रेमियों के लिए बुनियादी उपकरण

आशावादी और निराशावादी अवरोधन। कुछ डेटा पर ये दो प्रकार के लॉक होते हैं जिन्हें एक ही समय में एक्सेस किया जा सकता है।

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

एक उदाहरण के रूप में, मुझे एक डेवलपर के दैनिक जीवन से एक तकनीक मिली जो आशावादी लॉकिंग जैसी किसी चीज़ का उपयोग करती है - यह HTTP प्रोटोकॉल है। आरंभिक HTTP GET अनुरोध की प्रतिक्रिया में क्लाइंट के बाद के PUT अनुरोधों के लिए एक ETag हेडर शामिल हो सकता है, जिसे क्लाइंट इफ-मैच हेडर में उपयोग कर सकता है। GET और HEAD विधियों के लिए, सर्वर अनुरोधित संसाधन को केवल तभी वापस भेजेगा जब वह किसी एक Etag से मेल खाता है जिसे वह जानता है। पुट और अन्य असुरक्षित तरीकों के लिए, यह केवल इस मामले में भी संसाधन लोड करेगा। यदि आप नहीं जानते कि ETag कैसे काम करता है, तो यहां "फ़ीडपार्सर" लाइब्रेरी (जो RSS और अन्य फ़ीड को पार्स करने में मदद करता है) का उपयोग करके एक अच्छा उदाहरण दिया गया है।


>>> import feedparser 
>>> d = feedparser.parse('http://feedparser.org/docs/examples/atom10.xml') 
>>> d.etag 
'"6c132-941-ad7e3080"' 
>>> d2 = feedparser.parse('http://feedparser.org/docs/examples/atom10.xml', etag=d.etag) 
>>> d2.feed 
{} 
>>> d2.debug_message 
'The feed has not changed since you last checked, so the server sent no data.  This is a feature, not a bug!' 

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

एक अतिरिक्त खतरा, हालांकि, संभावित गतिरोध में दुबक जाता है, जिसमें कई प्रक्रियाएँ एक दूसरे द्वारा लॉक किए गए संसाधनों की प्रतीक्षा करती हैं। उदाहरण के लिए, एक लेन-देन के लिए संसाधनों A और B की आवश्यकता होती है। प्रक्रिया 1 ने संसाधन A पर कब्जा कर लिया है, और प्रक्रिया 2 ने संसाधन B पर कब्जा कर लिया है। दोनों में से कोई भी प्रक्रिया निष्पादन जारी नहीं रख सकती है। इस मुद्दे को हल करने के कई तरीके हैं - मैं अब विवरण में नहीं जाना चाहता, इसलिए पहले विकिपीडिया पढ़ें, लेकिन संक्षेप में, लॉक पदानुक्रम बनाने की संभावना है। यदि आप इस अवधारणा को और अधिक विस्तार से जानना चाहते हैं, तो आपको "डाइनिंग फिलॉसॉफर्स प्रॉब्लम" ("डाइनिंग फिलॉसॉफर्स प्रॉब्लम") पर अपने दिमाग को रैक करने के लिए आमंत्रित किया जाता है।

यहाँ एक अच्छा उदाहरण है कि एक ही परिदृश्य में दोनों तालों का व्यवहार कैसा होगा।

ताले के कार्यान्वयन के संबंध में। मैं विवरण में नहीं जाना चाहता, लेकिन वितरित सिस्टम के लिए लॉक मैनेजर हैं, उदाहरण के लिए: ज़ूकीपर, रेडिस, आदि, कंसुल।

7.3 संचालन की अक्षमता

Idempotent कोड आम तौर पर एक अच्छा अभ्यास है, और ठीक यही स्थिति है जब एक डेवलपर के लिए ऐसा करने में सक्षम होना अच्छा होगा, भले ही वह लेन-देन का उपयोग करता हो या नहीं। निष्क्रियता एक ऑपरेशन की संपत्ति है जो उसी परिणाम का उत्पादन करती है जब उस ऑपरेशन को फिर से किसी वस्तु पर लागू किया जाता है। समारोह बुलाया गया - परिणाम दिया। एक या पाँच सेकंड के बाद फिर से कॉल किया - वही परिणाम दिया। बेशक, अगर डेटाबेस में डेटा बदल गया है, तो परिणाम अलग होगा। तीसरी प्रणाली में डेटा एक फ़ंक्शन पर निर्भर नहीं हो सकता है, लेकिन जो कुछ भी करता है वह अनुमानित होना चाहिए।

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

यह एक शुद्ध कार्य है:


def square(num: int) -> int: 
	return num * num 

लेकिन यह कार्य शुद्ध नहीं है, लेकिन बेवकूफ है (कृपया निष्कर्ष न निकालें कि मैं इन टुकड़ों से कोड कैसे लिखता हूं):


def insert_data(insert_query: str, db_connection: DbConnectionType) -> int: 
  db_connection.execute(insert_query) 
  return True 

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

समस्या यह है कि एसएनएस "कम से कम एक बार" ("कम से कम एक बार डिलीवरी") संदेश देता है। इसका मतलब क्या है? जल्दी या बाद में आपके लैम्ब्डा कोड को दो बार कॉल किया जाएगा। और यह सच में होता है। ऐसे कई परिदृश्य हैं जहां आपके कार्य को निष्क्रिय होने की आवश्यकता है: उदाहरण के लिए, जब किसी खाते से पैसा निकाला जाता है, तो हम उम्मीद कर सकते हैं कि कोई व्यक्ति उसी राशि को दो बार निकालेगा, लेकिन हमें यह सुनिश्चित करने की आवश्यकता है कि ये वास्तव में 2 स्वतंत्र समय हैं - दूसरे शब्दों में, ये 2 अलग-अलग लेन-देन हैं, और एक की पुनरावृत्ति नहीं है।

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

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

import json
import os
from typing import Any, Dict

from aws_lambda_powertools.utilities.typing import LambdaContext  # needed only for argument type annotation
import boto3

limit = os.getenv('LIMIT')

def handler_name(event: Dict[str: Any], context: LambdaContext):

	request_id = context.aws_request_id

	# We find user_id in incoming event
	user_id = event["user_id"]

	# Our table for DynamoDB
	table = boto3.resource('dynamodb').Table('my_table')

	# Doing update
	table.update_item(
    	Key={'pkey': user_id},
    	UpdateExpression='ADD requests :request_id',
    	ConditionExpression='attribute_not_exists (requests) OR (size(requests) < :limit AND NOT contains(requests, :request_id))',
    	ExpressionAttributeValues={
        	':request_id': {'S': request_id},
        	':requests': {'SS': [request_id]},
        	':limit': {'N': limit}
    	}
	)

	# TODO: write further logic

	return {
    	"statusCode": 200,
    	"headers": {
        	"Content-Type": "application/json"
    	},
    	"body": json.dumps({
        	"status ": "success"
    	})
	}

चूंकि मेरा उदाहरण वास्तव में इंटरनेट से लिया गया है, मैं मूल स्रोत के लिए एक लिंक छोड़ दूंगा, खासकर जब से यह थोड़ी अधिक जानकारी देता है।

याद रखें कि मैंने पहले कैसे उल्लेख किया था कि साझा किए गए डेटा को लॉक करने के लिए एक विशिष्ट लेनदेन आईडी जैसी किसी चीज़ का उपयोग किया जा सकता है? अब हम जान गए हैं कि इसका उपयोग संचालन को निष्प्रभावी बनाने के लिए भी किया जा सकता है। आइए जानें कि आप किन तरीकों से खुद ऐसी आईडी जेनरेट कर सकते हैं।