Qué es la paginación de APIs: estrategias de cursor y offset
La paginación de APIs divide resultados grandes en páginas. Compara offset, número de página, cursor y keyset, con ejemplos SQL, cabecera Link y errores.
¿Qué es la paginación de APIs?
La paginación de APIs devuelve un conjunto grande de resultados en trozos más pequeños, llamados páginas, a lo largo de varias peticiones. En lugar de una respuesta con un millón de registros, el cliente pide 50 o 100 cada vez y solicita el siguiente trozo cuando lo necesita. Así se protege la base de datos, la memoria y la red, y los tiempos de respuesta se mantienen predecibles.
La estrategia elegida afecta a la velocidad, la corrección y lo que puede hacer el cliente. La paginación por offset es la más simple; la de cursor y la keyset aguantan mejor con datos grandes o cambiantes. Una mala elección es un motivo habitual de que un endpoint se vuelva más lento al crecer los datos.
Principales estrategias de paginación
| Estrategia | Forma de la petición | Ideal para |
|---|---|---|
| Offset y límite | ?offset=200&limit=50 | Conjuntos pequeños, saltar a una página |
| Número de página | ?page=5&per_page=50 | Igual que offset con una URL más amable |
| Cursor | ?cursor=abc123&limit=50 | Feeds, scroll infinito, datos cambiantes |
| Keyset (seek) | ?after_id=12345&limit=50 | Conjuntos grandes con orden estable |
Offset y límite
El cliente indica cuántas filas saltar y cuántas devolver.
SELECT * FROM users
ORDER BY created_at, id
LIMIT 50 OFFSET 200;Ventajas: fácil de construir, permite saltar a la página 7 y hace sencillo el total. Inconvenientes: la base de datos sigue teniendo que recorrer todas las filas omitidas, así que las páginas profundas se vuelven lentas. Si entre peticiones se insertan o borran filas, los elementos pueden repetirse o perderse.
Paginación por cursor
El servidor devuelve un token opaco que marca dónde terminó la última página, y el cliente lo reenvía para obtener la siguiente.
GET /api/users?cursor=eyJpZCI6MTIzNDV9&limit=50
{
"data": [ ... ],
"next_cursor": "eyJpZCI6MTIzOTV9",
"has_more": true
}Un cursor suele ser una clave de ordenación codificada en base64, como {"id": 12345}. Ventajas: estable cuando cambian los datos, rápido si la clave está indexada, natural para el scroll infinito. Inconvenientes: no permite saltar a una página arbitraria, y el total exige una consulta aparte.
Paginación keyset (seek)
La paginación keyset es lo que un cursor suele hacer por debajo: la consulta filtra por el último valor visto en lugar de saltar filas.
SELECT * FROM users
WHERE id > 12345
ORDER BY id
LIMIT 50;Con una ordenación compuesta, compare las columnas juntas. Las bases de datos que admiten comparación de filas, como PostgreSQL, aceptan esto:
SELECT * FROM users
WHERE (created_at, id) > ('2026-01-15', 12345)
ORDER BY created_at, id
LIMIT 50;Con un índice sobre las columnas de ordenación, la base de datos salta directamente al punto de partida, así que la página 1.000 cuesta aproximadamente lo mismo que la 1.
Por qué los offsets profundos son lentos
Con páginas de 50, la base de datos tiene que recorrer las filas omitidas antes de devolver las 50 pedidas. La tabla muestra ese coste en una consulta con offset frente a una keyset. Es aritmética simple, no un benchmark, y los tiempos reales dependen de los índices y del motor.
| Página | Filas a recorrer más las devueltas (offset) | Filas a recorrer más las devueltas (keyset) |
|---|---|---|
| 1 | 50 | 50 |
| 10 | 500 | 50 |
| 100 | 5.000 | 50 |
| 1.000 | 50.000 | 50 |
| 10.000 | 500.000 | 50 |
Cómo encuentran los clientes la página siguiente
Hay dos patrones habituales. El primero pone la navegación en la cabecera Link, definida en RFC 8288 (Web Linking, que sustituyó a la RFC 5988):
Link: <https://api.example.com/users?page=3&per_page=50>; rel="next",
<https://api.example.com/users?page=1&per_page=50>; rel="prev"La API REST de GitHub funciona así, con las relaciones next, prev, first y last, y limita per_page a 100 en la mayoría de los endpoints. Los clientes deben seguir esas URL en lugar de construir las suyas. El segundo patrón pone metadatos en el payload de la respuesta: un next_cursor y un indicador has_more, o un objeto pagination con página, tamaño y totales. Las APIs de GraphQL suelen seguir el patrón de conexiones con cursor de Relay, con edges, pageInfo.hasNextPage y endCursor, y la documentación de GraphQL recomienda la paginación por cursor.
Buenas prácticas
- Fije un tamaño por defecto y uno máximo. Por ejemplo, de 25 a 50 por defecto y un tope documentado. Rechace o recorte todo lo que lo supere.
- Use una ordenación estable y única. Añada el id como desempate, o las filas con valores iguales se repetirán o desaparecerán entre páginas.
- Indexe las columnas de ordenación. Si no, cada página es una ordenación completa.
- Prefiera keyset o cursor en tablas grandes o en crecimiento. Deje el offset para listas de administración pequeñas.
- Cuidado con los totales.
COUNT(*)en una tabla grande es lento. Ofrezca una estimación o nada. - Documente el esquema. Indique en la especificación OpenAPI cómo pedir la página siguiente y cuándo termina la lista.
- Cachee la primera página. Es la más solicitada. Véase caché de APIs.
Errores habituales de paginación
- Offsets profundos.
offset=100000en una tabla grande es la consulta lenta clásica. - Ordenación no única. Ordenar por una columna con empates duplica u omite filas.
- Sin límite máximo. Un cliente envía
limit=1000000y usted se ha provocado una denegación de servicio. - Exponer cursores internos. Los clientes empiezan a interpretarlos y el formato ya no se puede cambiar. Manténgalos opacos.
- Contarlo todo en cada petición. La consulta de recuento puede costar más que la página.
- Olvidar el final de la lista. Devuelva una señal explícita, como
has_more: falseo unnext_cursorvacío.
La paginación en las pruebas de rendimiento
Los fallos de paginación suelen aparecer bajo carga, no en una sola petición. Una prueba de carga que solo pide la página 1 pasará por alto las consultas lentas de páginas profundas que provocan clientes y rastreadores reales. Incluya en sus escenarios una variedad de páginas, una página profunda de peor caso y el tamaño máximo de página, y observe el percentil 95 del tiempo de respuesta en lugar de la media. Después, vigile esos endpoints con la monitorización de APIs; las comprobaciones gratuitas se ejecutan cada 5 minutos. Empiece con comprobaciones de API gratuitas. Para seguir leyendo: métricas de rendimiento de APIs, problemas habituales de rendimiento de APIs y cómo hacer pruebas de carga a APIs RESTful.
Preguntas frecuentes: paginación de APIs
¿Debo usar paginación por offset o por cursor?
Use cursor o keyset en tablas grandes, feeds en vivo y todo lo que los usuarios recorren con scroll. El offset o el número de página bastan en listas pequeñas donde se salta a una página, como una tabla de administración.
¿Cómo implemento la paginación por cursor?
Ordene por una clave indexada y única. Devuelva la clave del último elemento como cursor codificado. En la siguiente petición, descodifíquelo y consulte las filas posteriores a esa clave con el mismo orden.
¿Cómo devuelvo un total con cursores?
Ejecute una consulta de recuento aparte, devuelva una estimación o prescinda del total. Muchas APIs con cursor no lo exponen a propósito porque los recuentos exactos son caros.
¿Qué tamaño de página debo permitir?
Elija un valor por defecto moderado y un máximo documentado. Lo habitual son máximos en torno a 100, que es lo que usa GitHub en la mayoría de los endpoints. Las páginas más grandes dan respuestas más lentas y pesadas.
¿Los parámetros de paginación van en la query string o en el cuerpo?
En la query string para peticiones GET, porque es lo habitual y se puede cachear. Un cuerpo es aceptable en búsquedas por POST con filtros complejos.
¿Necesito cursores de siguiente y de anterior?
Para la mayoría de los feeds basta avanzar. Admitir páginas anteriores implica una consulta invertida y más casos límite.
Términos relacionados
- ¿Qué es la seguridad de API?
- ¿Qué es el API sprawl? Definición, causas y cómo corregirlo
- Qué es un bearer token: definición, JWT y seguridad
- ¿Qué es el comando curl? Ejemplos, flags y peticiones HTTP
- ¿Qué es la API History?
- ¿Qué es la idempotencia? Métodos HTTP, idempotency keys, ejemplos
- ¿Qué es JSON? Guía para principiantes con sintaxis y ejemplos
- ¿Qué es JSON-RPC? Protocolo, ejemplos y comparación con REST
Herramientas LoadFocus relacionadas
Lleva este concepto a la práctica con LoadFocus, la misma plataforma que potencia todo lo que acabas de leer.