Apa Itu Multi-Tenant Architecture?
Multi-tenant artinya satu instance aplikasi melayani banyak customer sekaligus. Ini adalah fondasi utama SaaS yang efisien. Tanpa multi-tenant, Anda harus deploy instance terpisah untuk setiap customer.
Saya sudah implement multi-tenant di beberapa produk dan ada 3 pendekatan utama yang perlu Anda pahami.
3 Pendekatan Multi-Tenant
1. Pool Database Shared Schema
Semua tenant share database yang sama, dibedakan oleh column tenant_id. Ini paling murah dan paling umum untuk SaaS kecil-menengah.
// Contoh query dengan tenant_id
func GetProjects(db *gorm.DB, tenantID uint) ([]Project, error) {
var projects []Project
err := db.Where("tenant_id = ?", tenantID).Find(&projects).Error
return projects, err
}
2. Pool Database Separate Schemas
Setiap tenant punya schema sendiri dalam database yang sama. Lebih aman tapi lebih kompleks.
3. Database per Tenant
Full isolation. Cocok untuk enterprise SaaS yang handle data sensitif. Tapi cost-nya jauh lebih tinggi.
Implementasi di Go Tenant Middleware
Di Go, saya biasanya pakai middleware untuk inject tenant context di setiap request:
func TenantMiddleware(c *fiber.Ctx) error {
tenantID := c.Get("X-Tenant-ID")
if tenantID == "" {
return c.Status(400).JSON(fiber.Map{"error": "tenant ID required"})
}
c.Locals("tenantID", tenantID)
return c.Next()
}
Dengan approach ini, setiap handler bisa langsung akses tenant ID tanpa perlu parse ulang. Clean dan efisien.
Performance Tips
Pool database dengan shared schema punya bottleneck di query performance. Solusinya:
- Index tenant_id di semua tabel ini wajib
- Gunakan connection pooling di Go dengan sql.DB.SetMaxOpenConns
- Pertimbangkan caching layer seperti Redis untuk data tenant yang jarang berubah
Dari pengalaman saya, pool database shared schema bisa handle ribuan tenant dengan response time under 50ms selama Anda proper index.
Tenant Isolation: Aspek Keamanan yang Kritis
Salah satu tantangan terbesar multi-tenant adalah memastikan data isolation. Satu kesalahan kecil di query bisa leak data antar tenant. Ini nightmare yang harus dicegah.
Di Go, saya selalu pakai row-level security di PostgreSQL sebagai layer pertahanan kedua. Meskipun application layer sudah filter by tenant_id, database tetap enforce isolation di level table:
-- Enable RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- Policy: user hanya bisa akses data tenant-nya
CREATE POLICY tenant_isolation ON projects
USING (tenant_id = current_setting('app.tenant_id')::int);
Dengan kombinasi application-level filtering dan database-level RLS, kemungkinan data leak menjadi sangat kecil. Defense in depth selalu lebih baik dari single point of failure.
Migration Strategy untuk Multi-Tenant
Menambah kolom atau tabel baru di multi-tenant setup membutuhkan perencanaan ekstra. Jangan asal ALTER TABLE. Pertimbangkan impact ke semua tenant. Saya biasanya pakai expand-contract pattern: tambah kolom baru, migrate data, lalu hapus kolom lama. Proses ini harus di-automate dan bisa di-rollback.