Strategi Pengasingan Penyewa dan Implikasinya
Bandingkan model pengasingan skema dikongsi, skema mengikut penyewa dan pangkalan data mengikut penyewa untuk beban kerja SaaS.
Strategi Pengasingan Penyewa dan Implikasinya ialah pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI percuma di CoddyKit. Ini ialah pelajaran 1 daripada 4. Sebanyak 3 pelajaran dalam laluan pembelajaran ini boleh dibaca sepenuhnya secara percuma — selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan praktikal dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.
Mengapa Pengasingan Penyewa Penting
Dalam SaaS berbilang penyewa, ramai pelanggan (penyewa) berkongsi satu aplikasi FastAPI yang sedang berjalan. Persoalan utamanya ialah: sejauh manakah data setiap penyewa diasingkan?
Pengasingan mempengaruhi empat perkara yang perlu anda timbang secara berterusan:
- Keselamatan & jejari kesan — bolehkah pepijat membocorkan baris Tenant A kepada Tenant B?
- Kos — berapa banyak infrastruktur yang digunakan oleh setiap penyewa?
- Kerumitan operasi — migrasi, sandaran, pemulihan.
- Penyesuaian mengikut penyewa — bolehkah satu penyewa mendapat lajur tambahan atau skema tersuai?
Terdapat tiga model utama: skema dikongsi, skema bagi setiap penyewa, dan pangkalan data bagi setiap penyewa. Baki pelajaran ini membandingkannya untuk beban kerja FastAPI.
Skema Dikongsi: Satu Jadual, Satu Lajur tenant_id
Model paling mudah: baris semua penyewa berada dalam jadual yang sama, dibezakan melalui lajur tenant_id. Setiap pertanyaan mesti menapis berdasarkan lajur ini.
Ini ialah pilihan paling murah dan paling mudah diskalakan untuk ribuan penyewa kecil, tetapi pengasingannya semata-mata logik — satu WHERE tenant_id = ... yang tertinggal akan membocorkan data merentas penyewa.
Di bawah ialah bentuk model SQLAlchemy yang lazim. Perhatikan tenant_id yang diindeks pada setiap jadual milik penyewa.
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"),)Menentukan Penyewa daripada Permintaan
Sebelum sebarang pertanyaan dijalankan, anda mesti mengetahui penyewa yang memiliki permintaan tersebut. Strategi yang biasa digunakan:
- Subdomain —
acme.app.com→ penyewaacme. - Tuntutan JWT — token akses membawa
tenant_id. - Pengepala —
X-Tenant-ID(dalaman/antara perkhidmatan).
Dalam FastAPI, ini menjadi kebergantungan yang menentukan dan mengesahkan penyewa sekali, kemudian menyuntikkannya di semua tempat. Jangan sesekali mempercayai id penyewa yang boleh ditetapkan secara bebas oleh klien melainkan id itu terikat secara kriptografi (contohnya di dalam JWT yang ditandatangani).
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)):
# ...Bahaya Skema Dikongsi: Terlupa Menapis
Risiko nombor satu dalam skema dikongsi ialah pembangun terlupa WHERE tenant_id = :tenant. Pertanyaan itu masih berjaya — cuma ia mengembalikan data semua orang.
Dua perlindungan berikut lebih mudah diskalakan berbanding bergantung pada ketelitian:
- Lapisan repositori yang sentiasa menyuntik
tenant_id, supaya kod laluan tidak boleh mengeluarkan pertanyaan mentah tanpa skop. - Keselamatan Tahap Baris (RLS) Postgres sebagai perlindungan sandaran yang dikuatkuasakan oleh pangkalan data (diterangkan seterusnya).
Di sini, repositori nipis menjamin penetapan skop pada lapisan aplikasi.
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()Pengasingan yang Dikuatkuasakan Pangkalan Data dengan RLS
Keselamatan Tahap Baris membolehkan Postgres sendiri menolak baris yang tidak sepadan dengan penyewa semasa — walaupun aplikasi terlupa penapis. Anda menetapkan pemboleh ubah sesi bagi setiap permintaan dan dasar menggunakannya.
Ini mengubah skema dikongsi daripada "harap-harap WHERE ada" kepada "pangkalan data menjaminnya". Imbangannya: setiap sambungan mesti menetapkan pemboleh ubah tersebut, yang berinteraksi dengan teliti dengan pengumpulan sambungan (tetapkannya bagi setiap transaksi).
-- 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.Menyepadukan RLS ke dalam Permintaan FastAPI
Untuk menjadikan RLS berfungsi, setiap permintaan membuka transaksi, mengeluarkan SET LOCAL app.current_tenant, kemudian menjalankan semua pertanyaan di dalamnya. SET LOCAL terhad kepada transaksi, jadi sambungan yang dikumpulkan tidak akan membocorkan nilai itu kepada penyewa seterusnya.
Corak ini menggabungkan kebergantungan aplikasi (menentukan penyewa) dengan penguatkuasaan pangkalan data (dasar RLS) — pertahanan berlapis.
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 sessionSkema bagi Setiap Penyewa: Pangkalan Data Sama, Ruang Nama Berasingan
Dalam skema bagi setiap penyewa, satu pangkalan data Postgres menyimpan banyak skema — tenant_acme.invoices, tenant_globex.invoices. Jadualnya sama, tetapi diasingkan secara fizikal mengikut skema.
Kelebihan: pengasingan lebih kukuh berbanding skema dikongsi, tiada lajur tenant_id diperlukan, sandaran/pemulihan bagi setiap penyewa lebih mudah, dan anda boleh membuang penyewa dengan membuang skema.
Kekurangan: migrasi mesti dijalankan merentas setiap skema (N kali), dan prestasi Postgres merosot apabila bilangan skema/jadual terlalu tinggi (ribuan skema membebankan katalog). Paling sesuai untuk puluhan hingga beberapa ratus penyewa yang lebih besar.
Menghalakan Pertanyaan Mengikut Skema (search_path)
Postgres menentukan nama jadual yang tidak layak menggunakan search_path. Bagi setiap permintaan, anda menetapkannya kepada skema penyewa, lalu model ORM yang sama membaca/menulis jadual penyewa tersebut.
Seperti RLS, gunakan SET LOCAL search_path di dalam transaksi supaya sambungan yang dikumpulkan tidak pernah membawa skema seorang penyewa ke dalam permintaan penyewa lain.
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 sessionMigrasi Merentas Banyak Skema
Kos operasi skema bagi setiap penyewa ialah migrasi. Satu peningkatan Alembic mesti digunakan pada setiap skema penyewa. Anda mengulangi senarai penyewa, menetapkan skema, kemudian menjalankan migrasi.
Rancang untuk kegagalan separa: jika skema 200 daripada 300 gagal, anda memerlukan migrasi yang idempoten dan boleh disambung semula. Inilah sebab utama pasukan mengehadkan skema bagi setiap penyewa kepada ratusan, bukan ribuan, penyewa.
# 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}")Pangkalan Data bagi Setiap Penyewa: Pengasingan Maksimum
Pangkalan data bagi setiap penyewa memberikan setiap penyewa pangkalan datanya sendiri (kadangkala pelayannya sendiri). Pengasingannya adalah yang paling kukuh: sambungan berasingan, sandaran berasingan, malah rantau berasingan untuk pematuhan lokasi data.
Kelebihan: sempadan keselamatan yang kukuh, pemulihan bagi setiap penyewa yang mudah, pengasingan "jiran bising" yang mudah, pemadaman data bagi setiap penyewa yang ringkas (buang pangkalan data).
Kekurangan: kos dan beban operasi tertinggi — anda menyelenggara kumpulan sambungan bagi setiap pangkalan data, tidak boleh menjalankan analitis merentas penyewa dengan mudah, dan pendaftaran penyewa bermakna menyediakan pangkalan data. Paling sesuai untuk sedikit penyewa besar yang memerlukan pematuhan tinggi (contohnya B2B perusahaan).
Penghalaan Sambungan untuk Pangkalan Data bagi Setiap Penyewa
Aplikasi menyimpan pendaftaran yang memetakan penyewa → URL pangkalan data serta cache enjin/kumpulan. Kebergantungan menentukan penyewa, mencari DSN-nya, lalu mengembalikan sesi yang terikat pada pangkalan data tersebut.
Menyimpan enjin dalam cache adalah penting: mencipta enjin baharu bagi setiap permintaan akan menghabiskan sambungan. Simpan satu enjin bagi setiap penyewa dan gunakan semula kumpulan sambungannya.
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 sessionSemakan Pantas: Memilih Model Pengasingan
Sebuah syarikat pemula B2B menjangka akan mendaftarkan 5,000 penyewa kecil pada tahun pertama. Mereka mahukan kos infrastruktur terendah dan proses migrasi paling mudah, serta sanggup melabur dalam disiplin penetapan skop pertanyaan yang kukuh bersama perlindungan sandaran yang dikuatkuasakan pangkalan data. Model pengasingan manakah yang paling sesuai?
Imbas Kembali: Memilih Strategi Pengasingan yang Tepat
Tiga model pada satu spektrum daripada murah dan dikongsi kepada mahal dan terasing:
- Skema dikongsi — satu lajur
tenant_id. Paling murah, boleh diskalakan kepada ribuan penyewa kecil, satu migrasi. Risiko: pengasingan logik sahaja; kurangkan risiko dengan repositori penetapan skop serta RLS. - Skema bagi setiap penyewa — satu skema bagi setiap penyewa melalui
search_path. Pengasingan lebih kukuh, sandaran/pembuangan bagi setiap penyewa mudah. Kos: migrasi dijalankan N kali; terhad kepada ratusan penyewa. - Pangkalan data bagi setiap penyewa — satu pangkalan data bagi setiap penyewa, dengan enjin dicache bagi setiap penyewa. Pengasingan dan kawalan lokasi data paling kukuh. Kos: operasi/infrastruktur tertinggi; paling sesuai untuk sedikit penyewa besar yang memerlukan pematuhan tinggi.
Faktor penentu: bilangan dan saiz penyewa, keperluan pematuhan/lokasi data, toleransi migrasi, dan belanjawan. Banyak sistem sebenar bersifat hibrid — skema dikongsi untuk pelanggan kecil yang ramai, pangkalan data bagi setiap penyewa untuk akaun perusahaan. Dalam FastAPI, titik penyambung bagi ketiga-tiganya sama: kebergantungan penentuan penyewa yang menyuntik sesi yang betul.
Pelajari Kem Intensif Pembangunan Bahagian Belakang FastAPI dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 21
- Pelajaran
- 84
Soalan Lazim
Adakah pelajaran “Strategi Pengasingan Penyewa dan Implikasinya” percuma?
Ya — sebanyak 3 pelajaran dalam laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, termasuk “Strategi Pengasingan Penyewa dan Implikasinya”, boleh dibaca sepenuhnya secara percuma di web ini. Selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan interaktif dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Strategi Pengasingan Penyewa dan Implikasinya”?
Bandingkan model pengasingan skema dikongsi, skema mengikut penyewa dan pangkalan data mengikut penyewa untuk beban kerja SaaS. Anda berlatih Kem Intensif Pembangunan Bahagian Belakang FastAPI menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Kem Intensif Pembangunan Bahagian Belakang FastAPI?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 1 daripada 4.
Berapa lamakah pelajaran “Strategi Pengasingan Penyewa dan Implikasinya” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI ini?
Ya. Setiap pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Strategi Pengasingan Penyewa dan Implikasinya
- Penyelesaian Konteks Penyewa dan Middleware
- Keselamatan Tahap Baris dan Pembahagian Data
- Pengukuran Penggunaan, Kuota dan Cangkuk Pengebilan