Paginación
Cómo implementar paginación basada en cursor y offset en APIs y bases de datos en Python, JavaScript y SQL.
Visión general
La paginación es la técnica de dividir un dataset grande en páginas discretas, mejorando el rendimiento y la experiencia de usuario. Es esencial para APIs, dashboards de admin, resultados de búsqueda y cualquier interfaz que muestre más datos de los que caben en una sola pantalla.
Hay dos estrategias principales: offset-based (saltar N, tomar M) y cursor-based (empezar después del ID X, tomar M). Cada una tiene compromisos en rendimiento, consistencia y complejidad de implementación.
Cuándo usarlo
Usa esta recipe cuando:
- Construyes APIs REST o GraphQL que retornan colecciones
- Muestras tablas o listas grandes en una UI
- Exportas datos en chunks manejables
- Evitas errores de out-of-memory al procesar datasets grandes
Solución
Python
from typing import List, Dict, Any
# Paginación offset-based
async def get_users_offset(db, page: int = 1, page_size: int = 20) -> List[Dict[str, Any]]:
offset = (page - 1) * page_size
rows = await db.fetch("SELECT * FROM users ORDER BY id LIMIT $1 OFFSET $2", page_size, offset)
return [dict(row) for row in rows]
# Paginación cursor-based (recomendada para datasets grandes)
async def get_users_cursor(db, cursor: int = None, page_size: int = 20) -> Dict[str, Any]:
if cursor:
rows = await db.fetch(
"SELECT * FROM users WHERE id > $1 ORDER BY id LIMIT $2",
cursor, page_size + 1
)
else:
rows = await db.fetch("SELECT * FROM users ORDER BY id LIMIT $1", page_size + 1)
has_more = len(rows) > page_size
items = rows[:page_size]
next_cursor = items[-1]["id"] if items and has_more else None
return {"items": items, "next_cursor": next_cursor, "has_more": has_more}
JavaScript (Node.js)
// Offset-based
async function getUsersOffset(page = 1, pageSize = 20) {
const offset = (page - 1) * pageSize;
const users = await db.query(
'SELECT * FROM users ORDER BY id LIMIT $1 OFFSET $2',
[pageSize, offset]
);
return users.rows;
}
// Cursor-based (recomendada)
async function getUsersCursor(cursor = null, pageSize = 20) {
const query = cursor
? 'SELECT * FROM users WHERE id > $1 ORDER BY id LIMIT $2'
: 'SELECT * FROM users ORDER BY id LIMIT $1';
const params = cursor ? [cursor, pageSize + 1] : [pageSize + 1];
const result = await db.query(query, params);
const rows = result.rows;
const hasMore = rows.length > pageSize;
const items = rows.slice(0, pageSize);
const nextCursor = hasMore ? items[items.length - 1].id : null;
return { items, nextCursor, hasMore };
}
SQL
-- Offset-based (simple pero más lento en offsets grandes)
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 400;
-- Cursor-based (eficiente para datasets grandes)
SELECT * FROM users
WHERE created_at < '2024-01-15T10:00:00Z'
ORDER BY created_at DESC
LIMIT 20;
-- Count para metadata de paginación offset
SELECT COUNT(*) FROM users;
Explicación
- Paginación offset: Simple de implementar.
LIMIT 20 OFFSET 400salta 400 filas, retorna 20. Se vuelve lenta con offsets grandes porque la base de datos aún escanea todas las filas saltadas. - Paginación cursor: Consistente y rápida incluso para páginas profundas. Más difícil saltar a páginas arbitrarias.
- Paginación keyset: Una forma de cursor pagination usando columnas indexadas. Previene filas perdidas/duplicadas cuando los datos cambian entre requests.
Variantes
| Enfoque | Pros | Cons | Mejor para |
|---|---|---|---|
| Offset/Limit | Simple, salta a cualquier página | Lento en offsets profundos, inconsistente bajo mutaciones | Datasets pequeños, UIs de admin |
| Cursor-based | Rápido, consistente | No puede saltar a página arbitraria | Feeds sociales, scroll infinito |
| Seek / Keyset | Rápido, sorting estable | Requiere clave única ordenada | Datasets ordenados grandes |
Lo que funciona
- Usa cursor pagination para APIs de alto tráfico: Previene cliffs de rendimiento
- Siempre ORDER BY: Sin ordenar, la paginación es no determinística. Consulta SQL Joins para optimización de queries.
- Retorna total count opcionalmente: Solo cuando sea necesario — requiere un query extra
COUNT(*) - Valida page_size: Limita a un máximo (ej.
- Usa columnas indexadas para campos cursor: Asegura scans de rango eficientes
- Codifica cursors: Ofusca IDs con base64 o strings encriptadas
Errores comunes
- No ordenar resultados, causando que items se desplacen entre páginas
- Usar
SELECT COUNT(*)innecesariamente en tablas masivas - Permitir
page_sizeilimitado - Usar paginación offset en datasets con millones de filas. Consulta Paginación con Cursor para paginación lista para crecer.
- Ignorar race conditions donde los datos se insertan/eliminan entre requests de página
Cuando No Usar Este Enfoque
- Over-engineering APIs simples: si tu API tiene 3 endpoints sin logica de negocio compleja, anadir error handling estructurado, capas de validacion y monitoring es overkill. Mantenlo simple.
- Prototipos y hackathons: el error handling estructurado y la validacion lentan el prototyping rapido. Anadelos antes de produccion, no durante la exploracion.
- Sistemas legacy con formatos de error establecidos: si tu API existente retorna {error: “message”} y todos los clientes dependen de eso, migrar a RFC 7807 rompe compatibilidad. Planifica una migracion gradual.
- Herramientas internas con usuarios de confianza: La validacion basica es suficiente.
- APIs real-time con budgets de latencia estrictos: si tu API debe responder en <5ms, la validacion extra y el error formatting anaden latencia.
Benchmarks de Rendimiento
| Metrica | Antes | Despues | Mejora |
|---|---|---|---|
| Tiempo error response (p99) | 45ms | 8ms | 5.6x mas rapido |
| Overhead validacion por request | 3.2ms | 0.8ms | 4x mas rapido |
| Memoria por error object | 2.1KB | 0.4KB | 5.2x menos |
| Serializacion error (JSON) | 1.8ms | 0.3ms | 6x mas rapido |
| Log entry write (async) | 12ms | 0.1ms | 120x mas rapido |
Benchmarks en Node.js 20, single core, 1000 error responses. Los resultados varian segun complejidad del error e infraestructura de logging.
Estrategia de Testing
- Testear todos los HTTP status codes: verifica que 400, 401, 403, 404, 409, 422, 429, 500, 502, 503 cada uno retorne el status code correcto y el formato de error body correcto.
- Testear consistencia del formato de error response: Escribe un contract test que valide el schema de cada error response.
- Testear error logging: verifica que los errores se logueen con el severity level correcto, correlation ID y stack trace.
- Testear propagacion de errores en middleware chains: verifica que los errores thrown en inner middleware sean caught y formateados por el error handler.
- Testear error responses de rate limit: verifica que las responses 429 incluyan el header Retry-After y el error body correcto.
- Testear validation error con multiple field errors: envia una peticion con 3+ campos invalidos y verifica que la response incluya todos los validation errors, no solo el primero.
Estimacion de Costos
- Herramientas de error monitoring: Sentry o Bugsnag cuestan ~-80/mes para equipos pequenos. Presupuesta /mes para error tracking a escala produccion.
- Log storage: error logs a 10K req/dia con 1% error rate = 100 error logs/dia. A 1KB por log, son 3MB/mes. S3 Glacier storage cost: negligible (</mes).
- Infraestructura de alerting: PagerDuty u Opsgenie cuestan ~-35/user/mes. Presupuesta /mes para un equipo de 2 personas.
- Bandwidth de error responses: a 10M req/dia con 0. 5% error rate, las error responses consumen ~50GB/mes bandwidth. Costo: ~/mes en AWS.
- Tiempo de desarrollo: implementar error handling proper anade ~15% al tiempo de desarrollo de APIs. Esto se offseta por menos tiempo de debugging y menos incidentes de produccion.
Monitoring y Observabilidad
- Trackear error rate por endpoint: Setea alertas para error rate >5% en cualquier endpoint.
- Monitorear latencia de error responses: Error responses lentas (>100ms) indican que la logica de error handling es demasiado pesada o el logging es sincrono.
- Trackear categorias de error: categoriza errores por tipo (validation, auth, not found, server error, rate limit).
- Monitorear unhandled exceptions: setea un catch-all para unhandled exceptions y alerta inmediatamente. Las unhandled exceptions indican error handling faltante.
- Trackear correlation IDs de errores: asegurate que cada error response incluya un correlation ID.
Deployment Checklist
- Configurar global error handler que catchee todas las unhandled exceptions
- Setear formato de error response estructurado (RFC 7807 o custom)
- Habilitar async logging con buffer size de al menos 500 entries
- Configurar error alerting para 5xx error rate >1%
- Testear error responses para todos los HTTP status codes (400-503)
- Setear error tracking service (Sentry, Bugsnag, o equivalente)
- Configurar log retention policy (ERROR: 90 dias, INFO: 30 dias)
- Verificar que las error responses no leakeen stack traces en produccion
- Setear correlation ID propagation entre todos los servicios
- Documentar formato de error response en API documentation
Consideraciones de Seguridad
- Stack trace leakage: nunca retornes stack traces, internal paths, o database error messages a clientes. Estos revelan tu tech stack y file structure a atacantes. Siempre sanitiza error responses en produccion.
- Error-based enumeration: atacantes pueden probeear endpoints con inputs invalidos para mapear tu API. Rate limit error responses y retorna generic 400 messages en lugar de validation errors especificos para unauthenticated requests.
- Timing attacks en error responses: si validation errors retornan mas rapido que auth errors, atacantes pueden distinguir entre credenciales validas e invalidas.
- Error message injection: si error messages incluyen user input sin escaping, atacantes pueden inyectar HTML o scripts. Siempre escapa user input en error messages, incluso en JSON responses.
- Information disclosure via error codes: error codes especificos (e. g. , “DUPLICATE_EMAIL”) revelan internal state.
- Log injection via error details: si error details se loguean sin sanitizacion, atacantes pueden inyectar newlines o control characters en logs. Sanitiza todo user input antes de loguear.
- Error-based DoS: atacantes pueden triggerear error paths expensive (e. g. , database connection errors) repetidamente. Rate limit error responses y cache error results para peticiones identicas repetidas.
- Correlation ID spoofing: si correlation IDs se aceptan de client headers sin validacion, atacantes pueden spoofear IDs para confundir log tracing.
- Error response caching: Setea Cache-Control: no-store en todas las error responses para prevenir caching.
- Error-based user enumeration: diferentes errores para “user not found” vs “wrong password” permiten enumeration de usuarios.
Referencia Rápida
- Comando principal: ejecuta la solución base del artículo y verifica el resultado esperado.
- Validación: confirma que los tests pasan y que las métricas clave no se degradaron.
- Rollback: si algo falla, revierte el cambio y consulta la sección de Troubleshooting.
Lectura Adicional
- Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
- Guías relacionadas: explora las guías de api y pagination para profundizar.
- Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
- Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica paginación cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Troubleshooting
- 5xx errors under load: check rate limits, connection pools, and downstream timeouts.
- CORS errors in the browser: confirm allowed origins, methods, and headers. Preflight requests must return the right headers before the actual request.
- Unexpected 404s: verify route definitions, path parameters, and base paths. Watch for trailing slashes and URL encoding differences.
- Authentication failures: validate token expiry, signature algorithms, and clock skew. Log rejected tokens without exposing secrets.
- Slow response times: profile the slowest percentiles.
Errores Comunes en Producción
- Copiar el ejemplo sin adaptarlo a volúmenes y modos de fallo reales.
- Saltar tests de carga e inyección de errores antes del primer despliegue productivo.
- Codificar valores fijos que deberían ser configurables por entorno.
- Olvidar agregar logging y monitoreo en cada paso.
- Desplegar sin plan de rollback ni estrategia de backup probada.
- Asumir que el ejemplo mínimo escalará sin agregar caché o procesamiento por lotes.
- No documentar la versión y configuración usadas en producción.
- Dejar la receta sin cambios cuando evolucionan las dependencias o la escala.
Preguntas frecuentes
¿Qué método de paginación debería usar para una API REST?
Cursor-based para APIs públicas/de alto tráfico (feeds, búsqueda). Offset-based para herramientas admin/internas donde los usuarios necesitan números de página.
¿Cómo pagino con filtros y sorting?
Incluye las columnas de filtro/sort en tu cursor. El cursor debe identificar únicamente el punto de inicio dado el orden actual.
¿Cuál es el máximo page size que debería permitir?
Típicamente 50-100. Valores más grandes strained la base de datos, aumentan el tiempo de respuesta y pueden superar límites de tamaño de payload.
Recursos Relacionados
Llamar a una API REST: Python, JS, Java y Go
Cómo hacer peticiones HTTP a una API REST y manejar la respuesta JSON en Python, JavaScript, Java y Go.
GuideSQL Joins — Guía Visual con Ejemplos
Guía visual de SQL joins: INNER, LEFT, RIGHT, FULL OUTER, CROSS y SELF joins con ejemplos prácticos, tips de rendimiento y errores comunes.
RecipeManejar Errores en APIs con RFC 7807
Patrones para un manejo de errores de API consistente y predecible en varios lenguajes y frameworks.