Exportar 500 contatos do CRM é instantâneo. Exportar 500 mil trava, dá timeout ou volta com erro genérico. A causa quase nunca é “muito dado” — é o tipo de paginação que a API usa para entregar resultados em pedaços. Existem duas famílias, e uma delas piora conforme a base cresce; a outra não.
Pense numa lista telefônica impressa. Se alguém pedir “me dê a página 400”, você folheia da capa até lá, contando página por página — quanto mais fundo no livro, mais tempo leva. Agora pense num marcador de página física: você abre exatamente onde parou da última vez, sem folhear nada antes. A primeira é paginação por offset; a segunda é paginação por cursor. A diferença de desempenho entre as duas é exatamente essa.
Como cada abordagem funciona
Toda API que devolve listas grandes — contatos, pedidos, eventos, produtos — precisa dividir o resultado em páginas. Como ela decide qual “pedaço” entregar a cada chamada é o que separa as duas famílias:
| Abordagem | Como é pedida | Como o servidor processa | Comportamento com a base crescendo |
|---|---|---|---|
| Offset / page | ?page=400&size=100 | Conta e descarta as 39.900 linhas anteriores antes de devolver as 100 pedidas | Degrada — cada página fica mais lenta que a anterior |
| Cursor / keyset | ?cursor=xk29fA... | Salta direto para o ponto marcado pelo cursor, sem varrer o que veio antes | Estável — a página 4.000 custa o mesmo que a página 4 |
No offset, o banco de dados literalmente varre e descarta todas as linhas anteriores à página pedida, toda vez. Pedir a página 1 é barato. Pedir a página 5.000 exige que o banco percorra as 500 mil linhas anteriores antes de entregar as 100 que você realmente quer — e descartar 500 mil linhas não é grátis, mesmo que elas nunca cheguem até você.
No cursor, cada resposta vem com um “marcador” que aponta exatamente onde a próxima busca deve começar — geralmente baseado num identificador único e ordenado, como um ID crescente ou timestamp. A próxima chamada usa esse marcador para pular direto ao ponto certo, sem processar nada anterior. Não importa se você está na página 4 ou na página 40 mil: o custo de buscar a próxima leva de resultados é sempre o mesmo.
O sintoma reconhecível
Você não precisa entender a implementação para suspeitar do diagnóstico. O padrão é sempre o mesmo:
- Exportar uma base pequena ou testar com poucos registros funciona perfeitamente rápido.
- Ao crescer — mais contatos, mais pedidos, mais eventos — a mesma operação começa a demorar cada vez mais.
- Em algum ponto, a chamada simplesmente não completa: timeout do lado do cliente, erro 504 do servidor, ou a ferramenta trava sem mensagem clara.
- Rodar de novo não ajuda — o problema não é instabilidade pontual, é o tamanho da base combinado com o tipo de paginação.
Se esse padrão soa familiar — “funcionava bem quando a base era menor” — a causa provável é paginação por offset numa API que não oferece alternativa.
Por que isso importa além da exportação manual
O caso mais visível é a exportação pontual de relatório, mas o mesmo mecanismo afeta qualquer processo automatizado que precise varrer uma base inteira:
- Sincronização entre sistemas. Um job noturno que copia todos os contatos do CRM para o data warehouse via API fica mais lento a cada mês que a base cresce, até estourar a janela de tempo disponível.
- Pipelines de ETL. Se a ingestão de dados depende de paginar uma API de origem, o tempo de processamento cresce de forma não linear conforme a base de origem cresce — um problema silencioso que só aparece meses depois de o pipeline entrar em produção.
- Auditorias e migrações. Trocar de CRM ou consolidar bases geralmente exige extrair tudo via API. Se a origem só oferece offset, a migração de uma base grande pode ser tecnicamente inviável dentro do prazo, e ninguém percebe isso até tentar.
O que fazer quando a API só oferece offset
Nem toda API vai adotar cursor amanhã, e trocar de fornecedor por causa disso raramente compensa. As saídas práticas, em ordem de preferência:
- Verificar se existe endpoint alternativo. Muitos fornecedores mantêm o endpoint padrão em offset por compatibilidade, mas expõem uma versão mais nova com cursor para volumes grandes — geralmente documentada separadamente ou disponível sob pedido.
- Exportar por faixa de data ou de ID. Em vez de pedir “página 5.000”, peça “todos os registros criados entre 1º e 15 de março”. Isso divide a base em fatias menores e evita que qualquer fatia individual fique grande o suficiente para travar — não elimina o problema de fundo, mas contorna na prática.
- Reduzir o tamanho da página. Pedir páginas de 20 em vez de 100 registros não resolve a degradação, mas adia o ponto em que ela se torna crítica — é paliativo, não solução.
- Pedir ao fornecedor. Se a exportação é uma necessidade recorrente e crítica, vale reportar o problema formalmente. Fornecedores de porte costumam ter roadmap para migrar de offset para cursor exatamente por essa queixa ser comum.
Um caso intermediário: paginação por timestamp
Existe uma terceira variante, menos discutida, que vale conhecer porque aparece com frequência em APIs de martech mais simples: paginação baseada em data de criação ou modificação, sem um cursor opaco formal. A chamada pede algo como “todos os registros criados depois de 2026-03-01T14:00:00”, e o servidor devolve o próximo lote a partir dali. Tecnicamente, isso se comporta como cursor — o servidor não precisa varrer e descartar nada anterior, desde que o campo de data tenha um índice adequado no banco — mas é mais simples de implementar do que um cursor opaco formal, porque usa um valor legível e previsível em vez de um token gerado especificamente para esse fim.
A armadilha dessa variante aparece quando múltiplos registros compartilham exatamente o mesmo timestamp — situação comum em importações em lote ou em bancos com baixa precisão de data. Nesse caso, a paginação por timestamp puro pode pular ou duplicar registros na fronteira entre duas páginas. APIs bem desenhadas resolvem isso combinando o timestamp com um segundo critério de desempate, geralmente o ID do registro, para garantir uma ordenação total e sem ambiguidade.
Reconhecendo cursor na documentação
Ao avaliar uma API antes de contratar uma ferramenta, alguns sinais na documentação indicam qual abordagem ela usa:
- Parâmetros como
page,offsetouskipcombinados comlimitousize— sinal de offset. - Parâmetros como
cursor,after,next_tokenou um campohas_morecom um token opaco na resposta — sinal de cursor. - APIs GraphQL modernas costumam seguir a especificação de conexões cursor-based formalizada pelo Facebook em 2015, reconhecível pelos campos
edges,nodeepageInfo— um padrão de mercado consolidado especificamente para resolver esse problema de escala.
Perguntar diretamente também funciona: “Ao paginar resultados grandes, a página 1.000 tem o mesmo tempo de resposta da página 1, ou fica mais lenta?” é uma pergunta que qualquer engenheiro de fornecedor consegue responder na hora, e a resposta revela imediatamente qual abordagem está por trás.
Por que o fornecedor não migra simplesmente
Vale entender que cursor não é estritamente melhor em todo cenário — é uma troca. Paginação por offset permite pular direto para qualquer página arbitrária (“me dê a página 50”), o que é útil em interfaces com numeração de página visível ao usuário. Cursor só permite avançar sequencialmente a partir de onde você parou — não dá para pular direto para “o meio” sem ter processado o que veio antes.
Isso explica por que interfaces com paginação numerada visível (1, 2, 3… 50) geralmente usam offset internamente, enquanto feeds de rolagem contínua (redes sociais, timelines) usam cursor. Para exportação de dados em massa — o caso que mais dói em martech — cursor quase sempre é a escolha certa, porque ninguém precisa “pular para a página 4.000” de uma exportação, só percorrer tudo uma vez.
Paginação e a decisão de arquitetura por trás dela
Vale situar essa escolha dentro de um quadro mais amplo: paginação por offset ou cursor é uma manifestação prática do mesmo raciocínio por trás de qualquer trade-off de arquitetura — simplicidade de implementação de um lado, desempenho em escala do outro. Cursor é mais trabalhoso de implementar corretamente (exige um campo ordenado e estável para servir de marcador, geralmente um ID crescente ou timestamp com precisão suficiente para não gerar ambiguidade entre registros criados quase ao mesmo tempo), enquanto offset é trivial de implementar sobre qualquer tabela, sem exigir esse cuidado extra de design.
Isso explica por que tantas APIs, especialmente as mais antigas ou as construídas rapidamente no início de um produto, escolhem offset por padrão — é o caminho de menor esforço de implementação — e só migram para cursor depois que o volume de dado real expõe a limitação. Para quem está especificando os requisitos de uma API nova, interna ou de um fornecedor, pedir cursor desde o início evita essa migração dolorosa mais tarde.
O efeito colateral menos óbvio: dado duplicado ou perdido durante a exportação
Existe uma armadilha adicional em paginação por offset que vai além da lentidão: se registros forem inseridos ou removidos durante o processo de exportação — algo comum quando a exportação de uma base grande leva minutos ou horas —, a numeração das páginas por offset pode “deslizar”. Um registro pode aparecer duas vezes em páginas diferentes, ou ser pulado inteiramente, porque a posição relativa de cada linha mudou entre uma chamada de página e a seguinte.
Paginação por cursor não sofre desse problema da mesma forma, porque o marcador aponta para uma posição fixa e específica (geralmente um ID), não para uma posição relativa que pode mudar conforme a base é alterada por outras operações em paralelo. Isso é especialmente relevante para exportações que rodam em produção, com o sistema recebendo escrita normal simultaneamente — cenário em que offset não garante apenas lentidão, mas também um resultado potencialmente incorreto.
Próximos passos para diagnosticar sua exportação
A ação concreta: da próxima vez que uma exportação ou sincronização travar em volume alto, olhe a URL ou os parâmetros da chamada de API por trás dela. Se você ver page= ou offset= combinado com uma base grande, agora você sabe o motivo exato — não é “muito dado”, é o tipo de paginação encontrando seu limite estrutural.
Se a exportação é recorrente e crítica para a operação, vale mapear hoje quais das suas integrações usam offset em bases que só crescem, antes que o timeout apareça durante uma migração ou auditoria com prazo apertado.