Multi-tenancy & SaaS Patterns

02.07.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 25 daqiqa o'qish

Multi-tenancy - bitta application bir nechta mijoz, kompaniya yoki organization’ga xizmat qilishi, lekin ularning ma’lumotlari bir-biridan ajratilgan bo‘lishi degani.

SaaS’da tenant odatda bitta mijoz tashkilot bo‘ladi:

Tenant A = Najot Ta'lim
Tenant B = PDP Academy
Tenant C = IT Park

Bitta system:

app.company.com

lekin ichida har tenant alohida ko‘rinadi:

najot.company.com
pdp.company.com
itpark.company.com

Bu mavzu quyidagilarni o‘z ichiga oladi: schema-per-tenant vs DB-per-tenant, TenantContext propagation, Dynamic DataSource routing, Hibernate multi-tenancy strategies, va tenant-aware caching.


1. Multi-tenancy nima uchun kerak?

Agar biz SaaS product qilsangiz, har bir mijoz uchun alohida application ko‘tarish qimmat:

client-a-app
client-a-db

client-b-app
client-b-db

client-c-app
client-c-db

Bu modelda:

  • deployment ko‘payadi

  • monitoring murakkablashadi

  • upgrade qilish qiyinlashadi

  • infra xarajat oshadi

  • bug fix hamma joyga alohida chiqadi

Multi-tenant SaaS’da esa:

bitta app cluster
ko‘p tenant
tenant bo‘yicha data isolation
bitta platform
bitta release pipeline

2. Tenant nima?

Tenant - systemdan mustaqil foydalanuvchi tashkilot sifatida foydalanadigan birlik.

Masalan:

Tenant = company
Tenant = school
Tenant = marketplace seller
Tenant = bank branch
Tenant = franchise
Tenant = organization

Application ichida deyarli hamma narsa tenant bilan bog‘lanadi:

users
orders
payments
products
roles
settings
subscriptions
invoices
audit logs

3. Tenant aniqlash usullari

Request kelganda system birinchi bo‘lib so‘raydi:

Bu request qaysi tenantga tegishli?

Tenant quyidagi joylardan olinishi mumkin.


1. Subdomain orqali

najot.myapp.com
pdp.myapp.com

Bu SaaS’da juda ko‘p ishlatiladi.

Afzallik:

tenant aniq
URL chiroyli
branding uchun qulay

Kamchilik:

DNS/wildcard certificate kerak
local development biroz murakkab

2. Path orqali

/api/tenants/najot/orders
/api/tenants/pdp/orders

Afzallik:

oddiy
test qilish oson
gateway bilan oson

Kamchilik:

URL har doim tenant bilan ifloslanadi
xavfsizlikda ehtiyot kerak

3. Header orqali

X-Tenant-Id: najot

Afzallik:

internal API uchun qulay
mobile/backend communication uchun yaxshi

Kamchilik:

client headerni qalbakilashtirishi mumkin
auth bilan tekshirish shart

4. JWT claim orqali

{
  "sub": "user-123",
  "tenant_id": "najot",
  "roles": ["ADMIN"]
}

Bu eng xavfsiz variantlardan biri, agar token ishonchli identity provider tomonidan berilgan bo‘lsa.

Spring Security resource server multi-tenancy hujjatida multi-tenant resource server bir nechta bearer token verification strategiyasiga ega bo‘lishi va odatda tenant’ni resolve qilish hamda propagate qilish kerakligi aytiladi. (Home)


4. Eng muhim qoida: tenantni clientdan ko‘r-ko‘rona olmaslik

Yomon:

X-Tenant-Id: pdp
Authorization: user najot tenantiga tegishli

Agar system faqat headerga ishonsa, user boshqa tenant ma’lumotiga kira olishi mumkin.

To‘g‘ri yondashuv:

1. User token tekshiriladi
2. Token ichidagi tenant yoki user membership tekshiriladi
3. Requestdagi tenant shu userga ruxsatlimi - tekshiriladi
4. Keyin TenantContext o‘rnatiladi

Ya’ni:

Tenant resolution - security decision bilan bog‘liq.


5. Multi-tenancy data isolation modellari

Asosiy 3 ta model bor:

1. Shared database + tenant_id column
2. Schema-per-tenant
3. Database-per-tenant

6. Shared database + tenant_id column

Bitta database, bitta schema, har table’da tenant_id bor.

orders table
 ├── id
 ├── tenant_id
 ├── customer_id
 ├── amount
 └── status

Misol:

id

tenant_id

amount

1

najot

100000

2

pdp

250000

3

najot

90000

Query:

SELECT *
FROM orders
WHERE tenant_id = 'najot';

Afzalliklari

eng sodda
eng arzon
bitta migration
bitta connection pool
analytics/report qilish osonroq
kichik/medium SaaS uchun yaxshi

Kamchiliklari

data leak xavfi yuqori
har query tenant filter talab qiladi
katta tenant boshqa tenantlarga ta’sir qilishi mumkin
backup/restore tenant bo‘yicha qiyin
compliance talablariga mos kelmasligi mumkin

Eng katta xavf:

SELECT * FROM orders;

Agar tenant_id filter unutilsa, hamma tenant data chiqadi.


7. Shared DB modelda himoya

1. Har table’da tenant_id

CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    tenant_id VARCHAR(64) NOT NULL,
    amount NUMERIC NOT NULL
);

2. Composite index

CREATE INDEX idx_orders_tenant_id_id
ON orders (tenant_id, id);

3. Unique constraint tenant bilan

Yomon:

CREATE UNIQUE INDEX ux_users_email
ON users (email);

Bu global email unique qiladi.

Yaxshi:

CREATE UNIQUE INDEX ux_users_tenant_email
ON users (tenant_id, email);

Chunki turli tenantlarda bir xil email bo‘lishi mumkin yoki bo‘lmasligi biznes qaroriga bog‘liq.


8. Schema-per-tenant

Bitta database, lekin har tenant alohida schema’da.

database: saas_db

schema: tenant_najot
 ├── orders
 ├── users
 └── payments

schema: tenant_pdp
 ├── orders
 ├── users
 └── payments

Query:

SELECT * FROM tenant_najot.orders;

yoki connection uchun current schema o‘rnatiladi.


Afzalliklari

data isolation shared-table modeldan kuchliroq
tenant bo‘yicha backup/restore osonroq
bitta DB server ichida boshqariladi
ba’zi compliance holatlariga mosroq

Kamchiliklari

migration har schema uchun yuradi
schema soni ko‘payganda boshqarish qiyinlashadi
connection/session current schema to‘g‘ri o‘rnatilishi shart
cross-tenant reporting murakkabroq

9. Database-per-tenant

Har tenant uchun alohida database.

tenant_najot_db
tenant_pdp_db
tenant_itpark_db

Application requestga qarab kerakli database’ga ulanadi.


Afzalliklari

eng kuchli isolation
tenant bo‘yicha backup/restore oson
katta tenantni alohida scale qilish mumkin
compliance uchun yaxshi
noisy neighbor muammosi kamayadi

Kamchiliklari

eng qimmat
ko‘p DataSource/connection pool kerak
migration murakkab
monitoring murakkab
yangi tenant provisioning murakkab
cross-tenant analytics qiyin

10. Model tanlash jadvali

Model

Isolation

Cost

Complexity

Qachon yaxshi

Shared DB + tenant_id

Pastroq

Past

Past/O‘rta

Kichik/medium SaaS

Schema-per-tenant

O‘rta/Yuqori

O‘rta

O‘rta/Yuqori

B2B SaaS, o‘rta isolation

DB-per-tenant

Yuqori

Yuqori

Yuqori

Enterprise, compliance, yirik tenantlar

Arxitektor qaror:

Agar tenantlar ko‘p va kichik bo‘lsa → shared DB + tenant_id
Agar B2B va isolation muhim bo‘lsa → schema-per-tenant
Agar enterprise/compliance talab kuchli bo‘lsa → DB-per-tenant

11. Hybrid model

Real hayotda ko‘pincha hybrid bo‘ladi:

small tenants → shared database
enterprise tenants → dedicated database
regulated tenants → dedicated cluster

Masalan:

Free/Starter plan      → shared DB
Business plan          → schema-per-tenant
Enterprise/Government  → DB-per-tenant

Bu SaaS monetization bilan ham bog‘liq.


12. TenantContext nima?

TenantContext - hozirgi request qaysi tenantga tegishli ekanini application ichida saqlash usuli.

Imperative Spring MVC’da ko‘p hollarda ThreadLocal ishlatiladi:

public final class TenantContext {

    private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();

    private TenantContext() {}

    public static void setTenantId(String tenantId) {
        CURRENT_TENANT.set(tenantId);
    }

    public static String getTenantId() {
        return CURRENT_TENANT.get();
    }

    public static void clear() {
        CURRENT_TENANT.remove();
    }
}

13. Tenant filter

Request boshida tenant aniqlanadi:

@Component
public class TenantFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {

        try {
            String tenantId = resolveTenant(request);

            // security check: user shu tenantga tegishlimi?
            TenantContext.setTenantId(tenantId);

            filterChain.doFilter(request, response);
        } finally {
            TenantContext.clear();
        }
    }

    private String resolveTenant(HttpServletRequest request) {
        return request.getHeader("X-Tenant-Id");
    }
}

Eng muhim joy:

finally {
    TenantContext.clear();
}

Agar clear() qilinmasa, thread pool sabab keyingi request eski tenant bilan ishlashi mumkin.


14. Dynamic DataSource Routing

DB-per-tenant yoki ba’zi schema-per-tenant holatlarida requestga qarab DataSource tanlanadi.

Spring’da bu uchun AbstractRoutingDataSource ishlatiladi. Uning determineCurrentLookupKey() metodi odatda thread-bound context’dan lookup key olish uchun implement qilinadi. (Home)

Misol:

public class TenantRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContext.getTenantId();
    }
}

Config:

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        Map<Object, Object> dataSources = new HashMap<>();

        dataSources.put("najot", najotDataSource());
        dataSources.put("pdp", pdpDataSource());

        TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource();
        routingDataSource.setTargetDataSources(dataSources);
        routingDataSource.setDefaultTargetDataSource(defaultDataSource());

        return routingDataSource;
    }
}

Flow:

Request keldi
   ↓
TenantFilter tenantId aniqladi
   ↓
TenantContext.set("najot")
   ↓
Repository query ishladi
   ↓
AbstractRoutingDataSource determineCurrentLookupKey()
   ↓
najot DataSource tanlandi

15. Dynamic DataSource’da katta ehtiyot

Muammo 1: Transaction oldin ochilib ketishi

Agar transaction boshlanganda tenant hali set qilinmagan bo‘lsa, noto‘g‘ri database tanlanadi.

To‘g‘ri tartib:

Security filter
Tenant resolution filter
Transaction
Repository

Muammo 2: Connection pool soni portlab ketadi

Agar 500 tenant va har birida Hikari pool bo‘lsa:

500 tenants × 10 connections = 5000 DB connections

Bu database’ni yiqitishi mumkin.

Yechimlar:

tenantlarni plan bo‘yicha ajratish
lazy DataSource creation
pool size limit
inactive tenant pool close
shared pool strategy
dedicated DB faqat enterprise tenantlarga

Muammo 3: Runtime tenant qo‘shish

Yangi tenant ro‘yxatdan o‘tsa, DataSource map yangilanishi kerak.

Oddiy setTargetDataSources static map bilan qiyin bo‘lishi mumkin.

Production’da ko‘pincha:

TenantRegistry
DataSourceProvider
cache with TTL
dynamic pool creation
tenant provisioning workflow

kerak bo‘ladi.


16. Hibernate multi-tenancy strategies

Hibernate multi-tenancy uchun asosan quyidagi yondashuvlar ishlatiladi:

DATABASE
SCHEMA
DISCRIMINATOR / tenant_id

Hibernate hujjatlarida multi-tenancy support second-level cache bilan ishlashi, cache key ichida tenant identifier kodlanishi aytiladi. (docs.hibernate.org)


DATABASE strategy

Har tenant alohida database connection oladi.

tenant_id → database

Yaxshi:

isolation kuchli
enterprise tenantlar uchun yaxshi

Qiyin:

ko‘p connection pool
migration murakkab
tenant provisioning murakkab

SCHEMA strategy

Bitta database, har tenant alohida schema.

tenant_id → schema

Implementation:

MultiTenantConnectionProvider
CurrentTenantIdentifierResolver

CurrentTenantIdentifierResolver current tenantni beradi.


DISCRIMINATOR strategy

Bitta table ichida tenant_id column.

tenant_id → row filter

Bu eng arzon, lekin eng ehtiyot talab qiladigan model.


17. Tenant-aware caching

Cache multi-tenancy’da juda xavfli joy.

Yomon cache key:

@Cacheable(cacheNames = "orders", key = "#orderId")
public Order getOrder(Long orderId) {
    ...
}

Agar orderId = 10 ikki tenantda ham bo‘lsa:

najot order 10
pdp order 10

cache noto‘g‘ri tenant data qaytarishi mumkin.

Yaxshi:

@Cacheable(
    cacheNames = "orders",
    key = "T(com.company.TenantContext).getTenantId() + ':' + #orderId"
)
public Order getOrder(Long orderId) {
    ...
}

Yoki custom KeyGenerator:

@Component("tenantAwareKeyGenerator")
public class TenantAwareKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        return TenantContext.getTenantId() + ":" +
                method.getName() + ":" +
                Arrays.deepToString(params);
    }
}

Usage:

@Cacheable(
    cacheNames = "orders",
    keyGenerator = "tenantAwareKeyGenerator"
)
public Order getOrder(Long orderId) {
    ...
}

Qoida:

Cache key ichida tenant bo‘lmasa, multi-tenant system’da data leak xavfi bor.


18. Tenant-aware authorization

Multi-tenancy faqat database emas. Authorization ham tenant-aware bo‘lishi kerak.

Masalan user:

Ali → najot tenantida ADMIN
Ali → pdp tenantida VIEWER

Demak role global emas, tenantga bog‘langan.

Yomon model:

user_roles:
ali = ADMIN

Yaxshi model:

tenant_user_roles:
ali + najot = ADMIN
ali + pdp = VIEWER

Spring Security method-level authorization annotationlar orqali method, class va interface darajasida authorization qo‘llash imkonini beradi. (Home)

Masalan:

@PreAuthorize("@tenantSecurity.canAccessOrder(#orderId, authentication)")
public OrderDto getOrder(Long orderId) {
    ...
}

19. TenantContext propagation: @Async, scheduler, messaging

TenantContext faqat HTTP requestda emas, boshqa joylarda ham kerak bo‘ladi:

@Async
Kafka listener
Rabbit listener
Scheduled job
Domain event listener
WebFlux reactive flow
Batch job

@Async muammosi

ThreadLocal boshqa threadda avtomatik o‘tmaydi.

@Async
public void sendEmail() {
    TenantContext.getTenantId(); // null bo‘lishi mumkin
}

Yechim:

TaskDecorator orqali tenantni ko‘chirish

Misol:

public class TenantTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        String tenantId = TenantContext.getTenantId();

        return () -> {
            try {
                TenantContext.setTenantId(tenantId);
                runnable.run();
            } finally {
                TenantContext.clear();
            }
        };
    }
}

Executor config:

@Bean
public ThreadPoolTaskExecutor applicationTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setTaskDecorator(new TenantTaskDecorator());
    return executor;
}

20. Reactive WebFlux’da TenantContext

WebFlux’da ThreadLocal xavfli, chunki request bitta thread’da boshlanib boshqa thread’da davom etishi mumkin.

Reactor’da context uchun Context ishlatiladi. Reactor hujjatlarida contextCapture operatori ThreadLocal qiymatlarini subscription vaqtida capture qilib Reactor Context’ga o‘tkazishi mumkinligi aytiladi. (projectreactor.io) Micrometer Context Propagation hujjatida context qiymatlari Reactor context va ThreadLocal orasida ko‘chirilishi mumkinligi ko‘rsatiladi. (Micrometer Documentation)

Reactive yondashuv:

return chain.filter(exchange)
        .contextWrite(context -> context.put("tenantId", tenantId));

Keyin:

Mono.deferContextual(ctx -> {
    String tenantId = ctx.get("tenantId");
    return service.process(tenantId);
});

Qoida:

Spring MVC → ThreadLocal ishlashi mumkin
Spring WebFlux → Reactor Context ishlatish kerak

21. Kafka/Rabbit message’da tenant

Event yoki message yuborilganda tenant ham ketishi kerak.

Yomon:

{
  "orderId": 123,
  "status": "PAID"
}

Yaxshi:

{
  "tenantId": "najot",
  "orderId": 123,
  "status": "PAID"
}

Yoki message header:

X-Tenant-Id: najot

Consumer:

@KafkaListener(topics = "orders")
public void listen(OrderPaidEvent event) {
    try {
        TenantContext.setTenantId(event.tenantId());
        orderService.handlePaid(event);
    } finally {
        TenantContext.clear();
    }
}

Qoida:

Async message’da tenant bo‘lmasa, consumer qaysi tenant database yoki cache bilan ishlashini bilmaydi.


22. Tenant provisioning

SaaS’da yangi tenant yaratish oddiy insert emas. Bu workflow.

Masalan:

1. Tenant record yaratish
2. Plan/subscription tanlash
3. DB/schema yaratish
4. Migration yuritish
5. Default roles yaratish
6. Admin user yaratish
7. Default settings yaratish
8. DNS/subdomain sozlash
9. Welcome email yuborish
10. Tenant status ACTIVE qilish

Tenant state:

CREATING
MIGRATING
ACTIVE
SUSPENDED
DELETING
FAILED

Agar provisioning o‘rtada yiqilsa, retry va cleanup kerak.


23. Migration strategy

Shared DB

Migration oddiyroq:

Flyway/Liquibase bitta database uchun yuradi

Schema-per-tenant

Migration har schema uchun yurishi kerak:

tenant_najot → V1, V2, V3
tenant_pdp   → V1, V2, V3
tenant_itpark → V1, V2, V3

DB-per-tenant

Migration har database uchun yuradi:

tenant_najot_db
tenant_pdp_db
tenant_itpark_db

Bu yerda migration orchestration kerak:

qaysi tenant qaysi versionda?
migration failed bo‘lsa nima bo‘ladi?
rolling migration mumkinmi?
downtime kerakmi?

24. Tenant-aware observability

Log, metric va trace tenant-aware bo‘lishi kerak, lekin ehtiyot bilan.

Log:

{
  "service": "order-service",
  "tenantId": "najot",
  "traceId": "abc123",
  "message": "Order created"
}

Metrics’da esa tenant label ehtiyot talab qiladi.

Yomon:

http_requests_total{tenantId="tenant-123456"}

Agar tenantlar juda ko‘p bo‘lsa, metric cardinality portlab ketadi.

Yaxshi:

tenant_tier="enterprise"
region="uz"

yoki faqat enterprise tenantlar uchun alohida metric.

Qoida:

Loglarda tenantId foydali. Metrics label’da tenantId ehtiyot bilan ishlatiladi.


25. Tenant-aware rate limiting

SaaS’da har tenantga limit bo‘lishi mumkin:

Free plan: 100 req/min
Business: 1000 req/min
Enterprise: custom

Rate limit key:

tenantId + endpoint

Masalan:

najot:/api/orders → 1000 req/min
pdp:/api/orders   → 100 req/min

Bu noisy neighbor muammosini kamaytiradi.


26. Tenant-aware feature flags

SaaS’da feature tenant bo‘yicha yoqiladi:

tenant najot → AI_REPORTS enabled
tenant pdp → AI_REPORTS disabled
tenant enterprise-x → BETA_CHECKOUT enabled

Model:

tenant_features
 ├── tenant_id
 ├── feature_key
 └── enabled

Feature flag check:

if (featureFlags.isEnabled("AI_REPORTS", TenantContext.getTenantId())) {
    // new feature
}

27. Tenant configuration

Har tenantda alohida settings bo‘lishi mumkin:

branding
currency
timezone
language
payment provider
notification templates
tax rules
invoice prefix
SLA plan

Bu settings cache qilinadi, lekin cache tenant-aware bo‘lishi shart.


28. Data deletion va compliance

Multi-tenant SaaS’da tenant o‘chirish katta masala.

Shared DB’da:

DELETE FROM orders WHERE tenant_id = 'najot';
DELETE FROM users WHERE tenant_id = 'najot';
DELETE FROM payments WHERE tenant_id = 'najot';

Bu xavfli va murakkab.

DB-per-tenant’da:

drop database tenant_najot_db

ancha toza, lekin backup retention va audit talablarini hisobga olish kerak.

Arxitektor qaror:

Agar tenant offboarding, compliance, data residency muhim bo‘lsa, DB-per-tenant yoki schema-per-tenant kuchliroq.


29. Data residency

Ba’zi tenantlar data ma’lum regionda turishini talab qiladi:

UZ region
EU region
US region

Bu holda tenant registry kerak:

tenant_id → region → cluster/database

Masalan:

najot → uz-central → db-uz-1
enterprise-eu → eu-west → db-eu-2

Request routing:

Gateway tenantni aniqlaydi
tenant regionni topadi
tegishli clusterga yo‘naltiradi

30. Real structure: Spring Boot SaaS

com.company.saas
 ├── tenant
 │    ├── Tenant.java
 │    ├── TenantRegistry.java
 │    ├── TenantContext.java
 │    ├── TenantFilter.java
 │    └── TenantProvisioningService.java
 │
 ├── order
 │    ├── api
 │    ├── application
 │    ├── domain
 │    └── adapter
 │
 ├── payment
 │    ├── api
 │    ├── application
 │    ├── domain
 │    └── adapter
 │
 └── platform
      ├── security
      ├── datasource
      ├── cache
      └── observability

tenant module infrastructure emas, core platform module bo‘ladi.


31. Minimal shared DB implementation

Entity:

@MappedSuperclass
public abstract class TenantAwareEntity {

    @Column(name = "tenant_id", nullable = false, updatable = false)
    private String tenantId;

    @PrePersist
    void assignTenant() {
        this.tenantId = TenantContext.getTenantId();
    }

    public String getTenantId() {
        return tenantId;
    }
}

Order:

@Entity
@Table(name = "orders")
public class OrderEntity extends TenantAwareEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private BigDecimal amount;
}

Lekin bu yetarli emas. Read querylarda ham tenant filter bo‘lishi kerak.

Repository method:

List<OrderEntity> findByTenantIdAndStatus(String tenantId, OrderStatus status);

Yoki specification/global filter yondashuvi ishlatiladi.


32. Eng xavfli xatolar

Xato 1: Tenant filter unutilishi

repository.findById(orderId);

Yomon, chunki orderId boshqa tenantga tegishli bo‘lishi mumkin.

Yaxshi:

repository.findByIdAndTenantId(orderId, TenantContext.getTenantId());

Xato 2: Cache key’da tenant yo‘q

key = "#id"

Yomon.

Yaxshi:

key = "tenantId + ':' + #id"

Xato 3: TenantContext clear qilinmaydi

Thread pool sabab bir tenant ikkinchi tenant requestiga sizib o‘tishi mumkin.


Xato 4: Headerga ko‘r-ko‘rona ishonish

X-Tenant-Id har doim token/user membership bilan tekshirilishi kerak.


Xato 5: Migration strategy oldindan o‘ylanmagan

Tenantlar ko‘paygandan keyin schema yoki database migration juda og‘ir bo‘ladi.


Xato 6: Metrics’da tenantId label qilish

Tenantlar ko‘p bo‘lsa, monitoring system cardinality muammosiga tushadi.


33. Arxitektor decision checklist

Multi-tenancy tanlashdan oldin savollar:

Tenantlar soni qancha bo‘ladi?
Har tenant data hajmi qancha?
Enterprise tenant bormi?
Compliance talablari bormi?
Data residency kerakmi?
Tenant bo‘yicha backup/restore kerakmi?
Cross-tenant analytics kerakmi?
Har tenant alohida customization qiladimi?
Rate limit tenant bo‘yicha bo‘ladimi?
Tenant offboarding qanday ishlaydi?

34. Qisqa tavsiya

Startup SaaS uchun

Shared DB + tenant_id
strict repository convention
tenant-aware cache
tenant-aware auth
strong tests

B2B SaaS uchun

Schema-per-tenant
tenant provisioning workflow
migration orchestration
tenant-aware observability

Enterprise SaaS uchun

Hybrid model
enterprise tenants → DB-per-tenant
small tenants → shared/schema
data residency
dedicated backup/restore

35. Arxitektor xulosasi

Multi-tenancy & SaaS patterns - bu faqat tenant_id column qo‘shish emas.

Bu quyidagi qarorlar majmuasi:

tenant qanday aniqlanadi?
tenant security qanday tekshiriladi?
data isolation qaysi modelda bo‘ladi?
cache tenant-awaremi?
async flow’da tenant yo‘qolmaydimi?
migration qanday yuradi?
tenant provisioning qanday ishlaydi?
observability tenantni ko‘rsatadimi?
enterprise tenantlar qanday ajratiladi?

Eng muhim qoida:

Multi-tenant system’da har bir query, cache, message, log, permission va background job tenant kontekstini hisobga olishi kerak.

Qisqa formula:

Tenant resolution
+ Tenant authorization
+ Tenant context propagation
+ Tenant data isolation
+ Tenant-aware cache/security/observability
= Production-ready SaaS architecture