7.1 ते का आवश्यक आहे

आम्ही ACID चे सर्व गुणधर्म, त्यांचा उद्देश आणि वापर प्रकरणे याबद्दल काही तपशीलवार चर्चा केली आहे. तुम्ही बघू शकता, सर्व डेटाबेस ACID हमी देत ​​नाहीत, चांगल्या कामगिरीसाठी त्यांचा त्याग करतात. त्यामुळे, तुमच्या प्रोजेक्टमध्ये ACID न देणारा डेटाबेस निवडला गेला असेल आणि तुम्हाला ऍप्लिकेशनच्या बाजूने काही आवश्यक ACID कार्यक्षमता लागू करावी लागेल. आणि जर तुमची सिस्टीम मायक्रोसर्व्हिसेस किंवा इतर काही प्रकारचे वितरित ऍप्लिकेशन म्हणून डिझाइन केलेली असेल, तर एका सेवेमध्ये सामान्य स्थानिक व्यवहार काय असेल तो आता वितरित व्यवहार होईल - आणि अर्थातच, त्याचे ACID स्वरूप गमावेल, जरी डेटाबेस प्रत्येक वैयक्तिक मायक्रोसेवा ACID असेल.

मी तुम्हाला व्यवहार व्यवस्थापक कसा तयार करायचा याबद्दल संपूर्ण मार्गदर्शक देऊ इच्छित नाही, फक्त कारण ते खूप मोठे आणि क्लिष्ट आहे आणि मला फक्त काही मूलभूत तंत्रे समाविष्ट करायची आहेत. जर आम्ही वितरित अनुप्रयोगांबद्दल बोलत नसलो, तर तुम्हाला ACID हमींची आवश्यकता असल्यास ऍप्लिकेशनच्या बाजूने ACID पूर्णपणे अंमलात आणण्याचा प्रयत्न करण्याचे मला कोणतेही कारण दिसत नाही - शेवटी, तयार उपाय घेणे प्रत्येक अर्थाने सोपे आणि स्वस्त असेल ( म्हणजे, ACID सह डेटाबेस).

परंतु मी तुम्हाला काही तंत्रे दाखवू इच्छितो जी तुम्हाला अर्जाच्या बाजूने व्यवहार करण्यात मदत करतील. शेवटी, ही तंत्रे जाणून घेतल्याने तुम्हाला विविध परिस्थितींमध्ये मदत होऊ शकते, त्यातही व्यवहारांचा समावेश नसतो आणि तुम्हाला एक चांगला विकासक बनवू शकतो (मला आशा आहे).

7.2 व्यवहार प्रेमींसाठी मूलभूत साधने

आशावादी आणि निराशावादी ब्लॉकिंग. काही डेटावर एकाच वेळी प्रवेश करता येणारे हे दोन प्रकारचे लॉक आहेत.

आशावादीअसे गृहीत धरते की समवर्ती प्रवेशाची संभाव्यता इतकी मोठी नाही आणि म्हणून ते पुढील गोष्टी करते: इच्छित ओळ वाचते, त्याचा आवृत्ती क्रमांक लक्षात ठेवतो (किंवा टाइमस्टॅम्प, किंवा चेकसम / हॅश - जर तुम्ही डेटा स्कीमा बदलू शकत नसाल आणि आवृत्तीसाठी कॉलम जोडू शकता. किंवा टाइमस्टॅम्प), आणि या डेटासाठी डेटाबेसमध्ये बदल लिहिण्यापूर्वी, या डेटाची आवृत्ती बदलली आहे का ते तपासते. जर आवृत्ती बदलली असेल, तर तुम्हाला निर्माण झालेला संघर्ष कसा तरी सोडवावा लागेल आणि डेटा ("कमिट") अद्यतनित करावा लागेल, किंवा व्यवहार परत करा ("रोलबॅक"). या पद्धतीचा तोटा असा आहे की ते "टाईम-ऑफ-चेक टू टाईम-ऑफ-वापर" या दीर्घ नावासह बगसाठी अनुकूल परिस्थिती निर्माण करते, ज्याचे संक्षिप्त रूप TOCTOU आहे: चेक आणि लिहिण्याच्या कालावधीत स्थिती बदलू शकते. मला आशावादी लॉकिंगचा अनुभव नाही,

उदाहरण म्हणून, मला विकसकाच्या दैनंदिन जीवनातील एक तंत्रज्ञान सापडले जे आशावादी लॉकिंगसारखे काहीतरी वापरते - हा HTTP प्रोटोकॉल आहे. प्रारंभिक HTTP GET विनंतीच्या प्रतिसादात क्लायंटच्या नंतरच्या PUT विनंत्यांसाठी ETag शीर्षलेख समाविष्ट असू शकतो, जो क्लायंट If-Match हेडरमध्ये वापरू शकतो. GET आणि HEAD पद्धतींसाठी, सर्व्हर विनंती केलेले संसाधन फक्त त्याला माहीत असलेल्या ETags पैकी एकाशी जुळले तरच परत पाठवेल. PUT आणि इतर असुरक्षित पद्धतींसाठी, ते केवळ या प्रकरणात संसाधन लोड करेल. जर तुम्हाला ETag कसे कार्य करते हे माहित नसेल, तर येथे "feedparser" लायब्ररी वापरण्याचे एक चांगले उदाहरण आहे (जे 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 व्यापलेले आहे. दोन्हीपैकी कोणतीही प्रक्रिया कार्यान्वित करणे सुरू ठेवू शकत नाही. या समस्येचे निराकरण करण्याचे विविध मार्ग आहेत - मला आता तपशीलात जायचे नाही, म्हणून प्रथम विकिपीडिया वाचा, परंतु थोडक्यात, लॉक पदानुक्रम तयार होण्याची शक्यता आहे. जर तुम्हाला ही संकल्पना अधिक तपशीलवार जाणून घ्यायची असेल, तर तुम्हाला "डायनिंग फिलॉसॉफर्स प्रॉब्लेम" ("डायनिंग फिलॉसॉफर्स प्रॉब्लेम") वर तुमचा मेंदू शोधण्यासाठी आमंत्रित केले आहे.

दोन्ही लॉक एकाच परिस्थितीत कसे वागतील याचे एक चांगले उदाहरण येथे आहे.

लॉकच्या अंमलबजावणीबाबत. मला तपशीलात जायचे नाही, परंतु वितरित सिस्टमसाठी लॉक व्यवस्थापक आहेत, उदाहरणार्थ: ZooKeeper, Redis, etcd, Consul.

7.3 ऑपरेशन्सची निर्दोषता

इडम्पोटंट कोड हा सामान्यतः चांगला सराव आहे आणि हेच घडते जेव्हा विकासकाने व्यवहार वापरतो की नाही याची पर्वा न करता हे करण्यास सक्षम असणे चांगले होईल. जेव्हा ते ऑपरेशन एखाद्या वस्तूवर पुन्हा लागू केले जाते तेव्हा समान परिणाम आणण्यासाठी इडम्पोटेन्सी ही ऑपरेशनची गुणधर्म आहे. फंक्शन कॉल केले होते - निकाल दिला. एक सेकंद किंवा पाच नंतर पुन्हा कॉल - समान परिणाम दिला. अर्थात, डेटाबेसमधील डेटा बदलला असल्यास, परिणाम भिन्न असेल. थर्ड सिस्टीममधील डेटा फंक्शनवर अवलंबून नसू शकतो, परंतु जे काही करेल ते अंदाजे असणे आवश्यक आहे.

अविचारीपणाचे अनेक प्रकटीकरण असू शकतात. त्यापैकी एक फक्त तुमचा कोड कसा लिहायचा याची शिफारस आहे. तुम्हाला आठवत आहे की सर्वोत्तम कार्य हेच आहे जे एक गोष्ट करते? आणि या कार्यासाठी युनिट चाचण्या लिहिणे चांगले काय असेल? जर तुम्ही या दोन नियमांचे पालन केले तर तुमची फंक्शन्स अशक्त होण्याची शक्यता तुम्ही आधीच वाढवली आहे. गोंधळ टाळण्यासाठी, मी हे स्पष्ट करेन की इडम्पोटेंट फंक्शन्स "शुद्ध" ("फंक्शन शुद्धता" च्या अर्थाने) आवश्यक नाहीत. प्युअर फंक्शन्स ही अशी फंक्शन्स आहेत जी केवळ इनपुटवर प्राप्त झालेल्या डेटावर कार्य करतात, त्यांना कोणत्याही प्रकारे बदलल्याशिवाय आणि प्रक्रिया केलेले परिणाम परत न करता. ही फंक्शन्स आहेत जी तुम्हाला फंक्शनल प्रोग्रामिंग तंत्र वापरून तुमचा अॅप्लिकेशन स्केल करू देतात. आम्ही काही सामान्य डेटा आणि डेटाबेसबद्दल बोलत असल्याने, आमची कार्ये शुद्ध असण्याची शक्यता नाही,

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


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 Lambda नावाची सेवा आहे. लॅम्बडा तुम्हाला सर्व्हरची काळजी न घेण्याची परवानगी देतो, परंतु काही कार्यक्रमांच्या प्रतिसादात किंवा वेळापत्रकानुसार चालणारा कोड फक्त लोड करतो. इव्हेंट संदेश ब्रोकरद्वारे वितरित केलेले संदेश असू शकतात. AWS मध्ये, हा दलाल AWS SNS आहे. मला वाटते की जे AWS सह काम करत नाहीत त्यांच्यासाठी देखील हे स्पष्ट असले पाहिजे: आमच्याकडे एक ब्रोकर आहे जो चॅनेलद्वारे संदेश पाठवतो ("विषय"), आणि या चॅनेलची सदस्यता घेतलेल्या मायक्रोसर्व्हिसेस संदेश प्राप्त करतात आणि त्यावर प्रतिक्रिया देतात.

समस्या अशी आहे की SNS संदेश "किमान एकदा" ("किमान-एकदा वितरण") वितरित करते. याचा अर्थ काय? लवकरच किंवा नंतर तुमच्या Lambda कोडला दोनदा कॉल केला जाईल. आणि ते खरोखर घडते. अशी अनेक परिस्थिती आहेत जिथे तुमचे कार्य अशक्त असणे आवश्यक आहे: उदाहरणार्थ, जेव्हा खात्यातून पैसे काढले जातात, तेव्हा आम्ही अशी अपेक्षा करू शकतो की कोणीतरी समान रक्कम दोनदा काढेल, परंतु आम्हाला खात्री करणे आवश्यक आहे की या खरोखर 2 स्वतंत्र वेळा आहेत - दुसऱ्या शब्दांत, हे 2 भिन्न व्यवहार आहेत, आणि एकाची पुनरावृत्ती नाही.

बदलासाठी, मी आणखी एक उदाहरण देईन - एपीआय ("दर मर्यादा") वर विनंतीची वारंवारता मर्यादित करणे. आमच्या Lambda ला ठराविक user_id सह एक इव्हेंट प्राप्त होतो ज्यासाठी त्या आयडी असलेल्या वापरकर्त्याने आमच्या काही API ला त्याच्या संभाव्य विनंत्या संपवल्या आहेत की नाही हे तपासले पाहिजे. आम्ही AWS वरून केलेल्या कॉलचे मूल्य DynamoDB मध्ये संग्रहित करू शकतो आणि आमच्या फंक्शनच्या प्रत्येक कॉलसह ते 1 ने वाढवू शकतो.

पण हे लॅम्बडा फंक्शन एकाच इव्हेंटद्वारे दोनदा कॉल केले तर? तसे, तुम्ही lambda_handler() फंक्शनच्या वितर्कांकडे लक्ष दिले आहे का. दुसरा युक्तिवाद, AWS Lambda मधील संदर्भ डीफॉल्टनुसार दिलेला आहे आणि प्रत्येक अद्वितीय कॉलसाठी व्युत्पन्न केलेल्या विनंती_आयडीसह विविध मेटाडेटा समाविष्ट आहेत. याचा अर्थ असा की, आता टेबलमध्ये केलेल्या कॉलची संख्या संग्रहित करण्याऐवजी, आम्ही request_id ची यादी संग्रहित करू शकतो आणि प्रत्येक कॉलवर आमचा Lambda दिलेल्या विनंतीवर आधीच प्रक्रिया झाली आहे का ते तपासेल:

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"
    	})
	}

माझे उदाहरण खरेतर इंटरनेटवरून घेतलेले असल्याने, मी मूळ स्त्रोताची लिंक देईन, विशेषत: ती थोडी अधिक माहिती देत ​​असल्याने.

सामायिक केलेला डेटा लॉक करण्यासाठी युनिक ट्रान्झॅक्शन आयडी सारखे काहीतरी वापरले जाऊ शकते हे मी आधी कसे नमूद केले ते लक्षात ठेवा? आम्ही आता शिकलो आहोत की ऑपरेशन्स अशक्त करण्यासाठी देखील याचा वापर केला जाऊ शकतो. आपण स्वतः असे आयडी कोणत्या मार्गांनी तयार करू शकता ते जाणून घेऊया.