Budowa Multi-Tenant SaaS z .NET i Azure
Multi-tenancy to fundament opłacalnej ekonomii SaaS. Pojedyncza infrastruktura obsługująca tysiące klientów jest 10-20x bardziej opłacalna niż dedykowane instancje per klient. Według Bessemer Venture Partners Cloud Index, udane firmy SaaS osiągają marże brutto 70-80%, głównie dzięki efektywnym architekturom multi-tenant.

Projektowałem platformy SaaS multi-tenant obsługujące od 50 do 5,000+ tenant. Ten przewodnik omawia wzorce architektoniczne, kwestie bezpieczeństwa, strategie bazodanowe i implementacje specyficzne dla Azure, które sprawiają że multi-tenant SaaS działa na dużą skalę.
01Modele Multi-Tenancy
1. Wspólna baza danych, wspólny schemat
Wszyscy tenant dzielą tabele z kolumną dyskryminatora TenantId. Najprostszy i najbardziej opłacalny model. Idealny dla platform korzystających z baz danych PostgreSQL, które oferują doskonałą wydajność dla dużej liczby tenant.
✓ Zalety
- • Najniższy koszt infrastruktury
- • Proste wdrożenia
- • Łatwe backupy i utrzymanie
- • Efektywne wykorzystanie zasobów
- • Proste skalowanie horyzontalne
✗ Wady
- • Ryzyko wycieku danych jeśli filtr pominięty
- • Problemy noisy neighbor
- • Ograniczona customizacja per-tenant
- • Skomplikowany recovery tenant
- • Wyzwania compliance
Najlepszy dla:
SMB SaaS z 100-10,000 tenant, podobne wzorce użycia, niskie wymagania compliance. Przykłady: narzędzia do zarządzania projektami, CRM dla małych firm.
2. Wspólna baza danych, osobne schematy
Pojedyncza baza danych ze schematem-per-tenant (schematy PostgreSQL lub SQL Server). Umiarkowana izolacja.
✓ Zalety
- • Lepsza izolacja tenant
- • Uprawnienia na poziomie schematu
- • Łatwiejszy backup/restore tenant
- • Dobra równowaga koszt-izolacja
✗ Wady
- • Skomplikowane migracje schematu
- • Overhead połączeń do bazy
- • Ograniczona skalowalność (max kilka tysięcy schematów)
- • Wolniejsze onboardowanie tenant
Najlepszy dla:
B2B SaaS dla średnich firm z 10-1,000 tenant, umiarkowane wolumeny danych per tenant, niektóre potrzeby compliance. Przykłady: vertical SaaS dla healthcare, legal.
3. Osobne bazy danych (Database-per-Tenant)
Każdy tenant dostaje dedykowaną bazę danych. Maksymalna izolacja i customizacja.
✓ Zalety
- • Maksymalna izolacja bezpieczeństwa
- • Łatwy backup/restore tenant
- • Customizacja per-tenant
- • Przyjazny dla compliance
- • Izolacja noisy neighbor
✗ Wady
- • Wysoki koszt infrastruktury
- • Skomplikowane wdrożenia
- • Overhead migracji schematu
- • Złożoność zarządzania
- • Trudna analityka cross-tenant
Najlepszy dla:
Enterprise B2B SaaS z wymaganiami regulacyjnymi, duże dane per tenant, customowe SLA. Przykłady: platformy HR, SaaS dla finansów.
4. Model hybrydowy (Rekomendowany dla wzrostu)
Łącz modele w zależności od warstwy tenant: wspólna baza dla SMB, dedykowane bazy dla enterprise.
Warstwa 1 (Free/Starter): Wspólna baza, wspólny schemat, 1,000+ tenant
Warstwa 2 (Professional): Wspólna baza, osobne schematy, 100-500 tenant
Warstwa 3 (Enterprise): Dedykowane bazy, 10-50 tenant
To maksymalizuje efektywność kosztową dla małych klientów jednocześnie oferując izolację dla klientów enterprise gotowych płacić premium.
02Implementacja z .NET i EF Core

Middleware rozpoznawania Tenant
Kluczowym elementem każdej platformy multi-tenant jest poprawne rozpoznawanie tenant. W nowoczesnej platformie .NET middleware stanowi idealny punkt do implementacji tej logiki.
// Strategie identyfikacji tenant
public class TenantResolutionMiddleware
{
public async Task InvokeAsync(HttpContext context, ITenantService tenantService)
{
// Strategia 1: Subdomena (tenant1.app.com)
var host = context.Request.Host.Host;
var tenantId = ExtractTenantFromSubdomain(host);
// Strategia 2: Header (X-Tenant-ID)
if (string.IsNullOrEmpty(tenantId))
tenantId = context.Request.Headers["X-Tenant-ID"];
// Strategia 3: JWT Claim
if (string.IsNullOrEmpty(tenantId) && context.User.Identity?.IsAuthenticated == true)
tenantId = context.User.FindFirst("tenant_id")?.Value;
// KRYTYCZNE: Nigdy nie ufaj tenant ID wysłanym przez klienta dla dostępu do danych
// Zawsze waliduj względem dozwolonych tenant uwierzytelnionego użytkownika
var tenant = await tenantService.GetAndValidateTenantAsync(tenantId, context.User);
context.Items["TenantId"] = tenant.Id;
}
}Globalne filtry zapytań (EF Core)
Automatycznie filtruj wszystkie zapytania po tenant żeby zapobiec wyciekom danych:
public class ApplicationDbContext : DbContext
{
private readonly ITenantService _tenantService;
public ApplicationDbContext(DbContextOptions options, ITenantService tenantService)
: base(options)
{
_tenantService = tenantService;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Zastosuj globalny filtr zapytań do wszystkich encji w zakresie tenant
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(ITenantEntity).IsAssignableFrom(entityType.ClrType))
{
var method = typeof(ApplicationDbContext)
.GetMethod(nameof(SetGlobalQueryFilter), BindingFlags.NonPublic | BindingFlags.Static)
?.MakeGenericMethod(entityType.ClrType);
method?.Invoke(null, new object[] { modelBuilder, _tenantService });
}
}
}
private static void SetGlobalQueryFilter<T>(ModelBuilder modelBuilder, ITenantService tenantService)
where T : class, ITenantEntity
{
modelBuilder.Entity<T>().HasQueryFilter(e => e.TenantId == tenantService.GetCurrentTenantId());
}
}
// Interfejs encji
public interface ITenantEntity
{
string TenantId { get; set; }
}
// Przykładowa encja
public class Order : ITenantEntity
{
public int Id { get; set; }
public string TenantId { get; set; } // Automatycznie filtrowane
public decimal Amount { get; set; }
}Database-per-Tenant z rozpoznawaniem Connection String
public class TenantDbContextFactory
{
private readonly ITenantService _tenantService;
private readonly IConfiguration _configuration;
public ApplicationDbContext Create()
{
var tenantId = _tenantService.GetCurrentTenantId();
var connectionString = GetConnectionStringForTenant(tenantId);
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseNpgsql(connectionString);
return new ApplicationDbContext(optionsBuilder.Options);
}
private string GetConnectionStringForTenant(string tenantId)
{
// Opcja 1: Dedykowana baza per tenant
return $"Host=postgres.app.com;Database=tenant_{tenantId};Username=app;Password=...";
// Opcja 2: Azure SQL Elastic Pool z bazami
// return $"Server=tcp:app.database.windows.net;Database=tenant_{tenantId};...";
// Opcja 3: Wyszukiwanie z tabeli konfiguracji tenant
// var tenant = await _tenantRepo.GetAsync(tenantId);
// return tenant.ConnectionString;
}
}03Usługi Azure dla Multi-Tenant SaaS

Przy wdrożeniu w Azure Kubernetes lub tradycyjnym App Service, Azure oferuje szereg usług idealnych dla platform multi-tenant. Kluczem jest optymalizacja kosztów chmury poprzez odpowiedni dobór warstw i auto-scaling.
Azure SQL Elastic Pools
Dziel zasoby obliczeniowe między wieloma bazami danych tenant zachowując izolację.
S3 Elastic Pool
100 eDTU, $200/miesiąc
~50 małych baz (2 eDTU każda)
S6 Elastic Pool
400 eDTU, $800/miesiąc
~100 średnich baz (4 eDTU śr.)
S12 Elastic Pool
3000 eDTU, $6,000/miesiąc
~500 baz z burstingiem
Azure AD B2C dla Multi-Tenant Auth
Zarządzaj użytkownikami tenant z Azure AD B2C: custom policies, social login, MFA, user flows.
- • Przechowuj kontekst tenant w claimach JWT
- • Wspieraj provisioning użytkowników oparty na zaproszeniach
- • Włącz SSO dla tenant enterprise
- • Ceny: Pierwsze 50K MAU za darmo, potem $0.00325/MAU
Application Insights - Telemetria Per-Tenant
// Dodaj kontekst tenant do całej telemetrii
services.AddApplicationInsightsTelemetry();
services.AddSingleton<ITelemetryInitializer, TenantTelemetryInitializer>();
public class TenantTelemetryInitializer : ITelemetryInitializer
{
private readonly ITenantService _tenantService;
public void Initialize(ITelemetry telemetry)
{
if (telemetry is ISupportProperties propertiesTelemetry)
{
propertiesTelemetry.Properties["TenantId"] = _tenantService.GetCurrentTenantId();
propertiesTelemetry.Properties["TenantName"] = _tenantService.GetCurrentTenantName();
}
}
}
// Zapytanie w portalu Azure:
// requests | where customDimensions.TenantId == "tenant-123" | summarize count() by bin(timestamp, 1h)04Skalowanie i Wydajność

Skalowanie platformy multi-tenant wymaga przemyślanej strategii. W architekturze mikrousług każdy komponent można skalować niezależnie, co idealnie wpasowuje się w modele multi-tenant z różnym obciążeniem per tenant.
Connection Pooling
Problem: Database-per-tenant tworzy overhead połączeń
Rozwiązanie: Użyj poolingu opartego na connection string z limitami max pool size
// .NET connection string
"...;Max Pool Size=20;Min Pool Size=2;"20 połączeń × 100 baz = 2,000 max połączeń. Monitoruj z PgBouncer lub Azure SQL monitoring.
Cache Per-Tenant
Użyj Redis z kluczami z prefixem tenant:
var cacheKey = $"tenant:{tenantId}:orders:{orderId}";
await _cache.SetStringAsync(cacheKey, json,
new DistributedCacheEntryOptions {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});Umożliwia invalidację cache per-tenant i zapobiega zanieczyszczeniu cache cross-tenant.
Database Sharding
Dla 1,000+ tenant, sharduj między wiele serwerów bazodanowych:
- • Shard 1: Tenant 1-500 → postgres-01
- • Shard 2: Tenant 501-1000 → postgres-02
- • Routuj na podstawie consistent hashing tenant ID
- • Użyj Azure Database for PostgreSQL z read replicas
Zapobieganie Noisy Neighbor
Implementuj rate limiting per tenant:
[TenantRateLimit(MaxRequests = 1000, WindowMinutes = 1)]
public async Task<IActionResult> GetOrders()
{
// Tenant automatycznie limitowany do 1000 req/min
}Użyj Redis dla rozproszonego rate limiting między wieloma instancjami app.
05Wdrożenie w praktyce

B2B SaaS do zarządzania projektami
Wybory architektoniczne
- • Model hybrydowy: wspólna baza dla SMB, dedykowana dla enterprise
- • 850 SMB tenant we wspólnym PostgreSQL (2TB łącznie)
- • 15 tenant enterprise z dedykowanymi bazami
- • Azure App Service P1V3 z autoscaling (2-10 instancji)
- • Azure SQL Elastic Pool S9 (1600 eDTU)
- • Redis Premium P1 dla cache i rate limiting
- • Komunikacja real-time z SignalR dla współpracy zespołowej
Koszty miesięczne
Koszt per tenant: $9.50/miesiąc (850 SMB + 15 enterprise). MRR: $45K. Infrastruktura: 18% przychodu.
Kluczowe metryki po 18 miesiącach
99.97%
Uptime (SLA: 99.9%)
Zero
Wycieków danych cross-tenant
850 →
2,100 tenant (147% wzrost)
06FAQ
Który model multi-tenancy powinienem wybrać?
Wybierz na podstawie skali i potrzeb izolacji: wspólna baza/wspólny schemat dla 100-10,000 tenant (najniższy koszt), wspólna baza/osobne schematy dla 10-1,000 tenant (umiarkowana izolacja), osobne bazy dla tenant enterprise (maksymalna izolacja). Większość platform SaaS używa podejścia hybrydowego: wspólna baza dla SMB, dedykowana dla enterprise.
Jak obsługiwać customizacje specyficzne dla tenant?
Użyj podejścia opartego na konfiguracji: przechowuj preferencje tenant w tabelach metadanych, używaj flag funkcjonalności dla feature'ów per-tenant, implementuj architekturę plugin dla customowej logiki biznesowej. Unikaj branchy kodu specyficznych dla tenant. Zamiast tego użyj pattern strategy z dependency injection do wymiany implementacji per tenant.
Jakie są dobre praktyki bezpieczeństwa?
Kluczowe praktyki: (1) Nigdy nie ufaj tenant ID wysyłanym przez klienta, zawsze wyprowadzaj z uwierzytelnionej tożsamości, (2) Używaj globalnych filtrów zapytań EF Core żeby zapobiec wyciekom danych cross-tenant, (3) Implementuj middleware kontekstu tenant, (4) Szyfruj wrażliwe dane kluczami specyficznymi dla tenant, (5) Audituj operacje cross-tenant, (6) Testuj izolację tenant z automatycznymi testami, (7) Implementuj rate limiting per tenant.
Ile kosztuje prowadzenie na Azure?
Mały SaaS (100 tenant): $500-1,500/miesiąc. Średni SaaS (1,000 tenant): $3,000-8,000/miesiąc. Duży SaaS (10K+ tenant): $20K-50K+/miesiąc. Kluczem jest odpowiedni dobór warstwy i autoscaling. Użyj Azure Cost Management do monitoringu i reserved instances dla 40% oszczędności na przewidywalnych obciążeniach.
Czy mogę zmigrować z single-tenant do multi-tenant?
Tak, podejście fazowe: (1) Dodaj kontekst tenant do tabel, (2) Implementuj middleware rozpoznawania tenant, (3) Zaktualizuj kod żeby filtrować po tenant ID, (4) Skonsoliduj bazy z migracją danych, (5) Testuj pod kątem wycieków danych, (6) Uruchom równolegle dla walidacji. Spodziewaj się 3-6 miesięcy dla migracji produkcyjnej w zależności od wielkości codebase.
Budujesz Multi-Tenant SaaS?
Pomagam firmom SaaS projektować i implementować skalowalne architektury multi-tenant z .NET i Azure. Porozmawiajmy o Twojej architekturze SaaS.
Powiązane artykuły
Azure Kubernetes (AKS) - przewodnik produkcyjny
Konfiguracja AKS i skalowanie w środowisku produkcyjnym
Migracja SQL Server do PostgreSQL
Strategie i narzędzia do migracji baz danych
Przewodnik migracji .NET
Migracja z .NET Framework do .NET 10
Microservices vs Monolith
Framework decyzyjny architektoniczny
Źródła
- [1] Microsoft Azure - Oficjalna dokumentacja -https://learn.microsoft.com/en-us/azure/
- [2] Microsoft Learn - Centrum szkoleń Azure -https://learn.microsoft.com/en-us/training/azure/
- [3] Kubernetes - Oficjalna dokumentacja -https://kubernetes.io/docs/
- [4] CNCF Annual Survey 2023 - Stan adopcji Kubernetes -https://www.cncf.io/reports/cncf-annual-survey-2023/
- [5] .NET - Oficjalna dokumentacja Microsoft -https://learn.microsoft.com/en-us/dotnet/
- [6] .NET Blog - Najnowsze informacje i best practices -https://devblogs.microsoft.com/dotnet/
- [7] Flexera State of the Cloud Report 2024 -https://www.flexera.com/blog/cloud/cloud-computing-trends-2024-state-of-the-cloud-report/
- [8] FinOps Foundation - Best Practices -https://www.finops.org/
- [9] Gartner - Cloud Computing Research -https://www.gartner.com/en/information-technology/insights/cloud-computing
- [10] AWS - Oficjalna dokumentacja -https://docs.aws.amazon.com/