टेनेंट अलगाव रणनीतियाँ और समझौते
SaaS कार्यभार के लिए साझा-स्कीमा, टेनेंट-विशिष्ट स्कीमा और टेनेंट-विशिष्ट डेटाबेस अलगाव मॉडल की तुलना कीजिए।
टेनेंट अलगाव रणनीतियाँ और समझौते, CoddyKit पर FastAPI बैकएंड डेवलपमेंट बूटकैंप का एक निःशुल्क पाठ है। यह 4 में से 1वाँ पाठ है। इस अध्ययन पथ के 3 तक कोई भी पाठ पूरा पढ़ना निःशुल्क है — इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ व्यावहारिक अभ्यास भी उपलब्ध कराता है। यह FastAPI बैकएंड डेवलपमेंट बूटकैंप सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
टेनेंट पृथक्करण क्यों महत्वपूर्ण है
बहु-टेनेंट SaaS में कई ग्राहक (टेनेंट) एक ही चल रहे FastAPI अनुप्रयोग को साझा करते हैं। मुख्य प्रश्न यह है: हर टेनेंट का डेटा कितना अलग रखा गया है?
पृथक्करण उन चार बातों को प्रभावित करता है जिनके बीच आपको लगातार संतुलन बनाना पड़ता है:
- सुरक्षा और प्रभाव-क्षेत्र — क्या कोई त्रुटि टेनेंट A की पंक्तियाँ टेनेंट B को दिखा सकती है?
- लागत — प्रत्येक टेनेंट कितने बुनियादी ढाँचे का उपयोग करता है?
- परिचालन जटिलता — माइग्रेशन, बैकअप और पुनर्स्थापन।
- प्रति-टेनेंट अनुकूलन — क्या किसी एक टेनेंट को अतिरिक्त कॉलम या कस्टम स्कीमा दिया जा सकता है?
तीन मानक मॉडल हैं: साझा-स्कीमा, प्रति-टेनेंट-स्कीमा, और प्रति-टेनेंट-डेटाबेस। इस पाठ के बाकी भाग FastAPI कार्यभार के लिए इनकी तुलना करते हैं।
साझा-स्कीमा: एक तालिका, एक tenant_id कॉलम
सबसे सरल मॉडल में हर टेनेंट की पंक्तियाँ एक ही तालिकाओं में रहती हैं और tenant_id कॉलम से अलग पहचानी जाती हैं। हर क्वेरी में इसके आधार पर फ़िल्टर लगना चाहिए।
हज़ारों छोटे टेनेंटों के लिए यह सबसे सस्ता और सबसे अधिक विस्तार योग्य विकल्प है, लेकिन पृथक्करण पूरी तरह तार्किक होता है — WHERE tenant_id = ... का एक बार छूट जाना टेनेंटों के बीच डेटा उजागर कर देता है।
नीचे सामान्य SQLAlchemy मॉडल का ढाँचा दिया गया है। हर टेनेंट-स्वामित्व वाली तालिका में अनुक्रमित tenant_id पर ध्यान दीजिए।
from sqlalchemy import String, Integer, ForeignKey, Index
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Invoice(Base):
__tablename__ = "invoices"
id: Mapped[int] = mapped_column(primary_key=True)
tenant_id: Mapped[str] = mapped_column(String, index=True)
amount_cents: Mapped[int] = mapped_column(Integer)
customer_email: Mapped[str] = mapped_column(String)
# Composite index: nearly every query filters by tenant first
__table_args__ = (Index("ix_invoices_tenant_id", "tenant_id"),)अनुरोध से टेनेंट निर्धारित करना
कोई भी क्वेरी चलने से पहले आपको पता होना चाहिए कि अनुरोध किस टेनेंट से संबंधित है। सामान्य रणनीतियाँ:
- सबडोमेन —
acme.app.com→ टेनेंटacme। - JWT दावा — एक्सेस टोकन में एक
tenant_idहोता है। - हेडर —
X-Tenant-ID(आंतरिक या सेवा-से-सेवा संचार के लिए)।
FastAPI में यह एक निर्भरता बन जाती है, जो टेनेंट को एक बार निर्धारित और सत्यापित करके उसे हर जगह उपलब्ध कराती है। क्लाइंट द्वारा स्वतंत्र रूप से तय की जा सकने वाली टेनेंट आईडी पर कभी भरोसा न कीजिए, जब तक वह क्रिप्टोग्राफ़िक रूप से संबद्ध न हो (जैसे हस्ताक्षरित JWT के भीतर)।
from fastapi import Depends, HTTPException, Request
async def get_current_tenant(request: Request) -> str:
host = request.headers.get("host", "")
sub = host.split(".")[0]
if not sub or sub in {"www", "app"}:
raise HTTPException(status_code=400, detail="Tenant could not be resolved")
return sub
# Usage in a route:
# @app.get("/invoices")
# async def list_invoices(tenant_id: str = Depends(get_current_tenant)):
# ...साझा-स्कीमा का खतरा: फ़िल्टर भूल जाना
साझा-स्कीमा का सबसे बड़ा जोखिम यह है कि डेवलपर WHERE tenant_id = :tenant लगाना भूल जाए। क्वेरी फिर भी सफल होती है — बस वह सभी का डेटा लौटा देती है।
अनुशासन पर निर्भर रहने से बेहतर, ये दो सुरक्षा उपाय विस्तार के साथ अधिक प्रभावी रहते हैं:
- एक रिपॉज़िटरी परत, जो हमेशा
tenant_idजोड़ती है, ताकि रूट कोड बिना दायरे वाली कच्ची क्वेरी न चला सके। - डेटाबेस द्वारा लागू किया गया Postgres Row-Level Security (RLS), जो अगला सुरक्षा-स्तर प्रदान करता है।
यहाँ एक पतली रिपॉज़िटरी अनुप्रयोग परत पर दायरा सुनिश्चित करती है।
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
class InvoiceRepository:
def __init__(self, session: AsyncSession, tenant_id: str):
self.session = session
self.tenant_id = tenant_id
async def list(self):
# tenant_id is ALWAYS applied — callers cannot bypass it
stmt = select(Invoice).where(Invoice.tenant_id == self.tenant_id)
result = await self.session.execute(stmt)
return result.scalars().all()
async def get(self, invoice_id: int):
stmt = select(Invoice).where(
Invoice.id == invoice_id,
Invoice.tenant_id == self.tenant_id,
)
return (await self.session.execute(stmt)).scalar_one_or_none()Postgres RLS के साथ डेटाबेस-लागू पृथक्करण
Row-Level Security Postgres को स्वयं उन पंक्तियों को अस्वीकार करने देता है जो वर्तमान टेनेंट से मेल नहीं खातीं — भले ही अनुप्रयोग फ़िल्टर लगाना भूल जाए। आप हर अनुरोध के लिए एक सत्र चर सेट करते हैं और नीति उसका उपयोग करती है।
इससे साझा-स्कीमा "WHERE मौजूद होने की उम्मीद" से बदलकर "डेटाबेस इसकी गारंटी देता है" बन जाता है। इसकी कीमत यह है कि हर कनेक्शन को चर सेट करना पड़ता है, जो कनेक्शन पूलिंग के साथ सावधानी से काम करता है (इसे हर लेन-देन में सेट कीजिए)।
-- One-time DDL
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.current_tenant', true));
-- Per request / per transaction, the app runs:
-- SET LOCAL app.current_tenant = 'acme';
-- Now SELECT * FROM invoices only returns acme's rows automatically.FastAPI अनुरोध में RLS जोड़ना
RLS को काम करने योग्य बनाने के लिए हर अनुरोध एक लेन-देन खोलता है, SET LOCAL app.current_tenant चलाता है, फिर सभी क्वेरी उसी के भीतर चलाता है। SET LOCAL का दायरा लेन-देन तक सीमित होता है, इसलिए पूल किया गया कनेक्शन अगली टेनेंट को यह मान नहीं दे सकता।
यह तरीका अनुप्रयोग की निर्भरता (टेनेंट निर्धारित करना) और डेटाबेस प्रवर्तन (RLS नीति) को जोड़ता है — यानी सुरक्षा की कई परतें।
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession
async def tenant_session(
tenant_id: str = Depends(get_current_tenant),
) -> AsyncSession:
async with SessionLocal() as session:
async with session.begin():
# bind param avoids SQL injection of the tenant value
await session.execute(
text("SET LOCAL app.current_tenant = :t"),
{"t": tenant_id},
)
yield sessionप्रति-टेनेंट-स्कीमा: एक ही DB, अलग नाम-स्थान
प्रति-टेनेंट-स्कीमा में एक Postgres डेटाबेस में कई स्कीमा होते हैं — tenant_acme.invoices, tenant_globex.invoices। तालिकाएँ समान होती हैं, लेकिन स्कीमा के आधार पर भौतिक रूप से अलग रहती हैं।
लाभ: साझा-स्कीमा से अधिक मजबूत पृथक्करण, tenant_id कॉलम की आवश्यकता नहीं, प्रति-टेनेंट बैकअप और पुनर्स्थापन आसान, तथा स्कीमा हटाकर टेनेंट हटाया जा सकता है।
हानियाँ: माइग्रेशन को हर स्कीमा में चलाना पड़ता है (N बार), और बहुत अधिक स्कीमा या तालिकाओं की संख्या होने पर Postgres का प्रदर्शन घटता है (हज़ारों स्कीमा कैटलॉग को अनावश्यक रूप से बड़ा कर देते हैं)। यह कुछ दर्जन से लेकर कम सैकड़ों बड़े टेनेंटों के लिए सबसे उपयुक्त है।
स्कीमा के आधार पर क्वेरी रूट करना (search_path)
Postgres बिना-योग्य तालिका नामों को search_path का उपयोग करके निर्धारित करता है। हर अनुरोध के लिए इसे टेनेंट के स्कीमा पर सेट कीजिए; अब वही ORM मॉडल उस टेनेंट की तालिकाओं से पढ़ेंगे और उनमें लिखेंगे।
RLS की तरह, लेन-देन के भीतर SET LOCAL search_path का उपयोग कीजिए, ताकि पूल किया गया कनेक्शन एक टेनेंट के स्कीमा को दूसरे टेनेंट के अनुरोध में न ले जाए।
from sqlalchemy import text
async def schema_scoped_session(
tenant_id: str = Depends(get_current_tenant),
):
schema = f"tenant_{tenant_id}"
async with SessionLocal() as session:
async with session.begin():
# quote_ident-style guard: validate before interpolating identifiers
if not tenant_id.isalnum():
raise HTTPException(400, "Invalid tenant identifier")
await session.execute(text(f'SET LOCAL search_path TO "{schema}", public'))
yield sessionकई स्कीमा में माइग्रेशन
प्रति-टेनेंट-स्कीमा की परिचालन लागत माइग्रेशन है। एकल Alembic अपग्रेड को हर टेनेंट स्कीमा पर लागू करना पड़ता है। आप टेनेंटों की सूची पर क्रम से चलते हैं, स्कीमा सेट करते हैं और माइग्रेशन चलाते हैं।
आंशिक विफलता के लिए योजना बनाइए: यदि 300 में से 200वाँ स्कीमा विफल हो जाए, तो आपको ऐसे माइग्रेशन चाहिए जिन्हें दोबारा सुरक्षित रूप से चलाया जा सके और जहाँ से काम रुका हो वहीं से जारी किया जा सके। यही मुख्य कारण है कि टीमें प्रति-टेनेंट-स्कीमा को हज़ारों के बजाय सैकड़ों टेनेंटों तक सीमित रखती हैं।
# Conceptual loop run by an Alembic env.py or a management command
tenant_schemas = ["tenant_acme", "tenant_globex", "tenant_initech"]
def run_migrations_for_all(connection, run_one):
failures = []
for schema in tenant_schemas:
try:
connection.execute(f'SET search_path TO "{schema}"')
run_one(connection) # apply the same upgrade per schema
except Exception as exc: # noqa: BLE001
failures.append((schema, str(exc)))
if failures:
raise RuntimeError(f"Migration failed for: {failures}")प्रति-टेनेंट-डेटाबेस: अधिकतम पृथक्करण
प्रति-टेनेंट-डेटाबेस में हर टेनेंट का अपना डेटाबेस होता है (कभी-कभी अपना सर्वर भी)। पृथक्करण का स्तर सबसे मजबूत होता है: अलग कनेक्शन, अलग बैकअप, और डेटा-निवास अनुपालन के लिए अलग क्षेत्र तक।
लाभ: मजबूत सुरक्षा सीमा, प्रति-टेनेंट पुनर्स्थापन सरल, "शोर करने वाले पड़ोसी" से पृथक्करण आसान, और प्रति-टेनेंट डेटा हटाना सरल (डेटाबेस हटा दीजिए)।
हानियाँ: सबसे अधिक लागत और परिचालन भार — आपको हर डेटाबेस के लिए एक कनेक्शन पूल बनाए रखना पड़ता है, टेनेंटों के बीच विश्लेषण आसानी से नहीं चला सकते, और किसी टेनेंट को जोड़ने का अर्थ डेटाबेस उपलब्ध कराना है। यह कम संख्या वाले, बड़े और कड़े अनुपालन वाले टेनेंटों (जैसे उद्यम B2B) के लिए सबसे उपयुक्त है।
प्रति-टेनेंट-डेटाबेस के लिए कनेक्शन रूटिंग
अनुप्रयोग में टेनेंट से डेटाबेस URL का मानचित्र और इंजन या पूल का कैश रहता है। एक निर्भरता टेनेंट निर्धारित करती है, उसका DSN खोजती है और उस डेटाबेस से संबद्ध सत्र लौटाती है।
इंजनों को कैश करना आवश्यक है: हर अनुरोध पर नया इंजन बनाने से कनेक्शन समाप्त हो जाते हैं। हर टेनेंट के लिए एक इंजन कैश कीजिए और उसके पूल का पुनः उपयोग कीजिए।
from functools import lru_cache
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
TENANT_DSNS = {
"acme": "postgresql+asyncpg://app@db-acme/acme",
"globex": "postgresql+asyncpg://app@db-globex/globex",
}
@lru_cache(maxsize=256)
def engine_for(tenant_id: str):
dsn = TENANT_DSNS.get(tenant_id)
if dsn is None:
raise HTTPException(404, "Unknown tenant")
return create_async_engine(dsn, pool_size=5, max_overflow=2)
async def db_per_tenant_session(tenant_id: str = Depends(get_current_tenant)):
maker = async_sessionmaker(engine_for(tenant_id), expire_on_commit=False)
async with maker() as session:
yield sessionत्वरित जाँच: पृथक्करण मॉडल चुनना
एक B2B स्टार्टअप को पहले वर्ष में 5,000 छोटे टेनेंट जोड़ने की अपेक्षा है। वे बुनियादी ढाँचे की सबसे कम लागत और सबसे सरल माइग्रेशन प्रक्रिया चाहते हैं, और मजबूत क्वेरी-दायरा अनुशासन तथा डेटाबेस द्वारा लागू सुरक्षा-स्तर में निवेश करने को तैयार हैं। कौन-सा पृथक्करण मॉडल सबसे उपयुक्त है?
पुनरावलोकन: सही पृथक्करण रणनीति चुनना
सस्ते और साझा मॉडल से महँगे और पृथक मॉडल तक तीन विकल्प:
- साझा-स्कीमा — एक
tenant_idकॉलम। सबसे सस्ता, हज़ारों छोटे टेनेंटों तक विस्तार योग्य, और एक माइग्रेशन। जोखिम: केवल तार्किक पृथक्करण; दायरा सुनिश्चित करने वाली रिपॉज़िटरी और RLS से इसे कम कीजिए। - प्रति-टेनेंट-स्कीमा —
search_pathके माध्यम से हर टेनेंट के लिए एक स्कीमा। अधिक मजबूत पृथक्करण, प्रति-टेनेंट बैकअप या हटाना आसान। लागत: माइग्रेशन N बार चलते हैं; यह सैकड़ों टेनेंटों तक सीमित रहता है। - प्रति-टेनेंट-डेटाबेस — हर टेनेंट के लिए एक DB, जिसका इंजन कैश किया जाता है। सबसे मजबूत पृथक्करण और डेटा-निवास नियंत्रण। लागत: सबसे अधिक परिचालन और बुनियादी ढाँचा; कम संख्या वाले बड़े और कड़े अनुपालन वाले टेनेंटों के लिए सर्वोत्तम।
निर्णय के आधार: टेनेंटों की संख्या और आकार, अनुपालन या डेटा-निवास की आवश्यकताएँ, माइग्रेशन की स्वीकार्य जटिलता और बजट। कई वास्तविक प्रणालियाँ हाइब्रिड होती हैं — छोटे ग्राहकों के बड़े समूह के लिए साझा-स्कीमा और उद्यम खातों के लिए प्रति-टेनेंट-डेटाबेस। FastAPI में तीनों मॉडलों की समान कड़ी है: एक टेनेंट-निर्धारण निर्भरता, जो सही सत्र उपलब्ध कराती है।
एआई शिक्षक के साथ FastAPI बैकएंड डेवलपमेंट बूटकैंप सीखें — निःशुल्क
अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।
- पाठ्यक्रम
- 21
- पाठ
- 84
अक्सर पूछे जाने वाले प्रश्न
क्या “टेनेंट अलगाव रणनीतियाँ और समझौते” पाठ निःशुल्क है?
हाँ — FastAPI बैकएंड डेवलपमेंट बूटकैंप अध्ययन पथ के 3 तक कोई भी पाठ, जिसमें “टेनेंट अलगाव रणनीतियाँ और समझौते” भी शामिल है, यहाँ वेब पर पूरा पढ़ना निःशुल्क है। इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ इंटरैक्टिव अभ्यास भी उपलब्ध कराता है। FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
“टेनेंट अलगाव रणनीतियाँ और समझौते” में मैं क्या सीखूँगा?
SaaS कार्यभार के लिए साझा-स्कीमा, टेनेंट-विशिष्ट स्कीमा और टेनेंट-विशिष्ट डेटाबेस अलगाव मॉडल की तुलना कीजिए। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ FastAPI बैकएंड डेवलपमेंट बूटकैंप का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।
क्या FastAPI बैकएंड डेवलपमेंट बूटकैंप शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?
पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर FastAPI बैकएंड डेवलपमेंट बूटकैंप शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 1वाँ पाठ है।
“टेनेंट अलगाव रणनीतियाँ और समझौते” पाठ पूरा करने में कितना समय लगता है?
CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।
क्या मैं इस FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ में कोड लिख और चला सकता हूँ?
हाँ। हर FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।
इस पाठ्यक्रम के सभी पाठ
- टेनेंट अलगाव रणनीतियाँ और समझौते
- टेनेंट संदर्भ निर्धारण और Middleware
- पंक्ति-स्तरीय सुरक्षा और डेटा विभाजन
- उपयोग मापन, कोटा और बिलिंग हुक