← blog · 25 Ağustos 2026

Çok kiracılı SaaS mimarisi: izolasyon stratejileri ve gerçek maliyetleri

Ayrı veritabanı, ayrı şema ve ortak tabloda satır bazlı ayrım: üçünün göç, yedekleme, gürültülü komşu ve sızıntı maliyeti farklıdır. Hangisiyle başlanmalı ve kiracı sınırı uygulama kodu yerine veritabanına nasıl indirilir.

Kiracı sınırı nereye çizilir

Çok kiracılı bir uygulamada tek bir soru mimarinin geri kalanını belirler: iki farklı müşterinin verisi hangi katmanda birbirinden ayrılıyor? Cevabı "sorgudaki bir koşul" olan sistemle cevabı "ayrı bir veritabanı" olan sistem aynı ürünü satsa bile aynı işletme maliyetine, aynı göç prosedürüne, aynı sızıntı riskine sahip değildir. Bu karar ilk aylarda verilir ve sonraki yıllar boyunca yaşanır, çünkü izolasyon modelini sonradan değiştirmek bir yeniden düzenleme değil, veri taşıma projesidir.

Üç model, üç farklı fatura

Kiracı başına ayrı veritabanı en güçlü ayrımı verir. Bir kiracının verisi başka bir kiracının sorgusuna teknik olarak görünmez, yanlış yazılmış tek bir sorgu sızıntıya dönüşemez. Bedeli işletmededir: her veritabanının kendi bağlantı havuzu, kendi bakım penceresi, kendi istatistikleri vardır. PostgreSQL'de max_connections varsayılanı 100'dür ve sunucu genelindedir. Yüz kiracıya birer havuz açan bir uygulama, henüz tek bir kullanıcı istek göndermeden bu sınıra dayanabilir. Birkaç yüz kiracıdan sonra bağlantı yönetimi asıl işiniz olur.

Kiracı başına ayrı şema aynı veritabanının içinde kiracı başına bir isim alanı açar. Bağlantı paylaşılır, ayrım mantıksaldır. Cazip görünür çünkü hem izolasyon hissi verir hem de tek sunucuda kalır. Gizli maliyeti katalogdadır: her şema, sistem tablolarında tablo ve indeks başına yeni satırlar demektir. Yüz tabloluk bir uygulama bin kiracıya çıktığında katalog yüz binlerce girdiye ulaşır, döküm alma ve otomatik vakumun planlaması bundan etkilenir.

İkinci maliyet bağlantı havuzuyla arasındaki gerilimdir. Şemayı search_path ile seçiyorsanız ve önünüzde işlem kipinde çalışan bir PgBouncer varsa, oturum kapsamlı SET güvenli değildir: PgBouncer arka uç bağlantısını tam da işlem sınırında geri dönüştürür, bir sonraki işlem başka bir kiracının şemasında açılabilir. Ya SET LOCAL kullanırsınız, ya rol düzeyinde sabitlersiniz, ya da PgBouncer'ın track_extra_parameters ayarıyla bu parametreyi izletirsiniz. Bunu bilmeyen ekipler üretimde açıklaması en zor hata sınıfını görür: aralıklı olarak yanlış kiracının verisi.

Ortak tabloda satır bazlı ayrım ise her tabloya bir tenant_id sütunu koyar ve her sorguyu ona göre süzer. En ucuz, en esnek ve en tehlikeli olan budur. Ucuzdur çünkü tek şema, tek göç, tek havuz. Tehlikelidir çünkü izolasyon tamamen uygulama kodunun disiplinine bağlıdır ve disiplin ölçeklenmez.

Göç yönetimi her modelde başka türlü kırılır

Ortak tabloda göç tek bir işlemdir ve tek bir riski vardır: büyük tabloya kilit. Bin kiracının verisi aynı yerdedir, ALTER TABLE hepsini birden etkiler. Buna karşılık geri alma da tektir.

Ayrı şema ve ayrı veritabanında göç bir döngüye dönüşür, döngü de kısmen başarısız olur. Beş yüz şemanın 380'inde göç geçer, 381'incisinde bir eşsiz kısıt ihlaline çarpar. Artık iki farklı şema sürümünün aynı anda yaşadığı bir üretim ortamınız var ve uygulama kodu ikisiyle de çalışmak zorunda. Bunu yönetmenin tek makul yolu göçleri baştan geriye uyumlu yazmaktır: sütunu önce boş geçilebilir ekle, bir süre çift yaz, geçmişi doldur, kodu yeni sütuna geçir, eskisini en sonda düşür. Tek kiracılı bir uygulamada bu titizlik lükstür, kiracı başına şemada giriş bedelidir. Göç durumunu ayrıca kiracı bazında saklamanız gerekir, yoksa döngünün nerede durduğunu bilemezsiniz.

Kimsenin ilk gün hesaba katmadığı bir şey daha var: yeni kiracı açma süresi. Yüz tabloluk bir şemayı sıfırdan kurmak saniyeler alır ve saniyeler kayıt formunun içine sığmaz. Şema oluşturmayı kuyruğa alın, kullanıcıya hesabın hazırlandığını gösterin.

Yedeklemenin asıl sorusu tek kiracının nasıl geri geleceğidir

Yedek almak herkesin yaptığı iştir. Asıl soru şu: bir müşteri "dün öğlen yanlışlıkla dört bin kaydı sildik" dediğinde, diğer dokuz yüz doksan dokuz kiracıya dokunmadan yalnızca onu geri getirebiliyor musunuz?

Ayrı veritabanında cevap kolaydır. Ayrı şemada da makul kalır: pg_dump -n ile tek şema alınır, geçici bir isim altına geri yüklenir, veri karşılaştırılıp taşınır. Ortak tabloda cevap acıdır. Tam yedeği ayrı bir sunucuya açar, ilgili kiracının satırlarını süzer, yabancı anahtar sırasını ve dizi değerlerini elle çözersiniz. Süre saatlerle ölçülür ve bunu ilk kez gerçek bir olayın ortasında denemek istemezsiniz.

Bu yüzden ortak tablo modelini seçen ekiplerin çoğu silmeyi gerçek silme olmaktan çıkarır. Yumuşak silme, olay günlüğü ya da denetim tablosu, geri yükleme taleplerinin büyük kısmını yedek yolundan çıkarıp uygulamanın içine alır. Bunu ürünün ilk sürümüne koyun, sonradan eklemek çoktan kaybettiğiniz geçmişi geri getirmez.

Gürültülü komşu

Ortak altyapıda tek bir kiracı hepsini yavaşlatabilir. Klasik biçimi büyük müşterinin rapor sorgusudur: on milyon satırlık bir tarama paylaşılan tampon havuzunu süpürür, herkesin gecikmesi artar. Daha sinsi biçimi kuyruktur. Bir kiracının içe aktarma işi elli bin görev üretir, işçiler saatlerce onunla meşgul olur, başka bir kiracının parola sıfırlama e-postası sırada bekler.

Etki sırasına göre yapılacaklar: kuyruğu kiracıya göre değil işin türüne ve beklenen süresine göre ayırın, sonra kiracı başına eşzamanlılık tavanı koyun. Ağır raporları okuma replikasına yönlendirin. Veritabanı tarafında ifade başına zaman aşımı tanımlayın, çünkü sınırsız süre çalışabilen tek bir sorgu izolasyon modelinden bağımsız olarak herkesin sorunudur. Büyük kiracıyı kendi veritabanına taşımak da bir araçtır ama ona ancak taşıma yolu önceden hazırsa uzanabilirsiniz.

Koşulu unutmak kişisel bir hata değil, yapısal bir kusurdur

Satır bazlı ayrımda kural herkesin söylediği kadar basittir: her sorguya tenant_id koşulu. Bu kural tutmaz. Yüz dosyada, üç yıl boyunca, beş farklı geliştiricinin yazdığı sorguların hepsinde tutmasını beklemek gerçekçi değil. Bir rapor sorgusu, bir toplu iş, bir yönetim ekranı, bir defalık veri düzeltme betiği; biri mutlaka atlar.

Çözüm insanlara hatırlatmak değil, koşulu koddan çıkarıp veritabanına indirmektir. PostgreSQL'de satır düzeyi güvenlik tam olarak bunun için var:

ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON invoices
  USING (tenant_id = current_setting('app.tenant_id', true)::uuid)
  WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);

İki ayrıntı burada hayat kurtarır. Birincisi FORCE: tablo sahibi normalde politikaları atlar, yani uygulamanız tabloların sahibi olan rolle bağlanıyorsa politika hiçbir iş yapmaz ve siz korunduğunuzu sanırsınız. İkincisi WITH CHECK: onsuz okuma süzülür ama başka bir kiracının kimliğiyle satır yazmak serbest kalır.

Değişkeni işlemin başında ayarlayın ve o işleme bağlayın:

BEGIN;
SELECT set_config('app.tenant_id', '3f2b6c1e-...', true);
SELECT id, total FROM invoices;
COMMIT;

set_config fonksiyonunun üçüncü parametresi ayarı işlemle sınırlar. Havuzlanmış bağlantılarda tek doğru kullanım budur, çünkü oturuma yazılan bir değer sizden sonra aynı bağlantıyı devralan isteğe sızar.

Uygulama tarafında da tek bir sahip olsun: bağlantıyı ödünç veren katman değişkeni ayarlasın, hiçbir kod yolu ham bağlantı almasın. Superuser rolleri ve BYPASSRLS niteliği taşıyan roller politikaları koşulsuz atlar, bu yüzden uygulama rolünüzün ikisine de sahip olmadığını gerçek bir testle sabitleyin. Ve o testin bir kez kırmızıya döndüğünü görün: politikayı geçici olarak kaldırıp testin gerçekten patladığını ölçmediyseniz elinizdeki şey bir güvence değil, bir his.

Kimlik kullanıcıya değil, kullanıcı ile kiracının çiftine bağlanır

Yetkilendirmede en sık yapılan hata rolü doğrudan kullanıcıya vermektir. Aynı e-posta adresi iki müşteride farklı yetkilere sahip olabilir ve bir noktada olacaktır. Aktif kiracıyı oturum jetonunun içine koyun, jetonu doğruladıktan sonra o kullanıcının o kiracıda üyeliği bulunduğunu ayrıca kontrol edin. Kiracı kimliğini istek gövdesinden ya da adres satırından alıp doğrulamadan güvenen her uç nokta, tarayıcıdan kiracı değiştirmeye açık bir kapıdır.

Destek ekibi için "kiracı gibi görün" özelliği eninde sonunda istenecek. Geldiğinde ayrı bir yoldan gelsin: ayrı bir rol, süresi dolan bir oturum, her kullanım için denetim kaydı. Normal giriş akışına iliştirilen bir yönetici bayrağı, en tehlikeli yeteneği en az korunan yola koymak demektir.

Kiracıya özel istekler mimariyi çürütür

İlk büyük müşteri gelir ve kendi onay akışını, kendi fatura düzenini, kendi alan adlarını ister. Bunu kiracıya özel kod dallarıyla karşılamak kolaydır ve ürünü sessizce ikiye böler. Altı ay sonra üç kiracının üç ayrı akışı olur, hiçbir göç hepsinde birden çalışmaz, testler yalnızca varsayılan yolu kapsar.

Sağlıklı sınır şudur: veri modelinde esneklik serbest, kod akışında dallanma yasak. Kiracı başına yapılandırma, kiracı başına alan tanımları, kiracı başına şablon; bunların hepsi veridir ve tek kod yolundan geçer. Bir istek yapılandırmayla ifade edilemiyorsa ya herkese açık bir ürün özelliğine dönüşür ya da cevap hayırdır. Kalıcı bir üçüncü seçenek yok.

Nereden başlamalı

Çoğu ekip için cevap nettir: ortak tabloda satır bazlı ayrımla başlayın ve izolasyonu ilk günden satır düzeyi güvenlikle veritabanına indirin. Bu ikisi birlikte gelmek zorunda, çünkü satır düzeyi güvenlik olmadan satır bazlı ayrım zamana yayılmış bir veri sızıntısıdır.

Şema başına modeli ancak sözleşmeniz verinin ayrı tutulacağını yazıyorsa ve kiracı sayınız üç haneye çıkmayacaksa seçin. Kiracı başına veritabanını da müşteri sayısı az, müşteri başına gelir yüksekse ya da veri ikamet yükümlülüğünüz varsa seçin. Bunlar aynı yolculuğun bir sonraki durağı değil, farklı bir işin cevabıdır.

Karşılığını gerçekten veren hazırlık dardır: tenant_id her tabloda birincil anahtarın parçası ya da en azından her bileşik indeksin ilk sütunu olsun, ve tek bir kiracının verisini dışa aktarıp başka bir veritabanına geri yükleyen bir yol yazın. O yol elinizdeyken büyük bir kiracıyı kendi veritabanına taşımak bir hafta sonu işidir. Elinizde değilken aynı iş bir çeyreklik projeye dönüşür.