Multi-Tenant .NET: Cơ so du lieu dung chung voi phan vung

Phan 1 trong 3: Xay dung ung dung multi-tenant tren .NET voi co so du lieu dung chung, su dung bo loc truy van toan cuc cua EF Core va phan vung bang trong SQL Server de luu du lieu cua tung tenant vao mot phan vung rieng.

Hay tuong tuong mot dai san hoi dong duc vao dem dong nguoi. Hang chuc nhom phieu luu chen chuc quanh nhung ban dai, cung goi bia ruou va nhan vien phuc vu dang qua tai phai tiep nhan tat ca yeu cau. Chi co mot cuon so sau quay, noi danh sach thanh vien, vang bac va nhat ky nhiem vu cua moi nhom deu duoc ghi lai. Mo hinh nay van hoat dong, cho den khi no khong con hoat dong tot nua. Nhan vien phuc vu phai lat tung trang, vuot qua ho so cua tat ca nhom khac moi tim duoc thong tin cua ban.

Do la multi-tenancy tom gon: mot ung dung la dai san hoi, nhieu tenant la cac nhom phieu luu, va mot co so du lieu dung chung la cuon so phai vua tach rieng du lieu cua moi nhom, vua cho phep tim nhanh trang cua bat ky nhom nao ma khong can doc ca cuon so.

Trong loat bai gom ba phan nay, chung ta se xem xet nhieu cach thuc hien multi-tenancy bang Entity Framework Core tren .NET voi SQL Server lam kho du lieu.

Ba cach trien khai multi-tenancy

Truoc khi viet code, hay xem ba phuong phap pho bien nhat:

  • Co so du lieu dung chung, schema dung chung: Moi tenant dung cung mot schema trong cung mot co so du lieu. Cac bang chinh co them cot ID de xac dinh ban ghi thuoc tenant nao. Day la cach re nhat khi thiet lap va van hanh, de migration nhat, va cung la lua chon pho bien cua cac ung dung SaaS. Bai nay tap trung vao cach nay.
  • Moi tenant mot schema: Moi tenant co mot schema rieng trong cung mot co so du lieu vat ly. Day la phuong an trung gian, nhung se tro nen kho quan ly khi so luong tenant tang. Phan 2 se trinh bay cach nay.
  • Moi tenant mot co so du lieu: Moi tenant co mot co so du lieu rieng. Cach nay co muc do cach ly manh nhat, nhung ban phai quan ly so luong co so du lieu tuong ung voi so tenant, cung voi migration, sao luu va cac cong viec lien quan. Phan 3 se di sau vao phuong an nay.

Mot loi ich cua SQL Server la ta co the giam bot nhuc diem “du lieu cua moi nguoi bi tron lan” trong phuong an dau tien bang tinh nang phan vung.

Tuy nhien, can lam ro ngay tu dau: phan vung trong SQL Server khong phai la bien gioi bao mat. No khong cach ly tenant nay khoi tenant khac. Day la mot ky thuat luu tru vat ly de cai thien hieu nang. No tao ra mot tu do gom nhieu ngan va quyet dinh ban ghi nam trong ngan nao dua tren cau hinh. Chi co vay.

Co che cach ly thuc su, ngan Tenant A doc du lieu cua Tenant B, den tu chuc nang bo loc truy van cua Entity Framework trong .NET. Phan vung chi giup moi thu chay nhanh hon va de bao tri hon. Hay luon tach bach hai khai niem nay.

Xac dinh tenant

Moi thu phu thuoc vao viec ung dung biet tenant nao dang thuc hien yeu cau. Trong ung dung web, thong tin nay thuong den tu subdomain, header hoac claim cua nguoi dung da xac thuc. Ta se dong goi cach lay thong tin vao mot provider de phan con lai cua ung dung khong can quan tam nguon du lieu la gi:

public interface ITenantProvider<br>{<br>    int GuildId { get; }<br>}<br><br>public sealed class HttpTenantProvider : ITenantProvider<br>{<br>    public int GuildId { get; }<br><br>    public HttpTenantProvider(IHttpContextAccessor accessor)<br>    {<br>        // Thay bang cach ung dung cua ban dang su dung:<br>        // subdomain, custom header, JWT claim...<br>        var claim = accessor.HttpContext?.User.FindFirst("guild_id")?.Value;<br><br>        GuildId = int.TryParse(claim, out var id)<br>            ? id<br>            : throw new InvalidOperationException("No guild in scope for this request.");<br>    }<br>}

Hay dang ky provider voi lifetime scoped de no duoc tao moi lan co request. Dieu nay rat quan trong, tranh de cac request cua tenant nay lan sang tenant khac:

builder.Services.AddHttpContextAccessor();<br>builder.Services.AddScoped<ITenantProvider, HttpTenantProvider>();<br>builder.Services.AddDbContext<GuildDbContext>(options =><br>    options.UseSqlServer(builder.Configuration.GetConnectionString("Guild")));

DbContext cung duoc dat o lifetime scoped, de moi request nhan mot context da biet no dang phuc vu tenant nao. Khong dung bien static dung chung va khong can truyen tenant qua tung lan goi ham.

Chao mung, phieu luu

Day la entity thuoc ve tenant. Ta danh dau entity bang mot interface de sau nay co the tim cac doi tuong “thuoc ve mot tenant” theo cach tong quat hon:

public interface IBelongToGuild<br>{<br>    int GuildId { get; }<br>}<br><br>public class Adventurer : IBelongToGuild<br>{<br>    public int Id { get; set; }<br>    public int GuildId { get; set; }<br>    public string Name { get; set; } = string.Empty;<br>    public string Class { get; set; } = string.Empty;<br>    public int Level { get; set; }<br>}

Tiep theo la DbContext chua bo loc truy van, cung voi mot diem can dac biet luu y:

public class GuildDbContext : DbContext<br>{<br>    private readonly int _guildId;<br><br>    public const string GuildFilter = nameof(GuildFilter);<br><br>    public GuildDbContext(DbContextOptions<GuildDbContext> options, ITenantProvider tenant)<br>        : base(options)<br>        => _guildId = tenant.GuildId;<br><br>    public DbSet<Adventurer> Adventurers => Set<Adventurer>();<br><br>    protected override void OnModelCreating(ModelBuilder modelBuilder)<br>    {<br>        modelBuilder.Entity<Adventurer>(b =><br>        {<br>            // Cot phan vung phai la mot phan cua khoa.<br>            b.HasKey(a => new { a.Id, a.GuildId });<br>            b.Property(a => a.Id).ValueGeneratedOnAdd();<br><br>            b.HasQueryFilter(GuildFilter, a => a.GuildId == _guildId);<br>        });<br>    }<br>}

Dong HasQueryFilter la phan thuc hien cach ly. Theo cau hinh nay, moi truy van ma EF Core tao cho bang Adventurers deu tu dong duoc bo sung WHERE GuildId = @guild. Ban khong phai tu viet hay tu nho them dieu kien nay. Nho do, ban khong the vo tinh quen bo loc tenant, mot loi co the tao ra mot ngay rat te trong vai tro lap trinh vien.

Neu ban van dung phien ban EF Core cu, hay chu y den ten cua bo loc la GuildFilter. Day la cach lam cua EF Core 10. Truoc EF Core 10, moi entity chi co the co mot bo loc. Neu muon co ca bo loc tenant va bo loc xoa mem, ban phai ket hop chung thanh mot dieu kien tat hoac mo.

Voi EF Core 10, ban co the xep chong nhieu bo loc co ten tren cung mot entity. Khi can, ham IgnoreQueryFilters() cho phep bo qua tung bo loc ma van giu cac bo loc khac. Vi du, IgnoreQueryFilters(["SoftDeleteFilter"]) bo qua bo loc xoa mem nhung van giu cach ly tenant. Neu ban xay dung ung dung multi-tenant, rieng tinh nang nay da la ly do dang de nang cap len .NET 10.

Mot diem quan trong khac: EF tham so hoa truong _guildId thay vi ghi truc tiep gia tri vao truy van da bien dich. Cach nay bao ve ban khoi cac kieu tan cong chen ma va cho phep SQL Server tai su dung ke hoach truy van cho moi tenant, tu do cai thien hieu nang.

Gan ma tenant khi ghi du lieu

Bo loc truy van xu ly phan doc, nhung phan ghi thi sao? Khong the tin mot nhan vat tu khai minh thuoc nhom nao. Ta can co che doi ung: khi mot nhan vat moi duoc tao, nguoi gac cong phai gan GuildId cho ho truoc khi cho phep vao. Ta them logic nay vao SaveChanges:

public override int SaveChanges()<br>{<br>    StampGuild();<br>    return base.SaveChanges();<br>}<br><br>public override Task<int> SaveChangesAsync(CancellationToken ct = default)<br>{<br>    StampGuild();<br>    return base.SaveChangesAsync(ct);<br>}<br><br>private void StampGuild()<br>{<br>    foreach (var entry in ChangeTracker.Entries<IBelongToGuild>())<br>    {<br>        if (entry.State == EntityState.Added)<br>        {<br>            entry.Property(nameof(IBelongToGuild.GuildId)).CurrentValue = _guildId;<br>        }<br>    }<br>}

Co che nay ngan viec ai do tu chen minh vao nhom cua tenant khac. Moi dong moi deu duoc gan tenant hien tai, khong ngoai le va khong phu thuoc vao viec lap trinh vien co nho viet hay khong.

Trong ung dung san xuat thuc te, co the toi se chuyen logic nay vao mot SaveChangesInterceptor de DbContext gon gang hon. Nhung y tuong cot loi van la nhu vay.

Phan vung SQL Server: moi nhom mot ngan rieng

Cho den luc nay, day la mo hinh multi-tenancy voi schema dung chung kinh dien. Bay gio ta tan dung them cac tinh nang cua SQL Server.

Hien tai, tat ca nhan vat cua moi nhom dang bi tron lan trong mot kho dong lon. Khi dai san hoi phat trien va co them nhieu nhom tham gia, viec tim mot phieu thu se ngay cang kho. Phan vung thay doi bo cuc luu tru vat ly cua bang, bien no thanh tap hop nhieu tap sach, moi tap nam o mot noi luu tru rieng. Nho do, SQL Server giam so ban ghi phai xu ly va bo qua phan du lieu khong lien quan.

Co ba thanh phan de thuc hien:

  1. Partition function: Quy dinh cach chia dong thanh cac phan vung dua tren gia tri cua mot cot.
  2. Partition scheme: Anh xa cac phan vung vao cac filegroup, tuc noi luu tru vat ly.
  3. Bang co clustered index: Clustered index cua bang phai duoc tao tren partition scheme.

-- 1. Mot moc phan vung cho moi guild.<br>-- RANGE RIGHT dat gia tri moc vao phan vung ben phai.<br>CREATE PARTITION FUNCTION pfGuild (int)<br>AS RANGE RIGHT FOR VALUES (1, 2, 3);<br><br>-- 2. Tam thoi dua moi phan vung vao PRIMARY.<br>CREATE PARTITION SCHEME psGuild<br>AS PARTITION pfGuild ALL TO ([PRIMARY]);

Voi RANGE RIGHT FOR VALUES (1, 2, 3), ban co bon phan vung: moi gia tri nho hon 1, gia tri 1, gia tri 2, va gia tri tu 3 tro len. Guild 1 vao ngan rieng, guild 2 vao ngan tiep theo, con guild 3 va cac guild lon hon dung chung phan vung cuoi.

Bay gio den diem de nham

Ban khong the phan vung mot bang theo cot khong nam trong unique key. Neu co gang tao clustered primary key chi tren Id trong khi phan vung theo GuildId, SQL Server se bao loi:

Partition columns for a unique index must be a subset of the index key.

Ly do hop ly: de dam bao tinh duy nhat, engine muon kiem tra trong mot phan vung duy nhat, vi vay cot phan vung phai nam trong khoa dam bao tinh duy nhat. Do la ly do khoa EF dung new { a.Id, a.GuildId } thay vi chi dung a.Id. Id van duy nhat tren toan bo he thong vi no la IDENTITY; khoa ket hop chi co nghia rang rang buoc duy nhat duoc ap dung tren cap gia tri nay.

Tich hop phan vung vao migration cua EF Core

EF Core khong biet va khong tu tao partition function hay partition scheme. No cung khong tu dua bang vao partition scheme. Ban phai tu lam viec nay vi EF Core duoc thiet ke de tuong doi doc lap voi he thong luu tru du lieu, trong khi phan vung chu yeu la tinh nang rieng cua SQL Server.

Truoc tien, de EF Core tao migration bang thong thuong nhu moi khi. Sau do, them mot migration thu hai chua SQL thuan. Cach nay giu cho model snapshot cua EF Core hop le, dong thoi van tan dung duoc cac tinh nang ben duoi cua SQL Server.

Migration dau tien la ket qua cua lenh dotnet ef migrations add InitialCreate: mot bang Adventurers thong thuong voi clustered primary key ket hop. Migration thu hai moi thuc hien phan viec quan trong:

public partial class PartitionAdventurersByGuild : Migration<br>{<br>    protected override void Up(MigrationBuilder migrationBuilder)<br>    {<br>        migrationBuilder.Sql("""<br>            CREATE PARTITION FUNCTION pfGuild (int)<br>            AS RANGE RIGHT FOR VALUES (1, 2, 3);<br>            """);<br><br>        migrationBuilder.Sql("""<br>            CREATE PARTITION SCHEME psGuild<br>            AS PARTITION pfGuild ALL TO ([PRIMARY]);<br>            """);<br><br>        migrationBuilder.Sql("ALTER TABLE Adventurers DROP CONSTRAINT PK_Adventurers;");<br>        migrationBuilder.Sql("""<br>            ALTER TABLE Adventurers<br>            ADD CONSTRAINT PK_Adventurers PRIMARY KEY CLUSTERED (Id, GuildId)<br>            ON psGuild(GuildId);<br>            """);<br>    }<br><br>    protected override void Down(MigrationBuilder migrationBuilder)<br>    {<br>        migrationBuilder.Sql("ALTER TABLE Adventurers DROP CONSTRAINT PK_Adventurers;");<br>        migrationBuilder.Sql("""<br>            ALTER TABLE Adventurers<br>            ADD CONSTRAINT PK_Adventurers PRIMARY KEY CLUSTERED (Id, GuildId);<br>            """);<br>        migrationBuilder.Sql("DROP PARTITION SCHEME psGuild;");<br>        migrationBuilder.Sql("DROP PARTITION FUNCTION pfGuild;");<br>    }<br>}

Clustered index chinh la du lieu cua bang. Viec xay dung lai index tren psGuild(GuildId) se sap xep lai cac dong, dua du lieu cua moi nhom vao phan vung tuong ung. EF Core van xem day la mot bang thong thuong; chi la bang do da duoc sap xep thanh cac ngan rieng.

Cung co loi cho nhau

Hay nho rang moi truy van deu co WHERE GuildId = @guild nho bo loc truy van. GuildId cung la cot phan vung. Khi SQL Server thay dieu kien nay trong menh de WHERE, no thuc hien partition elimination: xac dinh ngan chua ban ghi phu hop va bo qua toan bo cac phan con lai cua bang.

Vi vay, cung mot bo loc truy van vua ngan Tenant A doc du lieu cua Tenant B, vua bao cho engine chi can truy cap ngan cua Tenant A. Co che cach ly va co che phan vung cung duoc tao ra tu mot menh de WHERE. Ban khong can viet them code ma van nhan duoc loi ich cua ca hai.

Day moi la diem cot loi: hay chon cot phan vung phu hop voi bo loc ma ban da ap dung o moi noi. Khi do, hai tinh nang se ho tro lan nhau.

Tenant moi se quay lai gay kho cho ban

Dieu gi xay ra khi dai san hoi tiep nhan mot nhom moi? Neu khong lam gi, Tenant D, Tenant E va cac tenant tiep theo se bi gom vao cung phan vung voi Tenant C, vi ta moi chi dinh ba moc phan vung. Khi them tenant moi, ban can tach khoang:

ALTER PARTITION SCHEME psGuild NEXT USED [PRIMARY];<br>ALTER PARTITION FUNCTION pfGuild() SPLIT RANGE (4);

Ban co the tu dong hoa viec nay trong quy trinh dang ky tenant. Tuy nhien, can nho mot so diem quan trong:

  • Khong nen duy tri mot phan vung cho moi tenant mai mai. Cach nay gon gang khi quy mo nho va mo ra nhieu thao tac bao tri theo tung tenant: ban co the TRUNCATE, nen du lieu hoac SWITCH mot phan vung ma khong cham vao du lieu cua tenant khac. Viec xoa tenant vi the cung rat sach se. Tuy nhien, SQL Server gioi han toi da 15.000 phan vung, va hang nghin phan vung nho se tro nen kho quan ly tu rat lau truoc muc gioi han do. Khi so tenant tang cao, hay gom tenant vao cac bucket, chang han bam tenant vao N phan vung, thay vi tao mot ngan cho tung tenant.
  • Tinh nang nay hien co trong moi edition. Truoc day, phan vung chi co trong Enterprise, nen nhieu tai lieu cu van cho rang ban khong the dung tinh nang nay. Tu SQL Server 2016 SP1, phan vung co trong moi edition, bao gom Standard, Web, Express va ca Azure SQL.
  • Phan vung khong phai la cach ly. Bat ky ai goi IgnoreQueryFilters() hoac chuyen sang SQL thuan deu co the doc xuyen qua moi phan vung. Bien gioi tenant nam o bo loc truy van va cac lop bao ve bo sung. Phan vung la he thong luu tru ho so, khong phai khoa cua. Neu can cach ly thuc su, chang han yeu cau phap ly hoac yeu cau hai tenant khong bao gio dung chung luu tru, ban can mo hinh moi tenant mot co so du lieu, khong phai phan vung.

Tong ket

Cau hinh hoan chinh gom bon thanh phan, moi thanh phan co mot nhiem vu:

  • ITenantProvider: Xac dinh tenant nao dang thuc hien request.
  • Bo loc truy van co ten: Tu dong an cac dong cua tenant khac khi doc du lieu.
  • Gan ma trong SaveChanges: Gan moi dong moi voi dung tenant khi ghi du lieu.
  • Phan vung SQL Server: Luu dong cua moi tenant vao phan vung rieng, dong thoi tan dung bo loc tenant san co de thuc hien partition elimination.

Dai san hoi van la mot toa nha voi mot cuon so duy nhat. Tuy nhien, nhan vien phuc vu gio co the mo thang den trang cua nhom can tim, khong ai nhin thay danh sach cua nhom khac, va he thong co the mo rong ma khong bien cuon so thanh mot kho du lieu roi ren.

Tiep theo: phong rieng

Cach tiep can nay re va de van hanh, nhung muc do cach ly van chi la logic. Chi mot lan quen goi IgnoreQueryFilters() la Tenant A co the doc thu cua Tenant B.

Trong Phan 2, moi tenant se co mot schema rieng, de co che cach ly nam ngay trong cau truc co so du lieu thay vi chi nam trong menh de WHERE. Dieu thu vi la khi schema dam nhan viec cach ly, mot phan lon co che vua xay dung se khong con can thiet: bo loc truy van, thao tac gan ma trong SaveChanges, thậm chi ca cot TenantId.

Con bay gio, hay di lay them mot thung bia lua cua nguoi lun tu tang ham.