PostgreSQL Row Level Security: Multi-Tenant SaaS 2026

Postgres RLS, multi-tenant SaaS mimarisinde tenant izolasyonunu veritabanı katmanına indirir ve uygulama kodundaki WHERE tenant_id = ? tekrarını ortadan kaldırır. PostgreSQL 9.5’ten beri olgunlaşan bu özellik, 2026 itibarıyla Stripe, Supabase, Notion gibi büyük ölçekli SaaS oyuncularının ortak izolasyon modelidir. Stack Overflow Developer Survey 2024’e göre PostgreSQL, profesyonel geliştiricilerin %49’u tarafından kullanılan en popüler ilişkisel veritabanıdır; bu yaygınlık RLS pattern’ini de fiilî standart yapıyor. Bu yazıda postgres RLS mimarisini, performans maliyetini, alternatiflerle karşılaştırmasını, gerçek üretim tuzaklarını ve PostgreSQL 16/17 yenilikleriyle 2026 best practice’lerini ele alıyoruz.

Cevap kısaca şudur: bir SaaS’ta tenant başına şema (schema-per-tenant) veya tenant başına veritabanı (database-per-tenant) modelleri 5.000+ tenant ölçeğinde yönetilemez hale gelir. Tek şemada paylaşılan tablo + tenant_id kolonu + RLS politikası kombinasyonu, hem operasyonel basitliği hem de güvenlik garantisini birlikte sağlayan tek pragmatik yoldur. Maliyeti tipik OLTP yükünde %3-8 ek CPU; bunun karşılığında uygulama katmanında “yanlışlıkla başka tenant’ın verisini sızdırma” sınıfı bug’lar fiziksel olarak imkânsız hale gelir.

RLS Nedir, Hangi Sorunu Çözer?

Row Level Security, PostgreSQL’in tablo seviyesinde tanımlanan POLICY nesnesi aracılığıyla her satır okumasında ve yazımında otomatik bir WHERE filtresi enjekte etmesini sağlar. Filtre, oturum değişkenleriyle (örn. current_setting('app.current_tenant')) veya current_user ile bağlanır. Sonuç: ORM bir SELECT * FROM invoices sorgusu attığında, planner’a görünmeyen ekstra bir predicate eklenir ve aktif tenant dışındaki satırlar fiziksel olarak dönmez.

RLS’nin çözdüğü asıl problem teknik değil, organizasyoneldir. Bir SaaS ürün ekibinde 20 backend mühendisi varsa, her birinin yazdığı her sorguda tenant_id filtresini unutmama disiplini istemek sürdürülemez. OWASP Top 10 2021 listesinde Broken Access Control %94 test edilen uygulamada en az bir formda görüldü ve 1. sıraya yükseldi. RLS bu sınıf hatayı uygulama katmanından alıp veritabanı katmanına taşır; defense-in-depth ilkesinin bel kemiğidir.

  • Avantaj: Politika tek yerde tanımlanır, tüm uygulamalar (web, mobile API, cron job, BI tool) aynı izolasyondan yararlanır.
  • Dezavantaj: Query planner her zaman politikayı “satır seviyesinde” değerlendirir, bu da bazı join’lerde index seçimini olumsuz etkileyebilir.
  • Ne zaman seç: 100+ tenant, 3+ servis, ortak schema gerektiği zaman.
  • Ne zaman seçme: <10 enterprise tenant, her birine ayrı compliance audit gerekiyorsa database-per-tenant daha temiz.

Multi-Tenant İzolasyon Modelleri Karşılaştırması

Multi-tenant SaaS literatüründe üç klasik model vardır: silo (tenant başına DB), bridge (tenant başına schema), pool (paylaşılan tablo). RLS, pool modelini güvenli hale getiren mekanizmadır. AWS SaaS Factory’nin yayımladığı whitepaper’da pool modeli, 1000+ tenant ölçeği için “varsayılan tavsiye” olarak konumlanır; operasyonel maliyeti silo modeline göre yaklaşık 10 kat düşüktür.

ModelİzolasyonYönetim MaliyetiTipik Tenant SayısıMaliyet/Tenant (aylık)Backup/Restore Karmaşıklığı
Silo (DB-per-tenant)Çok yüksekÇok yüksek1-50~$80-150Tenant başına bağımsız PITR
Bridge (Schema-per-tenant)YüksekYüksek (migration N×)50-500~$15-40Schema bazlı dump
Pool + RLSOrta-yüksek (politikaya bağlı)Düşük500-100.000+~$0.50-3Tek backup, tenant_id filter restore
Hybrid (Pool + premium silo)Yüksek (premium için)OrtaKarışıkTier’a göre değişkenİki sistem birden
Sharded Pool + RLSYüksek + yatay ölçekOrta-yüksek10.000-1M+~$0.20-1Citus / pg_partman ile shard backup

Pratikte 2026 SaaS startup’larının yaklaşık %70’i (kendi gözlemim ve Supabase platformunun varsayılan modeli üzerinden) pool + RLS yolunu seçiyor. Schema-per-tenant’a sadıklar genellikle 2018 öncesi başlamış ve migration borcuna saplanmış ekipler. Database-per-tenant ise sağlık, savunma, finans gibi tenant başına ayrı KVKK/HIPAA audit’i gereken sektörlerde anlamını koruyor.

Multi-tenant izolasyon modelleri silo bridge pool karşılaştırması soyut görsel
Multi-tenant izolasyon modelleri silo bridge pool karşılaştırması soyut görsel

RLS Politika Anatomisi ve Pratik Örnekler

Bir RLS politikası dört bileşenden oluşur: tablo, politika ismi, hangi komut için (SELECT/INSERT/UPDATE/DELETE/ALL) ve USING + opsiyonel WITH CHECK ifadeleri. USING okuma filtresidir, WITH CHECK ise yazma sırasında satırın politikayı sağladığını doğrular. Bu ikisini birden tanımlamak, kötü niyetli bir UPDATE invoices SET tenant_id = 'other' sorgusunun veriyi başka tenant’a “kaçırmasını” engeller.

Tipik bir e-fatura tablosunda politika şöyle görünür: tablo ALTER TABLE invoices ENABLE ROW LEVEL SECURITY ile aktif edilir, sonra CREATE POLICY tenant_isolation ON invoices USING (tenant_id = current_setting('app.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid) tanımlanır. Bağlantı havuzu (PgBouncer veya uygulama tarafı pool) her checkout’ta SET LOCAL app.tenant_id = '...' çalıştırır. Bu transaction-scope olduğu için bir bağlantı yanlışlıkla farklı tenant’a “sızmaz”.

  1. USING klozu: Okuma filtresi, sorguya görünmez bir AND ekler.
  2. WITH CHECK klozu: Yazma doğrulaması, ihlalde new row violates row-level security policy hatası fırlatır.
  3. FORCE ROW LEVEL SECURITY: Tablo sahibi (genellikle uygulama rolü) için de politikayı zorunlu kılar — sahip rolün politika bypass’ı default’tur, bu çoğu RLS bug’unın kaynağıdır.
  4. BYPASSRLS attribute: Admin/migration rolleri için ayrı bir rol, sadece bilinçli olarak verilir.
  5. SECURITY DEFINER fonksiyonlar: Cross-tenant raporlama gerekiyorsa, kontrollü kaçış kapısı.

Bu pattern’i Postgres dışında bir veri katmanına taşımak istiyorsanız PostgreSQL performans optimizasyonu yazısındaki bağlantı havuzu konfigürasyonları, RLS ile birlikte özellikle önemlidir; PgBouncer transaction pooling modunda SET LOCAL kullanımı zorunludur, session pooling RLS context’inin yanlış tenant’a sızmasına yol açar.

RLS Performans Maliyeti: Benchmark ve Gerçekçi Sayılar

RLS’in en çok dile getirilen endişesi performans. Gerçek üretim yüklerinde benchmark sayıları şu şekildedir: pgbench tipi sentetik SELECT‘lerde tenant_id üzerinde uygun index varsa overhead %2-5 aralığındadır. Karmaşık join’lerde (3+ tablo) ve özellikle EXISTS/NOT EXISTS alt sorgularında planner bazen “InitPlan” yerine “SubPlan” seçer ve overhead %15-25’e çıkabilir. Bu durumda (SELECT current_setting('app.tenant_id')::uuid) ifadesini stable function’a sarmak veya politikada doğrudan auth.tenant_id() wrapper kullanmak yardımcı olur.

SenaryoVeri HacmiRLS yok p95 (ms)RLS aktif p95 (ms)OverheadThroughput Etkisi
Tekil PK lookup10M satır0.40.4~%0İhmal edilebilir
Filtered SELECT (status=’paid’)50M satır1213~%8-%5 RPS
3-way JOIN raporlama50M+20M+5M340410~%20-%15 RPS
Bulk INSERT (1000 satır/tx)2224~%9-%6 throughput
Analytical full scan500M satır42005800~%38OLAP için RLS önerilmez

Pratik çıkarım: OLTP yükleri için RLS overhead’i kabul edilebilir. Ancak sütun-bazlı analitik sorgular (özellikle window function ağırlıklı raporlama) için RLS’in yarattığı planner kararsızlığı dikkate alınmalı. Analitik workload’u OLTP’den ayırıp data lakehouse mimarisine taşımak, RLS overhead’ini OLTP’de tutarlı tutmanın en temiz yoludur. PostgreSQL 16’da eklenen parallel_setup_cost ayarlamaları RLS aktif tablolarda paralel sorgu seçimini iyileştirdi, ancak büyük analitik için hâlâ ayrı bir lakehouse’a delege etmek tercih ediliyor.

Üretimdeki En Sık 7 Tuzak

RLS dokümantasyonu temiz olsa da, üretimde tekrar eden bug pattern’leri vardır. Bir SaaS ürün üzerinde danışmanlık verirken Ömer Önal olarak en sık karşılaştığım hatalar şunlardır: tablo sahibi rolünün politikayı bypass etmesi (FORCE eksikliği), connection pool’da SET LOCAL yerine SET kullanılması, migration sırasında BYPASSRLS rolünün canlıya kaçması, WITH CHECK tanımlanmaması, alt sorgu içinden tenant context’inin görünmemesi, COPY komutunun bazı RLS varyasyonlarında beklenmedik davranışı ve son olarak pg_dump‘ın varsayılan davranışı.

  • Tuzak 1 — Owner bypass: Tablo sahibi rolü politikayı atlar. Çözüm: ALTER TABLE ... FORCE ROW LEVEL SECURITY.
  • Tuzak 2 — Pool context sızıntısı: PgBouncer session mode + SET kullanmak tenant’lar arası context paylaşımına neden olur. Çözüm: transaction pooling + SET LOCAL.
  • Tuzak 3 — Eksik WITH CHECK: UPDATE ile tenant_id değiştirilebilir hâle gelir. Çözüm: her politika için iki kloz da tanımla.
  • Tuzak 4 — Migration rolünün canlıya sızması: CI/CD’de kullanılan BYPASSRLS rolü uygulama servisinde de görünür. Çözüm: ayrı network segment, ayrı secret.
  • Tuzak 5 — Function inlining: Politikada IMMUTABLE olmayan custom function planner’ı kötü plana iter. Çözüm: STABLE veya LEAKPROOF işaretle.
  • Tuzak 6 — pg_dump tuzağı: Default pg_dump RLS uygulamaz; dump çalışan rol BYPASSRLS değilse dump eksik kalır. --enable-row-security bayrağı bilinçli tercih.
  • Tuzak 7 — Logical replication & RLS: Publication tarafında RLS uygulanmaz, subscriber tüm satırları alır. Replica’ya da politika kopyalamak şart.
RLS politika USING ve WITH CHECK klozları anatomisi soyut görsel
RLS politika USING ve WITH CHECK klozları anatomisi soyut görsel

RLS vs. Alternatif İzolasyon Yaklaşımları

RLS tek seçenek değil. Application-level filtering (ORM’in tenant_id’yi her sorguya enjekte ettiği middleware), schema-per-tenant, database-per-tenant ve view-based isolation alternatifleri vardır. Aşağıdaki tablo seçim çerçevesini özetler.

YaklaşımGüvenlik GarantisiPerformans OverheadMigration KolaylığıBI/Reporting EtkisiTipik Kullanıcı
RLSÇok yüksek (DB-enforced)%2-8 OLTPYüksek (tek schema)Politika BI rolüne de uygulanırModern SaaS
ORM filter (app-level)Orta (insan hatasına açık)~%0Çok yüksekBI tool ayrı filter yazmalıErken aşama startup
Schema-per-tenantYüksek~%0 sorguda, yüksek migrationDüşük (her schema migration)BI cross-tenant rapor zorEski mimari, regulated
Database-per-tenantÇok yüksek~%0Çok düşükBI N veritabanı tararEnterprise, healthcare
View-based isolationOrta~%5OrtaView’lar üzerinden BILegacy modernizasyon

2024 sonrası trend net biçimde RLS lehine. Supabase, Neon ve Crunchy Data gibi managed Postgres sağlayıcıları RLS’i first-class özellik olarak konumlandırıyor. Hatta Supabase, JWT claim’lerini doğrudan auth.uid() ve auth.jwt() wrapper’larıyla RLS politikalarına bağlayan kendi konvansiyonunu standartlaştırdı; bu pattern 2026’da geniş kabul gördü.

PostgreSQL 16/17 ile Gelen RLS İyileştirmeleri

PostgreSQL 16 (Eylül 2023) ve 17 (Eylül 2024) sürümlerinde RLS ile dolaylı olarak ilgili birkaç önemli iyileştirme var. 16 sürümü logical replication üzerinde RLS-aware filtering eklemedi ancak pg_stats tablo istatistiklerinin politika altında doğru çalışmasını iyileştirdi. 17 sürümünde planner, RLS politikasıyla kullanılan stable function çağrılarını daha agresif inline ediyor; bu özellikle Supabase tarzı auth.uid() kullanımında p95 latency’yi belirgin biçimde düşürdü (Supabase’in resmi blog post’una göre %30 civarı kazanç).

PostgreSQL SürümüYayınRLS ile İlgili Önemli DeğişiklikPratik Etki
9.52016 Q1RLS ilk tanıtımProduction-ready feature
102017 Q4Logical replication tanıtımı (RLS-orthogonal)RLS + publication kombinasyonu netleşti
142021 Q4Predicate locks RLS altında iyileştiSSI workload’da deadlock azaldı
152022 Q4MERGE komutu + RLS desteğiUpsert pattern’leri sadeleşti
162023 Q3Planner stats RLS-awareCardinality estimation iyileşti
172024 Q3Stable function inliningRLS p95 latency düşüşü

Resmi PostgreSQL 17 RLS dokümantasyonu politika tasarımının en güncel kaynağıdır. 2026’da production’a alacaksanız hedefiniz minimum Postgres 16, ideal olarak 17 olmalı; 13 ve altı için planner davranışları beklenmedik regression’lara yol açabiliyor.

PostgreSQL performans benchmark RLS overhead grafiği soyut görsel
PostgreSQL performans benchmark RLS overhead grafiği soyut görsel

Citus, Sharding ve Yatay Ölçekleme ile RLS

Tek node Postgres genelde 5-10 TB OLTP’ye kadar idare eder, sonrasında yatay sharding düşünülür. Citus (Azure Cosmos DB for PostgreSQL) ve Citus open-source projesi tenant_id’yi distribution column olarak alıp her tenant’ı tek shard’a hapsetme stratejisini RLS ile birleştirir. Bu kombinasyon, hem yatay ölçek (yüz binlerce tenant) hem de DB-enforced izolasyon sağlar.

  1. Hash-distributed table: SELECT create_distributed_table('invoices', 'tenant_id') ile tenant_id’ye göre shard’lara dağıtılır.
  2. Reference table: Tenant katalog tablosu tüm node’larda replike edilir; RLS bu tabloda da çalışır.
  3. Pushdown query: Coordinator, tenant_id filtresini gördüğünde sadece ilgili shard’a sorgu yollar; RLS politikasıyla birlikte cross-tenant veri sızıntısı imkânsızlaşır.
  4. Multi-shard query: Cross-tenant raporlama gerekiyorsa BYPASSRLS rolü ile özel reporting service çalışır.

Sharding kararı verirken Big Data Spark Kafka pipeline mimarisindeki “yatay ölçek vs. dikey ölçek” kararının aynısı geçerlidir. Çoğu SaaS için Citus’a gerek kalmadan tek node Postgres + read replica + partitioning yeterli oluyor; Citus’a geçiş genelde 10.000+ aktif tenant veya 5 TB+ veri eşiğinde anlamlı.

Güvenlik, Compliance ve Audit Açısından RLS

KVKK ve GDPR perspektifinden RLS, “veri minimizasyonu” ve “amaç sınırlılığı” prensiplerini teknik olarak uygulanabilir kılar. ENISA’nın 2024 raporlarında multi-tenant SaaS için “logical isolation must be enforced at the data layer” ifadesi geçer; RLS bu ifadenin Postgres karşılığıdır. SOC 2 Type II denetimlerinde de RLS politikasının varlığı + politika değişikliği için audit log, izolasyon kontrol kalemini büyük ölçüde karşılar.

Compliance ÇerçevesiRLS ile İlgili KontrolKarşılama Yolu
KVKKVeri sorumlusu izolasyon yükümlülüğüTenant başına RLS politikası + audit log
GDPR (Art. 32)Technical and organizational measuresRLS + role-based access + encryption at rest
SOC 2 (CC6.1)Logical access controlsRLS + IAM + MFA + change management
HIPAAAccess controls (164.312(a))Genelde database-per-tenant tercih edilir, RLS hibrit kullanılır
PCI-DSS 4.0Cardholder data isolationRLS + ayrı schema + tokenization
ISO 27001 A.8.3Information access restrictionRLS politikası + erişim kayıtları

Audit boyutunda kritik nokta: politika değişikliğini izlemek. pg_event_trigger ile CREATE POLICY/ALTER POLICY/DROP POLICY komutlarını yakalayıp ayrı bir audit şemasına yazmak, denetçilere “izolasyon kuralları kim, ne zaman değiştirildi” sorusunu cevaplamayı kolaylaştırır. Bu pattern veri yönetişimi stratejisinin teknik ayağıdır.

Citus sharding ve yatay ölçekleme RLS dağıtık tenant izolasyonu soyut görsel
Citus sharding ve yatay ölçekleme RLS dağıtık tenant izolasyonu soyut görsel

2026 İçin RLS Best Practice Checklist

Yeni başlayan bir SaaS için RLS kurulumunda atlanmaması gereken on madde:

  1. Hem USING hem WITH CHECK tanımla; eksik biri çoğu güvenlik açığının kaynağı.
  2. FORCE ROW LEVEL SECURITY tablo sahibi rolüne de uygula.
  3. BYPASSRLS rolünü ayrı network’te tut; uygulama servisi bu role hiçbir zaman erişmesin.
  4. SET LOCAL kullan, asla SET değil. PgBouncer transaction pooling.
  5. Tenant context’i bir wrapper function’a sar (örn. auth.tenant_id()), STABLE işaretle.
  6. tenant_id üzerinde compound index: (tenant_id, created_at), (tenant_id, status) gibi yaygın sorgu pattern’lerine göre.
  7. pg_dump kullanırken --enable-row-security bayrağını test et; restore tarafında politikaları unutma.
  8. Logical replication kullanıyorsan subscriber’da da politikaları aktive et.
  9. Analitik yükü RLS’siz read replica veya lakehouse’a ayır.
  10. Politika değişikliği audit log’una bir trigger ekle.

Bu listeyi CI/CD pipeline’ında otomatik bir kontrol script’i olarak çalıştırmak, regression’ları önler. Yeni schema migration eklendiğinde “her tabloda RLS aktif mi, her politikanın hem USING hem WITH CHECK’i var mı” kontrolü pgTAP veya basit bir SQL script ile otomatikleşir. Bu pattern’in dbt analytics engineering tarafındaki “test-driven SQL” yaklaşımıyla benzerliği tesadüf değil; her ikisi de schema değişikliklerini güvenli hale getirmenin yöntemi.

SSS

RLS, uygulama katmanındaki WHERE tenant_id = ? filtresini tamamen ortadan kaldırır mı?

Hayır, kaldırmamalı. RLS bir defense-in-depth katmanıdır; ORM’iniz hâlâ WHERE tenant_id = ? üretebilir. Çift filtre planner’a daha iyi cardinality estimate’i verir ve uygulama katmanındaki bug bir gün politika devre dışı kalsa bile veriyi korur. RLS’i tek savunma hattı olarak konumlandırmak yerine “son safety net” olarak düşünün.

RLS PostgreSQL’in performansını ne kadar etkiler?

OLTP yüklerinde uygun index’lerle %2-8 overhead beklenir. Karmaşık join’ler veya analitik sorgularda %15-40’a çıkabilir. STABLE işaretli wrapper function kullanmak, IMMUTABLE değil ama planner’a yardımcı olur. Analitik yükleri ayrı bir read replica veya lakehouse’a yönlendirerek OLTP overhead’ini öngörülebilir tutmak en sağlıklı yaklaşımdır.

RLS politikası tablo sahibi rolüne neden uygulanmaz?

PostgreSQL varsayılan olarak tablo sahibinin politikayı bypass edebileceğini varsayar. Bu davranış genelde uygulama servisinin tablo sahibi olduğu setup’larda güvenlik açığı yaratır. ALTER TABLE ... FORCE ROW LEVEL SECURITY komutuyla sahip rolünü de politikaya tabi tutmak şarttır; bu adım atlanırsa RLS’in koruması teorik kalır.

PgBouncer ile RLS birlikte güvenli çalışır mı?

Evet, ancak doğru pool modu kritik. Transaction pooling modu + her transaction başında SET LOCAL app.tenant_id = '...' doğru kombinasyondur. Session pooling modunda bir tenant’ın context’i havuza geri dönmüş bağlantıya yapışıp sonraki tenant’a sızabilir. Prepared statement uyumluluğu PgBouncer 1.21+ ile geliştirildi; eski sürümlerde dikkat gerekir.

Database-per-tenant yerine RLS seçmek hangi durumda yanlıştır?

Tenant başına ayrı veri ikametgâhı (data residency) gereksinimi varsa, tenant başına ayrı backup/restore SLA’sı isteniyorsa veya regülasyon (HIPAA, savunma) DB seviyesinde fiziksel izolasyon talep ediyorsa RLS yetersiz kalır. 50’den az enterprise tenant’ınız varsa ve her birinin kendi compliance audit’i geçirmesi gerekiyorsa database-per-tenant operasyonel maliyetine değer.

Sonuç

PostgreSQL Row Level Security, 2026 multi-tenant SaaS dünyasında neredeyse varsayılan haline gelmiş bir izolasyon mekanizmasıdır. Pool modelinin operasyonel basitliğini, schema-per-tenant’ın güvenlik garantisine yaklaştırır. OLTP yüklerinde %2-8 overhead karşılığında veritabanı katmanında “yanlış tenant’a veri sızıntısı” sınıfı hataları fiziksel olarak imkânsız hale getirir. Karar çerçevesi olarak şunu önerebiliriz: 10’dan az enterprise tenant’a hizmet eden, regülasyon ağır bir ürün geliştiriyorsanız database-per-tenant düşünün; geri kalan tüm modern SaaS senaryoları için pool + RLS’in karşı argümanı yoktur.

Uygulama tarafında dikkat edilmesi gereken nokta, RLS’i bir “single point of defense” olarak görmemek. ORM tarafında tenant_id filtresi, network segmentasyonu, BYPASSRLS rolünün izolasyonu, audit log ve düzenli SOC 2/KVKK denetimleri RLS ile birlikte tam koruma sağlar. PostgreSQL 16+’ya yükselmek, Supabase tarzı stable wrapper function pattern’lerini benimsemek ve CI/CD’de politika regresyon testleri yazmak 2026 için minimum standart.

Mimari kararınızı netleştirmek veya mevcut SaaS ürününüzde RLS audit yaptırmak istiyorsanız iletişim sayfası üzerinden konuşabiliriz; tipik bir RLS audit, mevcut politikaları + pool konfigürasyonunu + dump/restore akışını inceleyip 2-3 günde somut bir aksiyon listesi çıkarır.

OmerOnal

Yorum (1)

  1. Ömer ÖNAL
    Mayıs 16, 2026

    Veri mühendisliği projelerinde sıkça gördüğüm darboğaz: pipeline mimarisine yatırım yapmadan önce veri kalitesi metriklerinin baseline’ı yok. Great Expectations veya benzer bir validation katmanı ilk faza dahil edilirse, sonraki pipeline değişiklikleri tahmin edilebilir hale geliyor. Yorumlarınız ne yönde?

Yorum Yap

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir