paint-brush
Django SaaS आर्किटेक्चर: सिंगल-टेनेंट बनाम मल्टी-टेनेंट - आपके लिए क्या सही है?द्वारा@pbityukov
2,734 रीडिंग
2,734 रीडिंग

Django SaaS आर्किटेक्चर: सिंगल-टेनेंट बनाम मल्टी-टेनेंट - आपके लिए क्या सही है?

द्वारा Pavel Bityukov9m2023/11/01
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

Django एक लोकप्रिय ढांचा है जिसे आप अपनी कंपनी के लिए एप्लिकेशन विकसित करने के लिए चुन सकते हैं। लेकिन क्या होगा यदि आप एक SaaS एप्लिकेशन बनाना चाहते हैं, जिसका उपयोग कई क्लाइंट करेंगे? आपको कौन सा आर्किटेक्चर चुनना चाहिए? आइए देखें कि इस कार्य को कैसे पूरा किया जा सकता है।
featured image - Django SaaS आर्किटेक्चर: सिंगल-टेनेंट बनाम मल्टी-टेनेंट - आपके लिए क्या सही है?
Pavel Bityukov HackerNoon profile picture

Django एक लोकप्रिय ढांचा है जिसे आप अपनी कंपनी के लिए एप्लिकेशन विकसित करने के लिए चुन सकते हैं। लेकिन क्या होगा यदि आप एक SaaS एप्लिकेशन बनाना चाहते हैं जिसका उपयोग कई क्लाइंट करेंगे? आपको कौन सा आर्किटेक्चर चुनना चाहिए? आइए देखें कि इस कार्य को कैसे पूरा किया जा सकता है।

एकल-किरायेदार वास्तुकला

सबसे सीधा तरीका यह है कि आपके पास मौजूद प्रत्येक ग्राहक के लिए एक अलग उदाहरण बनाया जाए। मान लीजिए कि हमारे पास एक Django एप्लिकेशन और एक डेटाबेस है। फिर, प्रत्येक क्लाइंट के लिए, हमें उसका अपना डेटाबेस और एप्लिकेशन इंस्टेंस चलाने की आवश्यकता है। इसका मतलब है कि प्रत्येक एप्लिकेशन इंस्टेंस में केवल एक किरायेदार है।

समर्पित एप्लिकेशन और डेटाबेस उदाहरणों के साथ एकल-किरायेदार वास्तुकला


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


हालाँकि, मान लीजिए कि हम एक बड़ी कंपनी बना रहे हैं जो 100,000 संगठनों को एक कॉर्पोरेट मैसेंजर प्रदान करती है। कल्पना कीजिए, प्रत्येक नए ग्राहक के लिए संपूर्ण बुनियादी ढांचे की नकल करना कितना महंगा हो सकता है! और, जब हमें एप्लिकेशन संस्करण को अपडेट करने की आवश्यकता होती है, तो हमें इसे प्रत्येक क्लाइंट के लिए तैनात करने की आवश्यकता होती है, इसलिए तैनाती भी धीमी हो जाएगी

बहु-किरायेदार वास्तुकला

एक और दृष्टिकोण है जो उस परिदृश्य में मदद कर सकता है जब हमारे पास एप्लिकेशन के लिए बहुत सारे ग्राहक हों: एक बहु-किरायेदार वास्तुकला। इसका मतलब है कि हमारे पास कई ग्राहक हैं, जिन्हें हम किरायेदार कहते हैं, लेकिन वे सभी एप्लिकेशन के केवल एक उदाहरण का उपयोग करते हैं।

साझा एप्लिकेशन और डेटाबेस इंस्टेंस के साथ मल्टी-टेनेंट आर्किटेक्चर

जबकि यह आर्किटेक्चर प्रत्येक ग्राहक के लिए समर्पित उदाहरणों की उच्च लागत की समस्या को हल करता है, यह एक नई समस्या पेश करता है: हम कैसे सुनिश्चित कर सकते हैं कि ग्राहक का डेटा अन्य ग्राहकों से सुरक्षित रूप से अलग है?


हम निम्नलिखित दृष्टिकोणों पर चर्चा करेंगे:

  1. साझा डेटाबेस और साझा डेटाबेस स्कीमा का उपयोग करना: हम विदेशी कुंजी द्वारा यह पहचान सकते हैं कि कौन सा किरायेदार डेटा का मालिक है जिसे हमें प्रत्येक डेटाबेस तालिका में जोड़ने की आवश्यकता है।


  2. एक साझा डेटाबेस, लेकिन अलग डेटाबेस स्कीमा का उपयोग करना: इस तरह, हमें कई डेटाबेस उदाहरणों को बनाए रखने की आवश्यकता नहीं होगी, लेकिन किरायेदार डेटा अलगाव का एक अच्छा स्तर प्राप्त होगा।


  3. अलग-अलग डेटाबेस का उपयोग करना: यह एकल-किरायेदार उदाहरण के समान दिखता है, लेकिन समान नहीं होगा, क्योंकि हम अभी भी एक साझा एप्लिकेशन इंस्टेंस का उपयोग करेंगे और किरायेदार की जांच करके चयन करेंगे कि किस डेटाबेस का उपयोग करना है।


आइए इन विचारों को गहराई से देखें और देखें कि इन्हें Django एप्लिकेशन के साथ कैसे एकीकृत किया जाए।

साझा स्कीमा के साथ एक साझा डेटाबेस

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


आइए Django दस्तावेज़ से डेटाबेस संरचना का एक उदाहरण लें:

 from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)


हमें यह पहचानने की आवश्यकता होगी कि कौन से रिकॉर्ड किस किरायेदार के स्वामित्व में हैं। इसलिए, हमें प्रत्येक मौजूदा तालिका में एक Tenant तालिका और एक विदेशी कुंजी जोड़ने की आवश्यकता है:

 class Tenant(models.Model): name = models.CharField(max_length=200) class Question(models.Model): tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") class Choice(models.Model): tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)


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

 class Tenant(models.Model): name = models.CharField(max_length=200) class BaseModel(models.Model): tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) class Meta: abstract = True class Question(BaseModel): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") class Choice(BaseModel): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)


जैसा कि आप देख सकते हैं, यहां कम से कम दो प्रमुख जोखिम हैं: एक डेवलपर नए मॉडल में एक किरायेदार फ़ील्ड जोड़ना भूल सकता है , या एक डेवलपर डेटा फ़िल्टर करते समय इस फ़ील्ड का उपयोग करना भूल सकता है।


इस उदाहरण का स्रोत कोड GitHub पर पाया जा सकता है: https://github.com/bp72/django-multitenancy-examples/tree/main/01_shared_database_shared_schema

अलग-अलग स्कीमा के साथ एक साझा डेटाबेस

साझा स्कीमा के जोखिमों को ध्यान में रखते हुए, आइए एक अन्य विकल्प पर विचार करें: डेटाबेस अभी भी साझा किया जाएगा, लेकिन हम प्रत्येक किरायेदार के लिए एक समर्पित स्कीमा बनाएंगे। कार्यान्वयन के लिए, हम एक लोकप्रिय लाइब्रेरी django-किरायेदारों ( दस्तावेज़ीकरण ) को देख सकते हैं।


आइए अपने छोटे प्रोजेक्ट में django-tenants जोड़ें (आधिकारिक इंस्टॉलेशन चरण यहां पाए जा सकते हैं)।


पहला चरण pip के माध्यम से लाइब्रेरी इंस्टालेशन है:

pip install django-tenants


मॉडल बदलें: Tenant मॉडल अब एक अलग ऐप में होगा Question और Choice मॉडल का अब किरायेदार के साथ कोई संबंध नहीं होगा। चूंकि अलग-अलग किरायेदारों का डेटा अलग-अलग स्कीमा में होगा, इसलिए हमें अब अलग-अलग रिकॉर्ड को किरायेदार पंक्तियों से जोड़ने की आवश्यकता नहीं होगी।


फ़ाइल किरायेदार/मॉडल.py

 from django.db import models from django_tenants.models import TenantMixin, DomainMixin class Tenant(TenantMixin): name = models.CharField(max_length=200) # default true, schema will be automatically created and synced when it is saved auto_create_schema = True class Domain(DomainMixin): # a required table for django-tenants too ...


फ़ाइल poll/models.py

 from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)

ध्यान दें कि प्रश्न और विकल्प के पास अब किरायेदार के लिए कोई विदेशी कुंजी नहीं है!


दूसरी चीज़ जो बदली गई है वह यह है कि किरायेदार अब एक अलग ऐप में है: यह न केवल डोमेन को अलग करने के लिए है बल्कि महत्वपूर्ण भी है क्योंकि हमें tenants तालिका को साझा स्कीमा में संग्रहीत करने की आवश्यकता होगी, और प्रत्येक किरायेदार के लिए polls तालिकाएँ बनाई जाएंगी स्कीमा.


एकाधिक स्कीमा और किरायेदारों का समर्थन करने के लिए settings.py फ़ाइल में परिवर्तन करें:

 DATABASES = { 'default': { 'ENGINE': 'django_tenants.postgresql_backend', # .. } } DATABASE_ROUTERS = ( 'django_tenants.routers.TenantSyncRouter', ) MIDDLEWARE = ( 'django_tenants.middleware.main.TenantMainMiddleware', #... ) TEMPLATES = [ { #... 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.request', #... ], }, }, ] SHARED_APPS = ( 'django_tenants', # mandatory 'tenants', # you must list the app where your tenant model resides in 'django.contrib.contenttypes', # everything below here is optional 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.admin', ) TENANT_APPS = ( # your tenant-specific apps 'polls', ) INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS] TENANT_MODEL = "tenants.Tenant" TENANT_DOMAIN_MODEL = "tenants.Domain"


आगे, आइए माइग्रेशन बनाएं और लागू करें:

python manage.py makemigrations

python manage.py migrate_schemas --shared


परिणामस्वरूप, हम देखेंगे कि सार्वजनिक स्कीमा बनाई जाएगी और इसमें केवल साझा तालिकाएँ होंगी।

प्रथम माइग्रेट_स्कीमास कमांड चलाने के बाद डेटाबेस संरचना

हमें public स्कीमा के लिए एक डिफ़ॉल्ट किरायेदार बनाने की आवश्यकता होगी:

python manage.py create_tenant --domain-domain=default.com --schema_name=public --name=default_tenant


यदि पूछा जाए तो is_primary को True पर सेट करें।


और फिर, हम सेवा के वास्तविक किरायेदार बनाना शुरू कर सकते हैं:

 python manage.py create_tenant --domain-domain=tenant1.com --schema_name=tenant1 --name=tenant_1 python manage.py create_tenant --domain-domain=tenant2.com --schema_name=tenant2 --name=tenant_2


ध्यान दें कि डेटाबेस में अब 2 और स्कीमा हैं जिनमें polls टेबल हैं:

किरायेदारों को बनाने के बाद डेटाबेस स्कीमा

अब, जब आप किरायेदारों के लिए सेट किए गए डोमेन पर एपीआई कॉल करेंगे तो आपको विभिन्न स्कीमा से प्रश्न और विकल्प मिलेंगे - सब हो गया!


हालाँकि यदि आप मौजूदा ऐप को माइग्रेट करते हैं तो सेटअप अधिक जटिल और शायद और भी कठिन लगता है, फिर भी इस दृष्टिकोण में डेटा की सुरक्षा जैसे कई फायदे हैं।


उदाहरण का कोड यहां पाया जा सकता है।

अलग डेटाबेस

आखिरी दृष्टिकोण जिस पर हम आज चर्चा करेंगे वह और भी आगे जा रहा है और किरायेदारों के लिए अलग डेटाबेस बना रहा है।


इस बार, हमारे पास कुछ डेटाबेस होंगे:

किरायेदारों के लिए अलग डेटाबेस


हम साझा किए गए डेटा जैसे किरायेदार की मैपिंग को डेटाबेस के नामों में default_db में संग्रहीत करेंगे और प्रत्येक किरायेदार के लिए एक अलग डेटाबेस बनाएंगे।


फिर हमें सेटिंग्स.py में डेटाबेस कॉन्फ़िगरेशन सेट करने की आवश्यकता होगी:

 DATABASES = { 'default': { 'NAME': 'default_db', ... }, 'tenant_1': { 'NAME': 'tenant_1', ... }, 'tenant_2': { 'NAME': 'tenant_2', ... }, }


और अब, हम QuerySet विधि का using कॉल करके प्रत्येक किरायेदार के लिए डेटा प्राप्त करने में सक्षम होंगे:

 Questions.objects.using('tenant_1')…


विधि का नकारात्मक पक्ष यह है कि आपको इसका उपयोग करके प्रत्येक डेटाबेस पर सभी माइग्रेशन लागू करने की आवश्यकता होगी:

python manage.py migrate --database=tenant_1


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


दूसरी ओर, किरायेदार के डेटा का अलगाव वास्तव में अच्छा है: डेटाबेस को भौतिक रूप से अलग किया जा सकता है। एक और फायदा यह है कि हम केवल Postgresql का उपयोग करके सीमित नहीं रहेंगे क्योंकि यह django-tenants के लिए आवश्यक है, हम कोई भी इंजन चुन सकते हैं जो हमारी आवश्यकताओं के अनुरूप होगा।


एकाधिक डेटाबेस विषय पर अधिक जानकारी Django दस्तावेज़ में पाई जा सकती है।

तुलना


एकल किरायेदार

साझा स्कीमा के साथ एमटी

अलग स्कीमा के साथ एमटी

अलग डेटाबेस के साथ एमटी

डेटा अलगाव

✅उच्च

❌न्यूनतम

✅उच्च

✅उच्च

गलती से डेटा लीक होने का खतरा

✅कम

❌उच्च

✅कम

✅कम

बुनियादी ढांचे की लागत

❌प्रत्येक किरायेदार के साथ उच्चतर

✅निचला

✅निचला

✅❌ एकल-किरायेदार से कम

परिनियोजन गति

❌प्रत्येक किरायेदार के साथ कम करें

✅❌ माइग्रेशन धीमा होगा क्योंकि उन्हें प्रत्येक स्कीमा के लिए निष्पादित करने की आवश्यकता होगी

✅❌ माइग्रेशन धीमा होगा क्योंकि उन्हें प्रत्येक डेटाबेस के लिए निष्पादित करने की आवश्यकता होगी

कार्यान्वयन में आसान

❌ यदि सेवा पहले से ही एकल-किरायेदार ऐप के रूप में लागू की गई थी तो बहुत सारे बदलावों की आवश्यकता है

निष्कर्ष

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


अलग-अलग डेटाबेस किरायेदार के डेटा के लिए सबसे अच्छा अलगाव प्रदान करते हैं और लागू करने में आसान होते हैं, हालांकि, रखरखाव के लिए आपको अधिक लागत आती है: अद्यतन करने के लिए डेटाबेस, डेटाबेस कनेक्शन संख्या अधिक होती है।


कार्यान्वयन के लिए एक अलग स्कीमा बिट कॉम्प्लेक्स वाला एक साझा डेटाबेस और माइग्रेशन के साथ कुछ समस्याएं हो सकती हैं।


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