Tu RLS Policy Es un Filter Que No Scalea
Tienes una tabla orders con 10.000 filas. Aplicas RLS con tenant_id = auth.jwt() -> 'tenant_id'.
Funciona. El usuario solo ve sus datos.
Pero cuando escalan a 5 millones de filas, tu query tarda 4 segundos. El explain plan muestra un sequential scan que RLS injecta en cada consulta.
El problema real no es la seguridad. Es que RLS sin arquitectura adecuada convierte tu base de datos en un filtro caro.
La mayoría implementa RLS como medida de seguridad puntual. Pocos diseñan policies pensando en cómo interactúan con el query planner de PostgreSQL.
Este es el framework que separa los sistemas que escalan de los que se caen en producción.
Cómo PostgreSQL Ejecuta RLS Internamente
Row Level Security no es un WHERE clause que tú escribes. Es un policy engine que PostgreSQL aplica antes de devolver resultados.
Cuando ejecutas:
PostgreSQL realmente ejecuta:
El problema: current_setting() se evalúa por cada fila escaneada. Sin index en tenant_id, tienes un sequential scan con filtro.
El Query Planner No Sabe de Tus Policies
PostgreSQL optimiza queries. Pero no sabe que tu RLS policy filtra por tenant_id hasta que ejecuta la policy.
El resultado: PostgreSQL escanea todas las filas antes de filtrar por tenant.
Pattern 1: Bypass Policies con SECURITY DEFINER Functions
La manera más performante de acceder a datos es bypassear RLS completamente cuando el contexto ya está validado.
Ahora tu API llama get_tenant_orders(tenant_id) directamente. El query planner recibe una query con filtro directo, usa el index, y el rendimiento mejora drásticamente.
¿Cuándo Usar Bypass?
❌ NO lo uses para acceso directo desde el cliente (RLS existe para eso)
✅ Úsalo para queries complejas desde Edge Functions o APIs internas donde ya tienes validación de tenant
Pattern 2: Indexación Estratégica para RLS
El secreto para queries rápidas con RLS es diseñar indexes que el query planner pueda usar antes de aplicar la policy.
El orden de columnas importa. PostgreSQL usa el index de izquierda a derecha.
RLS Hints para el Query Planner
En PostgreSQL 15+, puedes usar pg_settings para pasar hints al planner:
Pattern 3: JWT Claims como Filtro Primera Clase
Supabase inyecta JWT claims en auth.jwt() automáticamente. Pero muchos developers no saben que puedes acceder a claims anidados y usarles en policies.
El Error de Usar auth.uid() Solo
❌ MAL:
Esta subquery se ejecuta por cada fila. En 5M de filas, tienes 5M de subqueries.
✅ BIEN:
El JWT ya contiene el tenant_id. No necesitas hacer lookup en cada query.
Pattern 4: Row Security con Roles Múltiples
En sistemas multi-tenant, necesitas diferentes niveles de acceso. No es lo mismo un usuario normal que un admin de tenant.
Service Role: Cuándo Hacer Bypass Real
El service_role de Supabase no tiene RLS por defecto. Esto es peligroso pero útil para ciertos casos.
❌ NUNCA hagas:
✅ SIEMPRE:
Framework: Implementación Paso a Paso
Step 1: Define tu Modelo de Tenancy
Antes de escribir una línea de SQL, decide:
Para el 95% de aplicaciones, Opción A con RLS bien diseñado es suficiente.
Step 2: Configura JWT con Claims de Tenant
En tu sistema de autenticación, incluye tenant_id en el JWT:
Step 3: Diseña Policies Defensivas
Step 4: Benchmark Antes y Después
El Anti-Pattern Que Destruye Rendimiento
❌ NUNCA HAGAS ESTO:
Tienes tres niveles de subqueries, cada una se ejecuta por fila. Para 1M de filas, eso son 3M de operaciones de lookup.
✅ SIEMPRE HAZ ESTO:
Validación y Testing de Policies
Supabase incluye herramientas para probar policies antes de deployar:
Resumen: Lo Que Necesitas Implementar Hoy
- Añade `tenant_id` al JWT en tu sistema de autenticación
- Crea indexes con `tenant_id` como primera columna en todas las tablas multi-tenant
- Usa functions SECURITY DEFINER para queries complejas que ya validan contexto
- Mide el overhead de RLS con EXPLAIN ANALYZE regularmente
- Nunca anides subqueries en policies — cada nivel multiplica el coste
La seguridad de datos y el rendimiento no están en extremos opuestos. Con la arquitectura correcta, RLS puede ser transparente para el usuario e invisible para el query planner.
Tu próximo paso: abre tu cliente de Supabase y ejecuta EXPLAIN ANALYZE en tu tabla más grande con RLS activo. Si ves sequential scans, ya sabes qué optimizar.
Artículos relacionados
- Vercel Deployment Best Practices: Lo Que Nadie Te Cuenta Sobre Observabilidad y Rollbacks
- Resend en Producción: Monitorización, Analíticas y Gestión de Bounces que Nadie Te Explica
- MCP en Producción: El Pattern de Composición que Nadie Te Explica
---
¿Quieres recibir contenido como este cada semana? Suscríbete a mi newsletter

