Bir üretim ERP’sini geliştirdiğim dönemde, birden fazla müşteriye aynı yazılımı sunma ihtiyacı doğmuştu. Bu, kaçınılmaz olarak multi-tenant mimarisini gündeme getirdi. İlk başta basit bir fikir gibi görünse de, doğru trade-off’ları yapmak, projenin hem teknik hem de ticari başarısı için kritik önem taşıyordu. Bu yazıda, ERP sistemlerinde multi-tenant mimarisi kurarken karşılaştığım zorlukları ve aldığım kararları, somut örneklerle paylaşacağım.
Multi-Tenant Mimarisine Neden İhtiyaç Duyarız?
Kurumsal kaynak planlama (ERP) sistemleri, genellikle işletmelerin çekirdek operasyonlarını yönettiği karmaşık yazılımlardır. Tek bir müşteriye özel olarak geliştirilen monolitik yapılar, zamanla bakım ve güncelleme zorlukları yaratabilir. Özellikle hizmet sağlayıcılar için, her müşteriye ayrı bir sunucu ve veritabanı kurmak hem maliyetli hem de operasyonel olarak yönetilmesi güç bir senaryodur. İşte tam bu noktada multi-tenant mimarisi devreye girer.
Tek bir yazılım örneğini, birden fazla müşterinin (tenant) erişimine açarak kaynakları verimli kullanmayı hedefleriz. Bu, hem yazılımın geliştirme ve bakım maliyetlerini düşürür hem de operasyonel yükü azaltır. Örneğin, bir üretim firmasının ERP’sini geliştirirken, 5 farklı müşteriye hizmet vermek istediğimizde, her biri için ayrı bir deploy süreciyle uğraşmak yerine, tek bir sistem üzerinden hizmet verebiliriz. Bu durum, özellikle başlangıç aşamasında veya ölçeklenmesi gereken projelerde büyük bir avantaj sağlar.
Veritabanı Seviyesinde Multi-Tenancy Yaklaşımları
Multi-tenant mimarisinin en temel ve kritik kararlarından biri, veritabanı katmanında nasıl bir strateji izleneceğidir. Genellikle karşımıza üç ana yaklaşım çıkar:
- Tek Veritabanı, Tek Schema: Tüm tenant’ların verileri tek bir veritabanı içinde, ancak her tenant için ayrı bir schema kullanılarak saklanır. Bu, mantıksal bir ayrım sağlar.
- Tek Veritabanı, Tek Schema (Shared): Tüm tenant’ların verileri aynı veritabanı ve aynı schema içinde tutulur. Veri ayrımı, her tablodaki bir
tenant_idsütunu ile yapılır. - Ayrı Veritabanları: Her tenant için tamamen ayrı bir veritabanı örneği oluşturulur.
Bu yaklaşımların her birinin kendine göre avantajları ve dezavantajları bulunmaktadır.
Ayrı Veritabanları: İzole Ama Maliyetli
Bu yaklaşımda, her müşteri için tamamen bağımsız bir veritabanı oluşturulur. Bu, veri izolasyonu açısından en güçlü seçenektir. Bir tenant’ın verilerinin diğerine sızma riski minimumdur. Ayrıca, bir tenant’ın veritabanında yaşanacak bir performans sorunu veya hata, diğer tenant’ları doğrudan etkilemez. Bir üretim ERP’sinde, yüksek güvenlik gerektiren finansal veriler için bu yaklaşım tercih edilebilir.
Ancak, bu yaklaşımın en büyük dezavantajı maliyettir. Her müşteri için ayrı bir veritabanı sunucusu veya en azından veritabanı örneği yönetmek, kaynak tüketimini ve operasyonel maliyetleri ciddi şekilde artırır. Örneğin, 100 müşteriye hizmet verirken, her biri için ayrı bir PostgreSQL instance’ı yönetmek, hem donanım hem de lisans maliyetleri (eğer ticari bir veritabanı kullanılıyorsa) açısından kabarık bir fatura çıkaracaktır. Ayrıca, veritabanı bakımı, yedekleme ve güncelleme işlemleri de her bir veritabanı için ayrı ayrı yapılmak zorunda kalır, bu da operasyonel karmaşıklığı artırır.
Tek Veritabanı, Tek Schema (Shared): Kaynak Verimliliği ve Risk
Bu yaklaşımda, tüm tenant’ların verileri aynı veritabanı ve aynı schema içindeki tablolarda saklanır. Veri ayrımı, her tablodaki tenant_id sütunu ile sağlanır. Örneğin, bir orders tablosunda her satırın hangi müşteriye ait olduğunu belirten bir tenant_id alanı bulunur. Bu, kaynak kullanımı açısından en verimli yöntemdir. Tek bir veritabanı örneği, birden fazla müşteriye hizmet verebilir. Bu, özellikle başlangıç aşamasındaki SaaS (Software as a Service) ürünleri için maliyet avantajı sağlar.
Ancak bu yaklaşımın en büyük riski, veri izolasyonunun zayıf olmasıdır. Eğer uygulama katmanında yeterli güvenlik önlemleri alınmazsa, bir tenant’ın diğerinin verilerine erişmesi mümkün olabilir. Hatalı bir WHERE tenant_id = X filtresi unutulduğunda, tüm müşterilerin verileri listelenebilir. Bu durum, özellikle finansal verilerle uğraşan bir ERP sisteminde felaketle sonuçlanabilir. Ayrıca, bir tenant’ın yoğun veritabanı kullanımı, diğer tüm tenant’ların performansını olumsuz etkileyebilir. Bir üretim ERP’sinde, bir müşterinin yoğun raporlama isteği, diğer müşterilerin sipariş girişlerini yavaşlatabilir.
-- Örnek: Shared Schema'da Veri Sorgulama
SELECT
order_id,
product_name,
quantity,
order_date
FROM
orders
WHERE
tenant_id = 'musteri-abc-123'; -- Mutlaka tenant ID ile filtreleme yapılmalı!
Bu yaklaşımda tenant_id filtrelemesi kritik öneme sahiptir. Yanlışlıkla bu filtreyi kullanmadan yapılan bir sorgu, tüm verilerin açığa çıkmasına neden olabilir. Bu nedenle, uygulama katmanında sıkı kontroller ve testler yapılmalıdır.
Tek Veritabanı, Farklı Schemalar: Dengeli Bir Yaklaşım
Bu yaklaşım, yukarıdaki iki yöntemin bir nevi ortasıdır. Tüm tenant’lar tek bir veritabanı örneğini paylaşır, ancak her tenant’ın verileri kendi özel schema’sında tutulur. Örneğin, musteri_abc schema’sı altında orders, products gibi tablolar bulunurken, musteri_xyz schema’sı altında da aynı isimde tablolar yer alır. Bu, hem kaynakları verimli kullanmayı sağlar hem de veri izolasyonunu bir nebze olsun güçlendirir.
Bu yöntem, veri güvenliği açısından shared schema’dan daha iyidir. Çünkü sorgular, doğrudan ilgili tenant’ın schema’sına yapılır ve tenant_id gibi ek filtrelemelere daha az ihtiyaç duyulur. Ancak, veritabanı yönetimi karmaşıklığı artar. Her schema’nın bakımı, yedeği ve güncellenmesi ayrı ayrı planlanmalıdır. Ayrıca, birden fazla schema yönetimi, bazı veritabanı operasyonlarını (örneğin, tüm tenant’lar için toplu bir güncelleme yapmak) daha karmaşık hale getirebilir.
Uygulama Katmanında Tenant Yönetimi
Veritabanı stratejisi ne olursa olsun, uygulama katmanında tenant’ı doğru bir şekilde tanımlamak ve yönetmek hayati önem taşır. Bir HTTP isteği geldiğinde, hangi tenant’a ait olduğunu bilmemiz gerekir. Bu bilgi, genellikle isteğin başlıklarından (örneğin, X-Tenant-ID gibi özel bir başlık), alt alan adlarından (örneğin, musteri-abc.erp.com) veya kullanıcı oturum bilgilerinden alınır.
Bu tenant kimliği, daha sonra veritabanı sorgularında kullanılacak doğru veritabanı bağlantısını veya schema’yı seçmek için kullanılır. Eğer shared schema yaklaşımı benimsendiyse, gelen tenant kimliği, her sorguya otomatik olarak eklenmelidir. Bu genellikle bir middleware veya request interceptor aracılığıyla yapılır. Örneğin, FastAPI kullanıyorsak, bir Depends fonksiyonu ile request’ten tenant bilgisini alıp, ORM’imize (Object-Relational Mapper) bu bilgiyi iletebiliriz.
# FastAPI örneği: Tenant bilgisini request'ten almak
from fastapi import FastAPI, Request, Depends
app = FastAPI()
async def get_current_tenant(request: Request) -> str:
tenant_id = request.headers.get("X-Tenant-ID")
if not tenant_id:
raise HTTPException(status_code=400, detail="X-Tenant-ID header is missing")
# Burada tenant ID'nin geçerliliğini kontrol edebiliriz
return tenant_id
@app.get("/orders")
async def read_orders(current_tenant: str = Depends(get_current_tenant)):
# Veritabanı sorgusunu current_tenant ile yap
orders = await db.fetch_all(f"SELECT * FROM orders WHERE tenant_id = '{current_tenant}'")
return orders
Bu kod parçacığı, gelen her isteğin X-Tenant-ID başlığını kontrol eder ve bu başlık olmadan gelen isteklere izin vermez. Ardından, alınan tenant kimliği, veritabanı işlemlerinde kullanılır. Bu, uygulama katmanında tenant izolasyonunu sağlamanın temel adımlarından biridir.
Ölçeklenebilirlik ve Performans Trade-off’ları
Multi-tenant bir mimari tasarlarken, ölçeklenebilirlik ve performans her zaman öncelikli konular olmalıdır. Özellikle yoğun veri işleyen ERP sistemlerinde, bir tenant’ın performansı diğerlerini etkilememelidir.
Shared veritabanı veya schema yaklaşımlarında, tüm tenant’lar aynı veritabanı kaynaklarını paylaşır. Bu, bir tenant’ın aşırı kaynak tüketmesi durumunda diğerlerinin yavaşlamasına neden olabilir. Örneğin, bir müşteri, büyük bir veri setini işleyen karmaşık bir rapor çalıştırdığında, sistem genelinde performans düşüşleri yaşanabilir. Bu tür durumları yönetmek için kaynak sınırlamaları (resource limiting) ve izleme mekanizmaları kritik önem taşır.
Ayrı veritabanı yaklaşımı, doğal olarak daha iyi ölçeklenebilirlik ve performans izolasyonu sunar. Ancak, bu durumda da her veritabanı örneğinin kendi başına ölçeklenmesi gerekir. Binlerce tenant’ınız olduğunda, her birini ayrı ayrı ölçeklendirmek operasyonel bir kabusa dönüşebilir. Bu nedenle, ölçeklendirme stratejisi, iş yükünün türüne ve maliyet kısıtlamalarına göre dikkatlice planlanmalıdır.
Güvenlik ve Uyumluluk (Compliance)
Multi-tenant sistemlerde güvenlik, en hassas konulardan biridir. Veri ihlalleri, sadece finansal kayıplara değil, aynı zamanda itibar kaybına ve yasal sorunlara da yol açabilir. ERP sistemleri genellikle hassas finansal, üretim ve müşteri verilerini barındırdığı için, güvenlik önlemleri üst düzeyde olmalıdır.
Veri izolasyonu, güvenlik stratejisinin temel taşıdır. Yukarıda bahsedilen veritabanı yaklaşımları, bu izolasyonu farklı seviyelerde sağlar. Ayrı veritabanları en yüksek izolasyonu sunarken, shared schema en düşük izolasyonu sunar. Uygulama katmanındaki kontrollerin doğruluğu, bu izolasyonun ne kadar etkili olacağını belirler.
Ayrıca, GDPR, CCPA gibi veri gizliliği düzenlemelerine uyum da multi-tenant mimariler için önemli bir faktördür. Her tenant’ın verilerinin nerede saklandığı, nasıl işlendiği ve kimlerin erişebildiği gibi konuların net bir şekilde belirlenmesi gerekir. Bu, özellikle farklı coğrafi bölgelerdeki müşterilere hizmet verirken daha da karmaşık hale gelir.
Maliyet Analizi ve Trade-off’lar
Son olarak, tüm bu kararların maliyet üzerindeki etkisini göz ardı etmemek gerekir.
- Ayrı Veritabanları: Yüksek başlangıç ve işletme maliyeti, ancak en iyi izolasyon ve potansiyel olarak en iyi performans.
- Tek Veritabanı, Tek Schema (Shared): En düşük başlangıç ve işletme maliyeti, ancak en düşük izolasyon ve performans riskleri.
- Tek Veritabanı, Farklı Schemalar: Maliyet ve izolasyon arasında iyi bir denge.
Bir üretim ERP’sini geliştirirken, ilk başta maliyetleri düşük tutmak adına shared schema ile başlamayı düşünebiliriz. Ancak, müşteri tabanı büyüdükçe ve veri hassasiyeti arttıkça, schema bazlı ayrım veya hatta bazı kritik tenant’lar için ayrı veritabanları gibi daha güvenli ve izole çözümlere geçiş yapmayı planlamak akıllıca olacaktır. Bu tür geçişler, mimarinin esnekliği ve modülerliği ile mümkün olur.
Örneğin, başlangıçta 5 küçük müşteriye hizmet verirken shared schema ile yola çıkıp, zamanla 50 müşteriye ulaştığımızda ve bu müşterilerden biri büyük bir kurumsal firma olduğunda, bu firmayı ayrı bir veritabanına taşımak mantıklı olabilir. Bu, stratejik bir karardır ve projenin büyüme aşamasına göre şekillendirilir. Bu tür kararlar, sadece teknik değil, aynı zamanda iş hedefleriyle de uyumlu olmalıdır.