En resumen
Para conjuntos de datos grandes, utiliza la paginación basada en cursor o en claves en lugar de la paginación basada en desplazamiento (offset). La paginación por desplazamiento (?page=1&limit=20) tiene un rendimiento deficiente con millones de registros y permite la inconsistencia de datos. Modern PetstoreAPI implementa la paginación basada en cursor con tokens opacos y enlaces HATEOAS para obtener resultados eficientes y consistentes.
Introducción
Tu API devuelve una lista de mascotas. Tienes 10 millones de mascotas en la base de datos. Un cliente solicita GET /pets?page=500000&limit=20. Tu base de datos ejecuta OFFSET 10000000 LIMIT 20. La consulta tarda 30 segundos. Tu API agota el tiempo de espera.
Este es el problema de la paginación por desplazamiento. Funciona bien para conjuntos de datos pequeños, pero falla a gran escala. La base de datos debe escanear millones de filas para alcanzar el desplazamiento, aunque solo devuelvas 20 resultados.
El antiguo Swagger Petstore no aborda la paginación en absoluto. Modern PetstoreAPI implementa la paginación basada en cursor que escala a millones de registros con un rendimiento consistente.
En esta guía, aprenderás por qué falla la paginación por desplazamiento, cómo funciona la paginación basada en cursor y cómo Modern PetstoreAPI implementa una paginación eficiente.
Por qué la paginación por desplazamiento falla a escala
La paginación por desplazamiento es el enfoque más común, pero tiene serios problemas.
Cómo funciona la paginación por desplazamiento
GET /pets?page=1&limit=20 → OFFSET 0 LIMIT 20
GET /pets?page=2&limit=20 → OFFSET 20 LIMIT 20
GET /pets?page=3&limit=20 → OFFSET 40 LIMIT 20
La base de datos omite las filas de offset y devuelve las filas de limit.
Problema 1: El rendimiento se degrada con el número de página
Página 1:
SELECT * FROM pets OFFSET 0 LIMIT 20;
-- Rápido: escanea 20 filas
Página 1000:
SELECT * FROM pets OFFSET 20000 LIMIT 20;
-- Lento: escanea 20,020 filas, devuelve 20
Página 500,000:
SELECT * FROM pets OFFSET 10000000 LIMIT 20;
-- Muy lento: escanea 10,000,020 filas, devuelve 20
La base de datos debe escanear todas las filas hasta el desplazamiento, aunque las descarte. El rendimiento se degrada linealmente con el número de página.
Problema 2: Resultados inconsistentes
Mientras un cliente navega por los resultados, los datos cambian:
Solicitud 1:
GET /pets?page=1&limit=2
Devuelve: [Mascota A, Mascota B]
Alguien añade Mascota Z (se ordena primero alfabéticamente)
Solicitud 2:
GET /pets?page=2&limit=2
Devuelve: [Mascota B, Mascota C] ← ¡Mascota B aparece dos veces!
La Mascota B apareció en ambas páginas porque se insertó una nueva mascota. Por el contrario, las mascotas pueden omitirse si se producen eliminaciones.
Problema 3: La paginación profunda es costosa
Los usuarios rara vez van más allá de la página 10. Pero si tu API permite ?page=1000000, debes manejarlo. Las consultas de paginación profunda son costosas y pueden usarse para ataques de denegación de servicio.
Cuándo es aceptable la paginación por desplazamiento
La paginación por desplazamiento funciona bien para:
- Conjuntos de datos pequeños (< 10,000 registros)
- APIs internas con uso controlado
- Interfaces de administración donde los usuarios no harán paginación profunda
- Datos que cambian con poca frecuencia
Para APIs públicas o conjuntos de datos grandes, utiliza la paginación basada en cursor.
Paginación basada en cursor explicada
La paginación basada en cursor utiliza un token opaco para marcar la posición en el conjunto de resultados.
Cómo funciona
Solicitud 1:
GET /pets?limit=20
Respuesta 1:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9",
"hasMore": true
}
}
Solicitud 2:
GET /pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20
El cursor es un token opaco (generalmente codificado en base64) que codifica la posición. El cliente no lo analiza, solo lo devuelve.
Beneficios
1. Rendimiento consistente
La base de datos utiliza un índice para encontrar la posición del cursor directamente:
SELECT * FROM pets
WHERE id > '019b4132-70aa-764f-b315-e2803d882a24'
ORDER BY id
LIMIT 20;
Esta consulta es rápida independientemente de la posición en el conjunto de datos. Utiliza una búsqueda de índice, no un escaneo.
2. Resultados consistentes
Los cursores son estables. Si los datos cambian entre solicitudes, seguirás obteniendo resultados consistentes. Los nuevos registros no causan duplicados ni saltos.
3. Sin ataques de paginación profunda
Los clientes no pueden saltar a posiciones arbitrarias. Deben paginar secuencialmente, lo que limita el abuso.
Formato del cursor
Los cursores suelen ser JSON codificados en base64:
// Cursor decodificado
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"createdAt": "2026-03-13T10:30:00Z"
}
El cursor contiene suficiente información para reanudar la paginación. Para Modern PetstoreAPI, esto incluye el ID del recurso y el campo de ordenación.
Paginación por conjunto de claves (keyset pagination) para datos ordenados
La paginación por conjunto de claves es una variante de la paginación basada en cursor para datos ordenados.
Cómo funciona
En lugar de un cursor opaco, utilizas el último valor de la página anterior:
Solicitud 1:
GET /pets?limit=20&sortBy=createdAt
Respuesta 1:
{
"data": [
{"id": "...", "createdAt": "2026-03-13T10:00:00Z"},
...
{"id": "...", "createdAt": "2026-03-13T10:30:00Z"}
]
}
Solicitud 2:
GET /pets?limit=20&sortBy=createdAt&after=2026-03-13T10:30:00Z
El parámetro after utiliza el último valor de createdAt de la página anterior.
Consulta SQL
SELECT * FROM pets
WHERE created_at > '2026-03-13T10:30:00Z'
ORDER BY created_at
LIMIT 20;
Esto es eficiente porque utiliza un índice en created_at.
Cuándo usar la paginación por conjunto de claves
- Los datos están ordenados naturalmente (por marca de tiempo, ID, etc.)
- Los clientes necesitan entender la clave de paginación
- Deseas una paginación transparente (no cursores opacos)
Modern PetstoreAPI utiliza la paginación basada en cursor por defecto, pero admite la paginación por conjunto de claves para datos de series temporales.
Cómo Modern PetstoreAPI implementa la paginación
Modern PetstoreAPI utiliza la paginación basada en cursor con enlaces HATEOAS.
Formato de solicitud
GET /pets?limit=20
GET /pets?cursor={token}&limit=20
Parámetros:
limit- Número de resultados por página (por defecto: 20, máx: 100)cursor- Token de paginación opaco de la respuesta anterior
Formato de respuesta
{
"data": [
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"species": "CAT"
}
],
"pagination": {
"limit": 20,
"hasMore": true,
"nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9"
},
"links": {
"self": "https://petstoreapi.com/pets?limit=20",
"next": "https://petstoreapi.com/pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20"
}
}
Características clave
1. Cursores opacos
Los cursores están codificados en base64. Los clientes no los analizan.
2. Enlaces HATEOAS
El objeto links proporciona URLs listas para usar. Los clientes no necesitan construir URLs de paginación.
3. Bandera hasMore
Indica si existen más resultados. Los clientes saben cuándo dejar de paginar.
4. Validación de límite
El límite máximo es 100. Evita que los clientes soliciten páginas enormes.
Consulta la documentación de paginación de Modern PetstoreAPI para obtener detalles completos.
Formato de respuesta de paginación
Modern PetstoreAPI envuelve las respuestas paginadas en una estructura consistente.
Contenedor de colección
{
"data": [...],
"pagination": {...},
"links": {...}
}
¿Por qué envolver colecciones?
- Extensibilidad - Puede añadir metadatos sin romper clientes
- Consistencia - Todos los puntos finales paginados usan el mismo formato
- HATEOAS - Los enlaces guían a los clientes a través de la paginación
Metadatos de paginación
"pagination": {
"limit": 20,
"hasMore": true,
"nextCursor": "...",
"totalCount": 1000 // Opcional, costoso de calcular
}
totalCount es opcional porque calcularlo es costoso para grandes conjuntos de datos. La mayoría de los clientes no lo necesitan.
Probando la paginación con Apidog
Apidog te ayuda a probar el comportamiento de la paginación de forma exhaustiva.
Escenarios de prueba
1. Primera página
GET /pets?limit=20
Esperar: 20 resultados, hasMore=true, nextCursor presente
2. Páginas subsiguientes
GET /pets?cursor={token}&limit=20
Esperar: 20 resultados, hasMore=true/false, nextCursor presente/ausente
3. Última página
GET /pets?cursor={lastToken}&limit=20
Esperar: < 20 resultados, hasMore=false, sin nextCursor
4. Resultados vacíos
GET /pets?status=NONEXISTENT&limit=20
Esperar: 0 resultados, hasMore=false, sin nextCursor
5. Validación de límite
GET /pets?limit=1000
Esperar: 400 Bad Request (excede el límite máximo)
Configuración de pruebas de Apidog
// Test: Estructura de paginación
pm.test("Response has pagination", () => {
pm.expect(pm.response.json()).to.have.property('pagination');
pm.expect(pm.response.json().pagination).to.have.property('hasMore');
});
// Test: Enlaces HATEOAS
pm.test("Response has links", () => {
const links = pm.response.json().links;
pm.expect(links).to.have.property('self');
if (pm.response.json().pagination.hasMore) {
pm.expect(links).to.have.property('next');
}
});
Eligiendo la estrategia de paginación adecuada
Diferentes estrategias se adaptan a diferentes casos de uso.
Paginación por desplazamiento
Usar cuando:
- El conjunto de datos es pequeño (< 10,000 registros)
- Los usuarios necesitan acceso aleatorio (saltar a la página 50)
- Los datos cambian con poca frecuencia
- API interna con uso controlado
No usar cuando:
- El conjunto de datos es grande (> 100,000 registros)
- El rendimiento importa
- Los datos cambian con frecuencia
Paginación basada en cursor
Usar cuando:
- El conjunto de datos es grande
- El rendimiento importa
- Los datos cambian con frecuencia
- El acceso secuencial es suficiente
No usar cuando:
- Los usuarios necesitan acceso aleatorio
- La complejidad del cursor es una preocupación
Paginación por conjunto de claves
Usar cuando:
- Los datos están ordenados naturalmente
- Se prefiere la paginación transparente
- El rendimiento importa
No usar cuando:
- El orden de clasificación es complejo
- Se necesitan múltiples campos de clasificación
Recomendación de Modern PetstoreAPI: Utilizar paginación basada en cursor para APIs públicas y grandes conjuntos de datos.
Conclusión
La paginación es fundamental para las APIs que devuelven grandes conjuntos de datos. La paginación por desplazamiento es simple pero no escala. La paginación basada en cursor proporciona un rendimiento consistente y resultados fiables para millones de registros.
Modern PetstoreAPI implementa la paginación basada en cursor con tokens opacos, enlaces HATEOAS y metadatos adecuados. Este diseño escala de manera eficiente y proporciona una excelente experiencia para el desarrollador.
Prueba tu implementación de paginación con Apidog para asegurarte de que maneje casos extremos, valide límites y devuelva resultados consistentes.
Puntos clave:
- Evita la paginación por desplazamiento para grandes conjuntos de datos
- Usa la paginación basada en cursor para la escalabilidad
- Envuelve las colecciones con metadatos y enlaces
- Prueba la paginación a fondo con Apidog
- Sigue los patrones de paginación de Modern PetstoreAPI
Preguntas frecuentes
¿Por qué no simplemente devolver todos los resultados sin paginación?
Devolver millones de registros en una sola respuesta causa problemas de memoria, transferencia de red lenta y una mala experiencia de usuario. La paginación es esencial para grandes conjuntos de datos.
¿Pueden los clientes saltar a una página específica con paginación por cursor?
No, la paginación por cursor requiere acceso secuencial. Si se necesita acceso aleatorio, considera la paginación por desplazamiento para conjuntos de datos pequeños o implementa búsqueda/filtrado en su lugar.
¿Cómo manejo la paginación con filtrado?
Incluye los parámetros de filtro en las solicitudes de paginación: GET /pets?status=AVAILABLE&cursor={token}&limit=20. El cursor codifica tanto la posición como el estado del filtro.
¿Debo incluir el recuento total en las respuestas de paginación?
Solo si los clientes lo necesitan y tu conjunto de datos es pequeño. Calcular el recuento total es costoso para grandes conjuntos de datos (requiere una consulta COUNT separada).
¿Cómo implemento la paginación por cursor en SQL?
Utiliza una cláusula WHERE con el valor del cursor: SELECT * FROM pets WHERE id > ? ORDER BY id LIMIT 20. Asegúrate de tener un índice en la columna de ordenación.
¿Qué pasa si mis tokens de cursor se vuelven inválidos?
Devuelve un 400 Bad Request con un mensaje de error. Los cursores pueden volverse inválidos si se eliminan datos o si el estado de paginación expira.
¿Cuánto tiempo deben permanecer válidos los cursores?
Los cursores de Modern PetstoreAPI son válidos indefinidamente siempre que exista el recurso al que hacen referencia. Algunas APIs caducan los cursores después de 24 horas.
¿Puedo usar la paginación por cursor con múltiples campos de ordenación?
Sí, pero el cursor debe codificar todos los campos de ordenación. Esto hace que los cursores sean más complejos. Considera usar una única clave de ordenación compuesta en su lugar.
