Saltar al contenido
Supabase•4 min de lectura

De MVP a Producción: Supabase en el mundo real

El día que tu MVP aparece en Product Hunt sin avisar descubres qué partes de tu stack eran de juguete. Las conexiones a la base de datos se agotan, la query que funcionaba con 10 usuarios se traba con 1.000, y ese RLS que "así estaba bien" se convierte en tu peor pesadilla. Todo al mismo tiempo, con tráfico real.

Lo mejor: casi todo se corrige antes del lanzamiento. En este post tienes las lecciones de llevar Supabase a producción de verdad: connection pooling, índices, seguridad y escalado.

Connection pooling

Supabase limita las conexiones simultáneas a PostgreSQL. Para producción, necesitas pooling.

Usar Supavisor

Supabase incluye Supavisor, su pooler de conexiones. Configura tu cliente para usarlo:

// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

// Para queries normales (usa el pooler)
export const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
)

// Para operaciones que necesitan conexiones directas (migraciones, etc.)
export const supabaseDirect = createClient(
  process.env.SUPABASE_URL!.replace('.supabase.co', '-pooler.supabase.co'),
  process.env.SUPABASE_ANON_KEY!
)

Configuración en el dashboard

Ve a Settings > Database > Connection pooling:

  • Mode: Transaction (recomendado para la mayoría de casos)
  • Pool size: 15-25 (free tier), 50+ (pro)
  • Default timeout: 10 segundos

Performance

Índices

Crea índices en columnas que filtras frecuentemente:

-- Índice en user_id (muy común)
CREATE INDEX idx_tasks_user_id ON tasks(user_id);

-- Índice compuesto para queries comunes
CREATE INDEX idx_tasks_user_completed ON tasks(user_id, completed);

-- Índice para ordenamiento por fecha
CREATE INDEX idx_tasks_created_at ON tasks(created_at DESC);

Analizar queries lentas

-- Ver queries lentas
SELECT * FROM pg_stat_statements 
ORDER BY mean_exec_time DESC 
LIMIT 10;

-- Analizar una query específica
EXPLAIN ANALYZE 
SELECT * FROM tasks 
WHERE user_id = 'uuid' 
AND completed = false 
ORDER BY created_at DESC;

Pagination

No traigas todos los datos de una vez:

// src/pagination.ts
const PAGE_SIZE = 20

export async function getTasks(page: number = 1) {
  const from = (page - 1) * PAGE_SIZE
  const to = from + PAGE_SIZE - 1

  const { data, error, count } = await supabase
    .from('tasks')
    .select('*', { count: 'exact' })
    .range(from, to)
    .order('created_at', { ascending: false })

  return {
    data,
    total: count,
    page,
    totalPages: Math.ceil((count || 0) / PAGE_SIZE)
  }
}

Seguridad en producción

Revisar políticas de RLS

Antes de ir a producción, audita tus políticas:

-- Ver todas las políticas
SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual 
FROM pg_policies 
WHERE schemaname = 'public';

Service Role Key

Nunca expongas la service role key en el cliente. Guárdala en variables de entorno del servidor:

// src/lib/supabase-admin.ts (SOLO SERVIDOR)
import { createClient } from '@supabase/supabase-js'

export const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
)

Rate limiting

Supabase no tiene rate limiting built-in. Implementa en tu Edge Function o API:

// supabase/functions/api/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

const rateLimit = new Map<string, { count: number; reset: number }>()

serve(async (req) => {
  const ip = req.headers.get('x-forwarded-for') || 'unknown'
  const now = Date.now()
  const limit = rateLimit.get(ip)

  if (limit && limit.count > 100 && now < limit.reset) {
    return new Response('Rate limit exceeded', { status: 429 })
  }

  if (!limit || now > limit.reset) {
    rateLimit.set(ip, { count: 1, reset: now + 60000 })
  } else {
    limit.count++
  }

  // Tu lógica aquí
})

Migraciones

Supabase CLI

// Crear migración
supabase migration new add_tasks_table

// Aplicar migraciones localmente
supabase db reset

// Aplicar en producción
supabase db push

Estructura de migraciones

supabase/
  migrations/
    20240101000000_create_users.sql
    20240102000000_create_tasks.sql
    20240103000000_add_rls_policies.sql

Ejemplo de migración

-- supabase/migrations/20240102000000_create_tasks.sql
CREATE TABLE tasks (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  completed BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Habilitar RLS
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- Políticas
CREATE POLICY "Users can view own tasks" ON tasks FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own tasks" ON tasks FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own tasks" ON tasks FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own tasks" ON tasks FOR DELETE USING (auth.uid() = user_id);

-- Índices
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_created_at ON tasks(created_at DESC);

Monitoring

Logs en el dashboard

Ve a Logs para ver:

  • API logs
  • Auth logs
  • Database logs
  • Edge Function logs

Métricas importantes

Monitorea:

  • Conexiones activas: No debe llegar al límite
  • Query duration: Queries > 100ms necesitan optimización
  • Error rate: Aumento indica problemas
  • Storage usage: No exceder el plan

Alertas

Configura alertas en tu proveedor de hosting (Vercel, Netlify, etc.) para:

  • Errores 500
  • Latencia alta
  • Caídas del servicio

Backups

Supabase backups

  • Free tier: Backups diarios, retención de 7 días
  • Pro: Backups diarios, retención de 28 días
  • Team/Enterprise: Point-in-time recovery

Backup manual

// Backup de la base de datos
supabase db dump > backup.sql

// Restaurar
psql -h db.xxx.supabase.co -U postgres -d postgres < backup.sql

Costos

Free tier limits

RecursoLímite
Database500 MB
Storage1 GB
Auth users50,000
Bandwidth500 MB
Edge Functions500K invocations

Optimizar costos

  1. Database: Usa índices, optimiza queries, archiva datos viejos
  2. Storage: Comprime imágenes, usa CDN, elimina archivos no usados
  3. Bandwidth: Usa transformaciones de imágenes, cachea respuestas
  4. Edge Functions: Reduce invocations, usa batch operations

Cuándo upgrade

  • Pro ($25/mes): Cuando necesitas más de 500MB de DB o backups mejorados
  • Team ($599/mes): Cuando necesitas SOC2, HIPAA, o soporte prioritario

Checklist de producción

Antes de lanzar:

  • [ ] RLS habilitado en todas las tablas
  • [ ] Service role key solo en el servidor
  • [ ] Índices en columnas frecuentemente consultadas
  • [ ] Connection pooling configurado
  • [ ] Migraciones versionadas
  • [ ] Backups configurados
  • [ ] Monitoreo activo
  • [ ] Rate limiting implementado
  • [ ] Error handling sólido
  • [ ] Logs centralizados

Qué sigue

En el próximo (y último) artículo vamos con Supabase para Mobile: cómo usar Supabase con Flutter y React Native para apps nativas.


> Más posts