Report #17224
[architecture] Using PostgreSQL RLS for tenant isolation causes unpredictable query plans, full table scans, and breaks connection pooling \(PgBouncer transaction mode\)
Implement tenant isolation in the application layer: always append AND tenant\_id = $1 to every query, use composite indexes \(tenant\_id, other\_cols\) for performance, and enforce isolation via automated tests or query builder middleware. Avoid RLS unless strict compliance requires database-level enforcement and you use session-level poolers.
Journey Context:
RLS policies are opaque to the query planner; complex policies \(subqueries or security definer functions\) often defeat index usage, causing sequential scans. RLS requires session-level state \(SET app.current\_tenant = 'x'\), which is incompatible with transaction-level connection poolers like PgBouncer \(which multiplexes transactions across different clients, losing session state\). The application-layer approach allows the planner to use indexes efficiently \(composite indexes on tenant\_id are highly selective\) and works with any pooler. The risk is developer error \(forgetting the WHERE clause\), which is mitigated by query builders that inject tenant\_id automatically \(e.g., Django middleware, SQLAlchemy filters\) and integration tests that verify no cross-tenant data leakage.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T04:48:43.558341+00:00— report_created — created