Kurumsal Yazılım ve Entegrasyon kod örnekli
Kurumsal Yazılımda Çok Kiracılı (Multi-Tenant) Veri Yalıtımı
Tek bir eksik WHERE koşulu, bir müşterinin verisini başka müşteriye gösterir. Yalıtımı umuda değil yapıya bağlayın.

İçindekiler
Bu yazıyı dinle
Yapay zekâ sesiyle yaklaşık 2 dakikalık özet
Çok kiracılı bir sistemde tek bir uygulama birden fazla müşteriye (kiracıya) hizmet eder. Kurumsal yazılımda bu model işletme maliyetini belirgin biçimde düşürür; buna karşılık tek bir hata sınıfı ölümcül hâle gelir: bir müşterinin verisinin diğerine görünmesi.

Üç model, üç maliyet
Ayrı veritabanı en güçlü yalıtımı verir; yedekleme ve geri yükleme müşteri bazında yapılabilir, gürültülü komşu sorunu yoktur. Bedeli işletmedir: yüz müşteri, yüz göç çalıştırması demektir.
Ayrı şema orta yoldur, ancak şema sayısı arttıkça bağlantı yönetimi ve göç koordinasyonu zorlaşır.
Paylaşımlı tablo (her satırda tenant_id sütunu) en ucuz ve en yaygın modeldir — ve doğru kurulmazsa en risklidir.
Asıl tehlike: unutulan WHERE
// Bu kod çalışır, test geçer, kod incelemesinden geçer —
// ve ÜRETİMDE başka müşterinin siparişini gösterir.
public function detay(int $id)
{
$siparis = $this->siparisModel->find($id); // tenant_id koşulu YOK
return view('siparis/detay', ['siparis' => $siparis]);
}Bu hatanın özelliği, tek müşterili test verisiyle asla ortaya çıkmamasıdır. Sızıntı ancak üretimde, gerçek müşteriler arasında görünür. Bu yüzden çözüm "dikkatli olmak" olamaz.
Çözüm: filtreyi altyapıya gömün
Doğru yaklaşım, ayrım koşulunu her sorguya elle eklemek değil, eklenmemesini imkânsız kılmaktır. CodeIgniter'da bunu temel bir model sınıfıyla yapabilirsiniz.
abstract class TenantModeli extends \CodeIgniter\Model
{
/** Model müşteriye bağlı mı? (ortak referans tablolarında false) */
protected bool $tenantaBagli = true;
protected function tenantId(): int
{
$id = (int) (session()->get('tenant_id') ?? 0);
// Bağlam yoksa sorgu ÇALIŞMAMALI — açık kapı bırakmaktansa hata verin
if ($id <= 0) {
throw new \RuntimeException('Müşteri bağlamı yok; sorgu reddedildi.');
}
return $id;
}
/** Her okuma bu kapıdan geçer */
protected function kapsam(): self
{
if ($this->tenantaBagli) {
$this->where($this->table . '.tenant_id', $this->tenantId());
}
return $this;
}
public function bul(int $id): ?array
{
return $this->kapsam()->where($this->table . '.id', $id)->first();
}
public function hepsi(): array
{
return $this->kapsam()->findAll();
}
/** Yazarken bu kimlik İSTEMCİDEN alınmaz, oturumdan yazılır */
protected function tenantEkle(array $veri): array
{
if ($this->tenantaBagli) {
$veri['tenant_id'] = $this->tenantId();
}
return $veri;
}
}tenant_id alanını asla istemciden gelen veriyle doldurmayın ve allowedFields listesine koymayın. Aksi hâlde form gövdesine elle bir kimlik ekleyen kullanıcı, kaydı başka müşteriye yazabilir — bu, toplu atama (mass assignment) açığının en tehlikeli biçimidir.
Veritabanı tarafında ikinci kapı
Uygulama katmanı tek savunma olmamalıdır. İki ek önlem işe yarar: benzersizlik kısıtlarını ayrım sütunuyla birlikte tanımlamak ve yabancı anahtarların aynı müşteri içinde kalmasını zorlamak.
-- YANLIŞ: e-posta tüm sistemde benzersiz → farklı müşteriler çakışır
UNIQUE KEY uq_eposta (eposta)
-- DOĞRU: benzersizlik müşteri İÇİNDE geçerli
UNIQUE KEY uq_kiraci_eposta (tenant_id, eposta)
-- Bileşik yabancı anahtar: kalem, siparişin sahibiyle aynı olmak ZORUNDA
ALTER TABLE siparis_kalemleri
ADD CONSTRAINT fk_kalem_siparis
FOREIGN KEY (tenant_id, siparis_id)
REFERENCES siparisler (tenant_id, id);Bileşik yabancı anahtar, uygulama katmanında bir hata olsa bile çapraz müşteri ilişkisi kurulmasını veritabanı seviyesinde engeller.
Test edilebilir hâle getirin
Yalıtımı doğrulayan testler, tek müşterili verilerle yazılan testlerden farklıdır: iki ayrı hesap oluşturup birinin diğerinin kaydına erişemediğini açıkça sınamalısınız. Bu testi bir kez yazıp her yeni modül için tekrarlamak, sızıntıyı üretimden önce yakalar. Kayıt bazlı yetki kontrollerinin nasıl kurulacağını yetkilendirme modelini doğru kurmak yazımızda ele almıştık; denetim izi ise sızıntı şüphesinde kimin neye eriştiğini geriye dönük görmenizi sağlar.
Müşteri bazlı özellik açma ihtiyacınız varsa bunu ayrım katmanına değil, özellik bayrağı sistemine taşımak daha sürdürülebilir olur.
Model seçimini erteleyin mi?
Paylaşımlı tablodan ayrı veritabanına geçmek mümkündür ama pahalıdır. Bu yüzden karar, müşteri sayısı ve uyumluluk gereksinimine göre baştan verilmelidir: birkaç büyük kurumsal hesap ve sıkı veri ayrımı şartı varsa ayrı veritabanı, çok sayıda küçük müşteri varsa paylaşımlı tablo genelde doğru tercihtir. Büyük geçişlerin riskini büyük patlama göç stratejisi yazımızda tartışmıştık. Kısıt tanımlarının ayrıntısı için MySQL yabancı anahtar belgeleri başvurulabilir.
Sonuç
Çok kiracılı bir sistemde veri yalıtımı, geliştiricinin her sorguda hatırlaması gereken bir kural olamaz — çünkü er ya da geç unutulur ve hata ancak üretimde görünür. Yalıtımı yapıya gömün: sorguları ayrım kapsamını zorunlu kılan bir temel modelden geçirin, bu kimliği istemci verisinden asla almayın, benzersizlik kısıtlarını ayrım sütunuyla birlikte tanımlayın ve bileşik yabancı anahtarlarla veritabanı seviyesinde ikinci kapıyı kurun. Bir sonraki modülünüzü yazarken şu soruyu sorun: "Bu sorguda ayrım koşulunu yazmayı unutsam, sistem beni durdurur mu?" Cevap hayırsa, düzeltilecek şey sorgu değil altyapıdır.
Sık sorulan sorular
Paylaşımlı tabloda performans sorun olur mu?
tenant_id sütunu bileşik indekslerin ilk alanı olduğu sürece genelde olmaz. Sorun büyük kiracıların küçükleri etkilemesidir (gürültülü komşu). Bu noktaya gelindiğinde en büyük kiracıları ayrı veritabanına taşımak, tüm modeli değiştirmekten daha pratiktir.
Kiracı kimliğini alt alan adından (subdomain) almak güvenli mi?
Alt alan adı kiracıyı belirlemek için uygundur, ancak tek başına yetki kanıtı değildir. Oturumdaki kullanıcının o kiracıya gerçekten bağlı olduğunu her istekte doğrulamanız gerekir; aksi hâlde adres çubuğunu değiştiren biri başka kiracının bağlamına geçmeyi deneyebilir.
Yönetici kullanıcılar tüm kiracıları görebilmeli, bunu nasıl yaparım?
Kapsamı bypass eden ayrı ve açıkça adlandırılmış bir yol tanımlayın; varsayılan davranışı gevşetmeyin. Ayrıca bu erişimlerin tamamını denetim iziyle kaydedin — çapraz kiracı erişimi her zaman kayda değer bir olaydır.
Raporlama sorgularında kapsam nasıl korunur?
Raporlar genelde ham SQL ile yazıldığı için otomatik kapsamın dışında kalır ve en sık sızıntı burada olur. Rapor sorgularını da tek bir yardımcı katmandan geçirin ve kiracı parametresini zorunlu argüman yapın; isteğe bağlı bir parametre er ya da geç boş geçilir.
Yazan
Çılgın Yazılım
Ben Evren Çılgın; yazılım geliştirme, web teknolojileri ve sistem tasarımı alanlarında uzmanlaşmış bir geliştiriciyim. Uzun yıllardır hem frontend hem de backend tarafında üretken, ölçeklenebilir ve kullanıcı dostu çözümler üretiyorum.
Bu konuda bir projeniz mi var?
İhtiyacınızı birkaç cümleyle anlatın; uygun yaklaşımı birlikte belirleyelim.
İlgili yazılar
Yorumlar
Henüz yorum yok. Sorunuzu ya da deneyiminizi ilk siz yazın.
Yorum yazın
Yorumunuz onaylandıktan sonra yayınlanır. Ekibimiz gerekirse konuyla ilgili bir yanıt da paylaşır.

