Blog

Data APIs: cómo gestionar la paginación cuando el volumen desborda lo esperado

6 de septiembre de 2026 · Equipo FeedScale

Data APIs: cómo gestionar la paginación cuando el volumen desborda lo esperado

El pipeline funciona. Los tests pasan. El entorno de staging no da errores. Y entonces llegas a producción y el volumen real triplica lo que estimaste. La paginación que diseñaste para diez mil registros al día tiene que manejar treinta mil. Y el sistema empieza a perder datos sin que nadie lo note.

Este escenario es más común de lo que los equipos admiten. El problema no es el volumen en sí: es que la mayoría de las implementaciones de paginación asumen un mundo estático que las APIs de datos reales nunca garantizan. Registros que aparecen entre dos llamadas consecutivas, índices que se desplazan, ventanas temporales que se solapan. El resultado es siempre el mismo: pérdida silenciosa de señales.

Entender por qué ocurre —y cómo prevenirlo— es trabajo de arquitectura, no de debugging reactivo.


El problema raíz: offset pagination en un dataset vivo

La paginación por offset (page=1, page=2, page=N) es la implementación más extendida porque es la más simple de razonar. También es la más peligrosa cuando el dataset subyacente muta entre llamadas.

El mecanismo es conocido: si entre la petición de la página 3 y la de la página 4 se insertan nuevos registros al principio del índice, todos los registros se desplazan una posición. El resultado es que la página 4 repite el último registro de la página 3, o salta el primero de la 4. La API no lanza error. El pipeline no levanta alarma. El dato simplemente no llega.

En APIs de señales de medios —donde los registros se indexan en tiempo real y el orden cronológico es el criterio dominante— este problema ocurre en cada ventana de alto volumen. No es una excepción. Es el comportamiento por defecto del sistema bajo carga.


Cursor-based pagination: el estándar que resuelve el problema

La alternativa robusta es la paginación por cursor. En lugar de pedir "la página N", pides "los registros a partir del identificador X". El servidor devuelve un cursor opaco —un token, un timestamp, un ID de registro— que señala el punto exacto desde el que continuar la siguiente llamada.

La ventaja es estructural: el cursor ancla la posición en el dataset con independencia de los registros que se inserten o eliminen después. No hay desplazamiento posible porque no hay aritmética de offsets.

Un patrón de implementación en Python:

import requests

def fetch_all(endpoint, params, token):
    cursor = None
    results = []

    while True:
        if cursor:
            params["cursor"] = cursor

        response = requests.get(
            endpoint,
            params=params,
            headers={"Authorization": f"Bearer {token}"}
        )
        response.raise_for_status()
        data = response.json()

        results.extend(data.get("items", []))

        cursor = data.get("next_cursor")
        if not cursor:
            break

    return results

Este patrón tiene tres propiedades críticas: termina cuando el servidor dice que no hay más datos, no asume nada sobre el tamaño total del dataset, y no pierde registros si el dataset crece durante la iteración.


Ventanas temporales como capa de seguridad adicional

El cursor resuelve el problema de posición. Pero hay un segundo vector de pérdida: las ventanas temporales mal definidas.

Cuando pagas por volumen —modelo habitual en APIs de datos con pricing pay-as-you-go— existe la tentación de ampliar la ventana de consulta para reducir el número de llamadas. Una sola petición para las últimas 24 horas en lugar de doce peticiones de dos horas cada una. El resultado inmediato es positivo: menos llamadas, menos overhead de red. El riesgo latente es que si esa petición falla, pierdes 24 horas de señales en un solo evento.

El patrón correcto es el opuesto: ventanas cortas, solapadas mínimamente, con idempotencia en la ingesta. Una ventana de 15 minutos con un solapamiento de 2 minutos hacia atrás cuesta más llamadas, pero garantiza que cualquier fallo recuperable no destruye más de 15 minutos de datos. Y el solapamiento permite detectar duplicados en lugar de huecos.

from datetime import datetime, timedelta

def generate_windows(start, end, window_minutes=15, overlap_minutes=2):
    windows = []
    current = start
    while current < end:
        window_end = min(current + timedelta(minutes=window_minutes), end)
        windows.append((current - timedelta(minutes=overlap_minutes), window_end))
        current = window_end
    return windows

El solapamiento genera duplicados. Eso es manejable. El hueco no lo es.


Idempotencia en la ingesta: el tercer pilar

Cursor + ventanas cortas resuelven la pérdida en el lado del productor. La idempotencia resuelve el problema en el lado del consumidor.

Cada registro que entra al pipeline debe poder insertarse dos veces sin crear duplicados. Esto implica que el modelo de datos tiene un identificador único por registro —normalmente el ID que devuelve la API— y que la capa de persistencia implementa un INSERT OR IGNORE o un ON CONFLICT DO NOTHING equivalente.

Sin idempotencia, el solapamiento de ventanas produce duplicados en el dataset final. Con idempotencia, el solapamiento produce seguridad sin coste en calidad.

El triángulo cursor + ventanas cortas + idempotencia es el mínimo viable para cualquier pipeline que consuma APIs de datos en producción con volumen no trivial.


Lo que el proveedor no va a decirte

La documentación de una API de datos suele describir los parámetros de paginación. No suele describir el comportamiento del índice bajo carga, el TTL de los cursores, ni qué ocurre si una ventana temporal cae en un momento de reindexación del dataset.

Antes de llevar un pipeline a producción, hay tres preguntas que el equipo debe haber respondido:

  1. ¿Los cursores caducan? Si el cursor tiene un TTL de 5 minutos y tu pipeline tarda 8 en procesarlo, el siguiente next_cursor falla en silencio o lanza un error que hay que manejar explícitamente.
  2. ¿El dataset puede reordenarse entre llamadas? Algunos proveedores reindexan periódicamente. Si ocurre durante una iteración de paginación, la posición del cursor puede quedar inválida.
  3. ¿Qué devuelve la API cuando no hay más registros? Un array vacío, un cursor nulo, un campo has_more: false. El contrato debe estar documentado. Si no lo está, hay que probarlo empíricamente antes de que el pipeline asuma un comportamiento.

En APIs como las que expone FeedScale, el contrato de paginación está explícito en la documentación técnica. Aun así, validarlo en staging con volumen sintético antes de producción sigue siendo el paso que más equipos omiten y más equipos acaban lamentando.


La regla práctica que resume todo

Un pipeline que pagina correctamente no es el que no falla. Es el que, cuando falla, sabe exactamente qué datos puede haber perdido y tiene los mecanismos para recuperarlos sin reingesta completa.

Cursor en lugar de offset. Ventanas cortas con solapamiento. Idempotencia en la capa de persistencia. Esos tres elementos no son optimizaciones: son la base sobre la que cualquier integración con una data API puede sobrevivir al volumen real.

El volumen real siempre es mayor de lo que dice la estimación inicial. Eso no es un problema de planificación. Es una constante del sector.


← Volver al blog