Multi-Tenant SaaS Architecture With Postgres RLS

Arsitektur SaaS multi-tenant adalah desain di mana satu instance aplikasi yang berjalan, dan biasanya satu database, melayani banyak organisasi pelanggan sekaligus. Setiap pelanggan, yang disebut tenant, hanya melihat datanya sendiri. Ini menaikkan kepadatan dan menurunkan biaya hosting dibandingkan memberi setiap pelanggan deployment terpisah.
Untuk tim satu sampai tiga orang, shared schema dengan kolom tenant_id biasanya default terbaik. Model ini paling murah dijalankan dan hanya punya satu schema untuk dimigrasi. PostgreSQL Row-Level Security lalu menegakkan isolasi di database, sehingga hampir seaman model yang lebih berat dengan biaya jauh lebih kecil.
Setelah RLS diaktifkan pada tabel dengan sebuah policy, PostgreSQL memfilter setiap query di dalam database agar hanya mengembalikan baris yang cocok dengan tenant saat ini. Id tenant disuplai per request melalui session setting yang dibaca policy. Karena filter berada di database, aplikasi yang lupa menambahkan kondisi tenant_id pun tetap tidak bisa melihat baris tenant lain.
Connection pool memakai ulang koneksi database antar request. Jika Anda menyetel konteks tenant pada sebuah koneksi dan tidak meresetnya, request berikutnya yang memakai ulang koneksi itu bisa mewarisi tenant yang salah dan membaca data pelanggan yang salah. Solusinya adalah membatasi setting tenant ke setiap transaksi agar bersih otomatis.
Ya. RLS secara efektif menambahkan pemeriksaan kesetaraan tenant_id ke setiap query, jadi tenant_id sebaiknya menjadi kolom pertama pada index gabungan yang menopang lookup umum Anda. Tanpa itu, database bisa memindai baris semua tenant sebelum membuang yang disembunyikan policy, yang melambat saat tenant tersibuk Anda membesar.

Ringkasan Utama
SaaS multi-tenant memungkinkan satu aplikasi dan satu database melayani banyak pelanggan sekaligus, sehingga biaya hosting tetap murah untuk tim kecil. PostgreSQL Row-Level Security menegakkan isolasi tenant di dalam database itu sendiri: setiap query difilter berdasarkan tenant_id melalui sebuah policy, jadi bug di aplikasi pun tidak bisa membocorkan baris satu pelanggan ke pelanggan lain.
Jika Anda membangun produk SaaS sebagai tim satu sampai tiga orang di Indonesia, desain database menentukan berapa biaya per pelanggan dan seberapa parah satu bug bisa merugikan Anda. Multi-tenancy adalah pola yang membuat satu instance aplikasi melayani semua pelanggan sekaligus, alih-alih membuat deployment terpisah untuk setiap pelanggan.
Pertanyaan sulitnya bukan apakah harus multi-tenant, melainkan seberapa ketat data setiap tenant harus diisolasi. Artikel ini membahas tiga model isolasi klasik, menjelaskan mengapa shared schema dengan kolom tenant_id ditambah PostgreSQL Row-Level Security adalah pilihan default yang praktis untuk tim kecil, dan menunjukkan cara menyiapkannya tanpa membocorkan data antar pelanggan.
Tenant adalah satu organisasi pelanggan beserta seluruh penggunanya. Dalam SaaS multi-tenant, satu instance aplikasi dan biasanya satu database melayani banyak tenant pada waktu yang sama. Alternatifnya, memberi setiap pelanggan salinan aplikasi dan database sendiri, disebut single-tenant, dan itu melipatgandakan tagihan hosting serta pekerjaan pemeliharaan sebanyak jumlah pelanggan Anda.
Bagi tim kecil, kepadatan adalah segalanya. Semakin banyak tenant yang Anda tampung pada infrastruktur bersama, semakin rendah biaya per pelanggan, dan itulah yang membuat SaaS ramping tetap menguntungkan pada harga murah. Konsekuensinya, infrastruktur bersama memindahkan beban menjaga tenant tetap terpisah ke desain Anda, sehingga isolasi harus disengaja, bukan kebetulan.
Ada tiga cara yang umum dikenal untuk menyimpan data banyak tenant, dan ketiganya menukar isolasi dengan biaya serta usaha operasional. Database terpisah per tenant memberi isolasi terkuat serta backup dan restore per pelanggan yang paling sederhana, tetapi paling mahal dan paling sulit dimigrasi, karena setiap perubahan schema harus diterapkan ke semua database. Schema per tenant, yaitu satu set tabel per pelanggan di dalam satu database bersama, berada di tengah: isolasi cukup baik, tetapi jumlah tabel tumbuh tanpa batas dan query lintas tenant menjadi merepotkan.
Model ketiga adalah shared schema: semua tenant berada di tabel yang sama, dan setiap baris membawa kolom tenant_id yang menyatakan pelanggan mana pemiliknya. Ini memberi kepadatan tertinggi dan biaya terendah, serta hanya satu schema untuk dimigrasi, tetapi menaruh tanggung jawab isolasi pada filter yang benar. Lupakan satu filter tenant_id pada satu query dan Anda membocorkan data pelanggan lain. Kelemahan tunggal itulah yang justru ingin dihilangkan oleh Row-Level Security.
Untuk tim satu sampai tiga orang yang menagih langganan sederhana, mulailah dengan shared schema dan kolom tenant_id. Model ini paling murah dijalankan dan paling mudah dimigrasi, dan RLS menutup satu-satunya celah nyatanya. Simpan database-per-tenant untuk pelanggan enterprise langka yang punya kebutuhan kepatuhan.
Row-Level Security adalah fitur PostgreSQL yang memfilter baris mana yang boleh dilihat atau diubah oleh sebuah query, dievaluasi di dalam database pada setiap statement. Setelah Anda mengaktifkannya di sebuah tabel dan menulis sebuah policy, database itu sendiri menolak mengembalikan baris yang bukan milik tenant saat ini, apa pun yang diminta kode aplikasi. Itulah pergeseran kuncinya: isolasi berhenti bergantung pada aplikasi yang harus ingat menambahkan filter.
Penyiapannya kecil. Tambahkan kolom tenant_id ke setiap tabel milik tenant, aktifkan row security pada masing-masing, dan tambahkan policy yang hanya mengizinkan baris yang tenant_id-nya sama dengan nilai yang dibaca dari session setting. Karena aplikasi Anda terhubung sebagai peran biasa alih-alih superuser, policy berlaku pada setiap query yang dijalankannya. Di awal setiap request, setelah memastikan siapa penggunanya, setel variabel session ke id tenant mereka di dalam transaksi yang sama yang mengerjakan pekerjaan itu.
Indexing penting karena RLS secara efektif menambahkan kondisi kesetaraan tenant_id ke setiap query. Letakkan tenant_id di posisi pertama pada index yang menopang lookup umum Anda, sehingga index gabungan pada tenant_id bersama kolom yang Anda filter atau urutkan menjaga query tetap cepat. Tanpa itu, database bisa memindai baris semua tenant sebelum membuang yang disembunyikan policy, dan itu makin lambat saat tenant tersibuk Anda membesar.
Menyetel konteks tenant pada koneksi pooled dan lupa meresetnya adalah kebocoran RLS klasik. Ikat setting ke transaksi agar otomatis bersih saat commit atau rollback, alih-alih ke seluruh session yang hidup lebih lama dari request.
RLS hanya sekuat cara Anda menerapkannya. Sebagian besar kebocoran antar tenant berasal dari segelintir kesalahan yang bisa diprediksi, dan setiap satunya mudah diperiksa sebelum Anda rilis.
Untuk tim SaaS kecil di Indonesia, shared schema dengan kolom tenant_id dan PostgreSQL Row-Level Security memberi isolasi kelas enterprise dengan biaya jalan terendah. Aktifkan RLS, paksakan pada pemilik tabel, jalankan sebagai peran non-superuser, dan batasi setting tenant ke setiap transaksi. Lakukan itu, dan tidak ada satu bug aplikasi pun yang bisa membocorkan data satu pelanggan ke pelanggan lain.