La respuesta corta
La paginación con offset es adecuada para conjuntos de datos pequeños y estáticos donde los usuarios necesitan saltar a páginas arbitrarias. La paginación con cursor es el valor por defecto correcto para colecciones en crecimiento o actualizadas frecuentemente, porque evita la degradación de rendimiento y los problemas de deriva de datos que introducen los enfoques basados en offset a escala. La paginación con keyset es un punto medio que ofrece navegación estable sin la complejidad de codificación del cursor.
Por qué importa la elección para tu API
Cuando tu API sirve miles o millones de registros, devolver todo en una sola respuesta no es viable. La paginación divide los resultados en fragmentos manejables, pero el método que elijas impacta directamente en tu factura de infraestructura, en la latencia que ven tus clientes y en la fiabilidad bajo carga. Para una startup que monetiza por uso o por席位, un endpoint mal paginado se convierte en un incidente operativo: consultas que consumen cada vez más CPU a medida que los offsets crecen, picos de latencia que degradan la experiencia de usuario y costes de base de datos que escalan con el peor caso de uso en lugar del caso medio. Los tres patrones REST que verás en producción —basada en offset, basada en cursor y basada en keyset— tienen compensaciones distintas que afectan al coste por solicitud, a la estabilidad bajo escrituras concurrentes y al esfuerzo de ingeniería para mantener la API.
Paginación basada en offset
La paginación con offset utiliza dos parámetros: un offset (el número de fila inicial) y un límite (cuántas filas devolver). Esto se mapea directamente a las cláusulas LIMIT y OFFSET de SQL que la mayoría de los desarrolladores ya conocen.
Cómo funciona:
GET /api/productos?offset=0&limit=10
GET /api/productos?offset=10&limit=10
GET /api/productos?offset=20&limit=10
Cuándo funciona bien:
- Conjuntos de datos pequeños (cientos a unos pocos miles de filas)
- Datos estáticos o que cambian raramente
- Interfaces administrativas donde saltar a la página 50 es un requisito real
- Implementaciones simples donde el tiempo del desarrollador es el cuello de botella
Cuándo falla:
El problema fundamental es que la base de datos debe recorrer y descartar cada fila antes del offset. Si solicitas offset 50.000 con un límite de 10, la base de datos recorre 50.010 filas y descarta las primeras 50.000. A medida que crece el offset, el tiempo de consulta crece proporcionalmente, lo que se traduce directamente en mayor latencia para el cliente final y mayor carga de CPU en tu base de datos. Este coste es lineal con el offset y se agrava en tablas grandes.
Un segundo problema es la deriva de datos. Si se insertan o eliminan filas entre solicitudes paginadas, el usuario ve registros duplicados o huecos. La documentación de paginación de la API REST de GitHub describe este comportamiento: el contenido agregado o eliminado entre solicitudes de página puede causar que los resultados se desplacen de forma impredecible, un riesgo real para cualquier API que sirva datos activos.
Paginación basada en cursor
La paginación con cursor reemplaza el offset numérico por un valor de cursor, típicamente un token opaco que codifica la posición en el conjunto de resultados. El cliente pasa el cursor de la respuesta anterior para obtener la siguiente página.
Cómo funciona:
GET /api/productos?limit=10
→ la respuesta incluye next_cursor: "eyJpZCI6MTIzNH0="
GET /api/productos?limit=10&cursor=eyJpZCI6MTIzNH0=
→ la respuesta incluye next_cursor: "eyJpZCI6MTIzNX0="
Cuándo funciona bien:
- Conjuntos de datos grandes o en crecimiento
- Datos actualizados frecuentemente o en tiempo real
- Interfaces de desplazamiento infinito
- APIs donde el ordenamiento consistente es más importante que el acceso aleatorio
Cómo implementarla correctamente:
El cursor debe codificar los valores usados en la cláusula ORDER BY, no solo un ID de fila. Un cursor basado solo en un ID autoincremental falla cuando las filas comparten la misma clave de ordenamiento o cuando la columna de orden no es única. Una implementación robusta codifica las columnas de ordenamiento más el ID como desempate:
SELECT * FROM productos
WHERE (fecha_creacion, id) > ('2024-01-15T10:30:00Z', 1234)
ORDER BY fecha_creacion ASC, id ASC
LIMIT 10;
El token del cursor codifica fecha_creacion e id de la última fila. Esto asegura un ordenamiento determinista incluso cuando varias filas comparten la misma marca de tiempo.
La compensación:
La paginación con cursor no soporta acceso aleatorio. No puedes saltar a la página 50. Debes recorrer el cursor secuencialmente. Esta es una decisión de diseño deliberada, no una limitación que debas superar. Si tu caso de uso requiere números de página, la paginación con offset o keyset es más apropiada.
Incluir un conteo total:
Una solicitud común del cliente es mostrar “Página 3 de 47” con paginación por cursor. Esto requiere una consulta COUNT(*) separada, que en tablas grandes es costosa por sí misma: recorrer toda la tabla o un índice completo para producir un único número. Sobre conjuntos de datos pequeños la operación es trivial, pero a escala se convierte en una fuente de latencia adicional y consumo de recursos. Esta consulta extra debe justificarse caso por caso: calcula el coste de un COUNT(*) en tu esquema concreto antes de exponerlo. Como alternativa, puedes aproximar el total con una estimación del motor o cachear el conteo durante un intervalo corto y refrescarlo periódicamente; ambas opciones reducen la carga bajo lecturas masivas. Decide si el beneficio de UX compensa el coste para tu API específica.
Paginación con keyset
La paginación con keyset está estrechamente relacionada con la paginación por cursor pero usa valores de columna explícitos en lugar de un token opaco. El cliente pasa el último valor visto de la columna de ordenamiento directamente.
Cómo funciona:
GET /api/productos?limit=10&after_id=1234
GET /api/productos?limit=10&after_id=1235
Cuándo funciona bien:
- Cuando quieres rendimiento similar al cursor sin tokens opacos
- Cuando la columna de ordenamiento es única o puedes agregar una columna de desempate
- Cuando la depuración y la exploración manual de la API son importantes
La compensación:
La paginación con keyset expone tu modelo de datos al cliente. La columna de ordenamiento se convierte en parte del contrato de la API pública. Si necesitas cambiar el orden de ordenamiento o los nombres de las columnas, rompes a los clientes existentes. La paginación con cursor oculta este detalle detrás de un token codificado, haciendo que los cambios de esquema sean más seguros.
Resumen de comparación
| Aspecto | Offset | Cursor | Keyset |
|---|---|---|---|
| Acceso a página arbitraria | Sí | No | No |
| Rendimiento en offsets profundos | Degrada | Estable | Estable |
| Resistencia a deriva de datos | Pobre | Fuerte | Fuerte |
| Complejidad de implementación | Baja | Media | Baja |
| Resiliencia a cambios de esquema | N/A | Buena | Pobre |
| Soporte de conteo total | Nativo | Requiere consulta extra | Requiere consulta extra |
Cuándo elegir cada patrón
Las cifras de filas que aparecen a continuación son referencias operativas para帮助你 a decidir, no umbrales universales: el corte depende del tamaño de fila, los índices disponibles, el motor de base de datos y tu presupuesto de latencia. Ejecuta un benchmark con un dataset representativo antes de comprometerte con un patrón.
Elige paginación con offset cuando:
- Tu conjunto de datos es lo bastante pequeño para que el coste lineal del offset sea aceptable y crece lentamente
- Tus consumidores de API necesitan saltar a páginas específicas
- Estás construyendo una herramienta administrativa o un reporte donde se esperan números de página
- La simplicidad de
LIMIT/OFFSETpesa más que el costo de rendimiento
Elige paginación con cursor cuando:
- Tu conjunto de datos es lo bastante grande o se modifica con tanta frecuencia que el coste del offset profundo o la deriva de datos se vuelven inaceptables
- Los datos se actualizan frecuentemente y la consistencia entre páginas importa
- Estás construyendo un feed, línea de tiempo o experiencia de desplazamiento infinito
- Quieres evitar la deriva de datos sin exponer columnas de ordenamiento a los clientes
Elige paginación con keyset cuando:
- Quieres paginación estable y performante sin tokens opacos
- Tu columna de ordenamiento es estable y es poco probable que cambie
- La depuración y las pruebas manuales de API son importantes para tu flujo de trabajo
- Estás dispuesto a incorporar la columna de ordenamiento en tu contrato público
Lista de verificación para la implementación
Independientemente del patrón que elijas, estas prácticas aplican:
-
Ordena explícitamente siempre. Nunca confíes en el ordenamiento implícito de la base de datos. Una cláusula
ORDER BYsin índice escaneará toda la tabla. -
Indexa tus columnas de ordenamiento. La paginación con offset en profundidad requiere un índice en la columna de offset. La paginación con cursor y keyset requiere un índice en la clave de ordenamiento más cualquier columna de desempate.
-
Devuelve metadatos de navegación. Incluye
next_cursoro equivalente en cada respuesta. La API REST de GitHub usa encabezadosLinkconrel="next"yrel="prev"para este propósito. Las APIs basadas en cursor deben incluir ambos cursoresnextypreviouscuando corresponda. -
Documenta el formato del cursor. Ya sea opaco o explícito, los clientes necesitan saber cómo extraer y pasar el valor del cursor. Documenta si el cursor es URL-safe, codificado en base64 o un valor crudo.
-
Establece un tamaño máximo de página. Permite que los clientes soliciten un límite, pero aplica un límite superior estricto. Esto evita que una sola solicitud sobrecargue tu base de datos o la memoria de tu cliente, y reduce el radio de impacto de un cliente malicioso o con bugs.
-
Prueba con escrituras concurrentes. Si tus datos cambian mientras un cliente navega por las páginas, verifica que los resultados sean consistentes. Los enfoques de cursor y keyset manejan esto mejor que offset, pero aún debes validar el comportamiento para tu carga de trabajo específica.
-
Monitoriza solicitudes con offset profundo y límites elevados. Trata los offsets grandes como una señal de alerta en tus logs y métricas de APM: suelen indicar un cliente que pagina incorrectamente, un crawler que ignora la documentación o un intento de extracción masiva. Configura alertas en tu sistema de monitorización cuando un cliente solicite offsets inusualmente altos o límites cercanos al máximo permitido, y responde con códigos 400 o 429 antes de que la consulta degrade el servicio para otros tenants. Limitar y observar estos patrones protege la latencia del conjunto de la API y reduce el coste medio por solicitud.
Preguntas frecuentes
¿Puedo combinar paginación con offset y cursor en la misma API?
Sí. Algunas APIs ofrecen paginación con offset para casos simples y paginación con cursor para conjuntos grandes. La API REST de GitHub acepta tanto page+per_page para recorrido simple como encabezados Link estilo cursor (rel="next") para navegación encadenada. Si ofreces ambos, documenta qué endpoints usan cuál patrón y haz que la distinción sea clara en tu especificación OpenAPI.
¿Funciona la paginación con cursor con filtrado?
Sí, pero el cursor debe codificar las condiciones de filtro además de la clave de ordenamiento. Un cursor que solo codifica la posición de ordenamiento producirá resultados incorrectos cuando los filtros cambien el conjunto de resultados. Incluye los parámetros de filtro en el token del cursor o requiere que los clientes pasen los mismos filtros con cada solicitud paginada.
¿Qué pasa con la paginación en GraphQL?
El spec de conexiones de Relay de GraphQL usa paginación basada en cursor por diseño. El cursor en GraphQL típicamente codifica el ID del nodo y la posición, haciéndolo compatible con el enfoque de cursor descrito aquí. Si estás construyendo una API GraphQL, sigue el spec de Relay en lugar de inventar un modelo de paginación personalizado.
¿Es la paginación con keyset lo mismo que la paginación con cursor?
Resuelven el mismo problema con contratos distintos para el cliente. La paginación con keyset expone el valor de orden; la paginación con cursor lo oculta detrás de un token. Desde la perspectiva del rendimiento de la base de datos, los planes de consulta típicos son los mismos, apoyándose en el índice de la clave de ordenamiento: la diferencia real está en cómo se almacena y se valida en caché el estado de paginación, en la estabilidad bajo inserciones concurrentes y en el contrato que firmas con tus clientes. Elige en función de esos factores operativos, no solo del coste de consulta.
Fuentes
- https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api
- https://github.com/uriyyo/fastapi-pagination/discussions/960
- https://medium.com/@maryam-bit/offset-vs-cursor-based-pagination-choosing-the-best-approach-2e93702a118b
- https://www.contentful.com/blog/cursor-based-pagination
- https://www.merge.dev/blog/cursor-pagination







