Resumo
Para grandes conjuntos de dados, use paginação baseada em cursor ou em chaves (keyset) em vez de paginação baseada em offset. A paginação por offset (?page=1&limit=20) tem desempenho ruim com milhões de registros e permite inconsistência de dados. A Modern PetstoreAPI implementa paginação baseada em cursor com tokens opacos e links HATEOAS para resultados eficientes e consistentes.
Introdução
Sua API retorna uma lista de pets. Você tem 10 milhões de pets no banco de dados. Um cliente solicita GET /pets?page=500000&limit=20. Seu banco de dados executa OFFSET 10000000 LIMIT 20. A consulta leva 30 segundos. Sua API expira.
Este é o problema da paginação por offset. Funciona bem para conjuntos de dados pequenos, mas falha em escala. O banco de dados precisa escanear milhões de linhas para atingir o offset, mesmo que você retorne apenas 20 resultados.
O antigo Swagger Petstore não aborda paginação. A Modern PetstoreAPI implementa paginação baseada em cursor que escala para milhões de registros com desempenho consistente.
Neste guia, você aprenderá por que a paginação por offset falha, como funciona a paginação baseada em cursor e como a Modern PetstoreAPI implementa paginação eficiente.
Por que a Paginação por Offset Falha em Escala
A paginação por offset é a abordagem mais comum, mas tem sérios problemas.
Como a Paginação por Offset Funciona
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
O banco de dados pula as linhas do offset e retorna as linhas do limit.
Problema 1: O Desempenho Degrada com o Número da Página
Página 1:
SELECT * FROM pets OFFSET 0 LIMIT 20;
-- Rápido: escaneia 20 linhas
Página 1000:
SELECT * FROM pets OFFSET 20000 LIMIT 20;
-- Lento: escaneia 20.020 linhas, retorna 20
Página 500.000:
SELECT * FROM pets OFFSET 10000000 LIMIT 20;
-- Muito lento: escaneia 10.000.020 linhas, retorna 20
O banco de dados deve escanear todas as linhas até o offset, mesmo que as descarte. O desempenho degrada linearmente com o número da página.
Problema 2: Resultados Inconsistentes
Enquanto um cliente navega pelos resultados, os dados mudam:
Requisição 1:
GET /pets?page=1&limit=2
Retorna: [Pet A, Pet B]
Alguém adiciona o Pet Z (ordena primeiro alfabeticamente)
Requisição 2:
GET /pets?page=2&limit=2
Retorna: [Pet B, Pet C] ← Pet B aparece duas vezes!
O Pet B apareceu em ambas as páginas porque um novo pet foi inserido. Por outro lado, pets podem ser pulados se ocorrerem exclusões.
Problema 3: Paginação Profunda é Cara
Os usuários raramente vão além da página 10. Mas se sua API permitir ?page=1000000, você deve lidar com isso. Consultas de paginação profunda são caras e podem ser usadas para ataques de negação de serviço.
Quando a Paginação por Offset é Aceitável
A paginação por offset funciona bem para:
- Pequenos conjuntos de dados (< 10.000 registros)
- APIs internas com uso controlado
- Interfaces de administração onde os usuários não farão paginação profunda
- Dados que mudam com pouca frequência
Para APIs públicas ou grandes conjuntos de dados, use paginação baseada em cursor.
Paginação Baseada em Cursor Explicada
A paginação baseada em cursor usa um token opaco para marcar a posição no conjunto de resultados.
Como Funciona
Requisição 1:
GET /pets?limit=20
Resposta 1:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9",
"hasMore": true
}
}
Requisição 2:
GET /pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20
O cursor é um token opaco (geralmente codificado em base64) que codifica a posição. O cliente não o analisa — apenas o repassa.
Benefícios
1. Desempenho Consistente
O banco de dados usa um índice para encontrar a posição do cursor diretamente:
SELECT * FROM pets
WHERE id > '019b4132-70aa-764f-b315-e2803d882a24'
ORDER BY id
LIMIT 20;
Esta consulta é rápida independentemente da posição no conjunto de dados. Ela usa uma busca de índice, não um scan.
2. Resultados Consistentes
Os cursores são estáveis. Se os dados mudarem entre as requisições, você ainda obterá resultados consistentes. Novos registros não causam duplicatas ou pulos.
3. Sem Ataques de Paginação Profunda
Os clientes não podem pular para posições arbitrárias. Eles devem paginar sequencialmente, o que limita o abuso.
Formato do Cursor
Os cursores são tipicamente JSON codificados em base64:
// Cursor decodificado
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"createdAt": "2026-03-13T10:30:00Z"
}
O cursor contém informações suficientes para retomar a paginação. Para a Modern PetstoreAPI, isso inclui o ID do recurso e o campo de ordenação.
Paginação por Chaves (Keyset) para Dados Ordenados
A paginação por chaves (keyset) é uma variante da paginação baseada em cursor para dados ordenados.
Como Funciona
Em vez de um cursor opaco, você usa o último valor da página anterior:
Requisição 1:
GET /pets?limit=20&sortBy=createdAt
Resposta 1:
{
"data": [
{"id": "...", "createdAt": "2026-03-13T10:00:00Z"},
...
{"id": "...", "createdAt": "2026-03-13T10:30:00Z"}
]
}
Requisição 2:
GET /pets?limit=20&sortBy=createdAt&after=2026-03-13T10:30:00Z
O parâmetro after usa o último valor de createdAt da página anterior.
Consulta SQL
SELECT * FROM pets
WHERE created_at > '2026-03-13T10:30:00Z'
ORDER BY created_at
LIMIT 20;
Isso é eficiente porque usa um índice em created_at.
Quando Usar Paginação por Chaves (Keyset)
- Os dados são naturalmente ordenados (por timestamp, ID, etc.)
- Os clientes precisam entender a chave de paginação
- Você quer paginação transparente (não cursores opacos)
A Modern PetstoreAPI usa paginação baseada em cursor por padrão, mas suporta paginação por chaves para dados de séries temporais.
Como a Modern PetstoreAPI Implementa a Paginação
A Modern PetstoreAPI usa paginação baseada em cursor com links HATEOAS.
Formato da Requisição
GET /pets?limit=20
GET /pets?cursor={token}&limit=20
Parâmetros:
limit- Número de resultados por página (padrão: 20, máximo: 100)cursor- Token de paginação opaco da resposta anterior
Formato da Resposta
{
"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"
}
}
Principais Recursos
1. Cursores Opacos
Os cursores são codificados em base64. Os clientes não os analisam.
2. Links HATEOAS
O objeto links fornece URLs prontas para uso. Os clientes não precisam construir URLs de paginação.
3. Flag hasMore
Indica se existem mais resultados. Os clientes sabem quando parar de paginar.
4. Validação de Limite
O limite máximo é 100. Impede que os clientes solicitem páginas enormes.
Consulte a documentação de paginação da Modern PetstoreAPI para detalhes completos.
Formato de Resposta da Paginação
A Modern PetstoreAPI encapsula respostas paginadas em uma estrutura consistente.
Wrapper de Coleção
{
"data": [...],
"pagination": {...},
"links": {...}
}
Por que encapsular coleções?
- Extensibilidade - Pode adicionar metadados sem quebrar clientes
- Consistência - Todos os endpoints paginados usam o mesmo formato
- HATEOAS - Links guiam os clientes pela paginação
Metadados de Paginação
"pagination": {
"limit": 20,
"hasMore": true,
"nextCursor": "...",
"totalCount": 1000 // Opcional, caro para calcular
}
totalCount é opcional porque calculá-lo é caro para grandes conjuntos de dados. A maioria dos clientes não precisa dele.
Testando Paginação com Apidog
O Apidog ajuda a testar o comportamento da paginação de forma abrangente.
Cenários de Teste
1. Primeira Página
GET /pets?limit=20
Esperado: 20 resultados, hasMore=true, nextCursor presente
2. Páginas Subsequentes
GET /pets?cursor={token}&limit=20
Esperado: 20 resultados, hasMore=true/false, nextCursor presente/ausente
3. Última Página
GET /pets?cursor={lastToken}&limit=20
Esperado: < 20 resultados, hasMore=false, sem nextCursor
4. Resultados Vazios
GET /pets?status=NONEXISTENT&limit=20
Esperado: 0 resultados, hasMore=false, sem nextCursor
5. Validação de Limite
GET /pets?limit=1000
Esperado: 400 Bad Request (excede o limite máximo)
Configuração de Teste do Apidog
// Teste: Estrutura da paginação
pm.test("Response has pagination", () => {
pm.expect(pm.response.json()).to.have.property('pagination');
pm.expect(pm.response.json().pagination).to.have.property('hasMore');
});
// Teste: Links 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');
}
});
Escolhendo a Estratégia de Paginação Correta
Diferentes estratégias se adequam a diferentes casos de uso.
Paginação por Offset
Use quando:
- O conjunto de dados é pequeno (< 10.000 registros)
- Usuários precisam de acesso aleatório (pular para a página 50)
- Os dados mudam com pouca frequência
- API interna com uso controlado
Não use quando:
- O conjunto de dados é grande (> 100.000 registros)
- O desempenho é importante
- Os dados mudam com frequência
Paginação Baseada em Cursor
Use quando:
- O conjunto de dados é grande
- O desempenho é importante
- Os dados mudam com frequência
- Acesso sequencial é suficiente
Não use quando:
- Usuários precisam de acesso aleatório
- A complexidade do cursor é uma preocupação
Paginação por Chaves (Keyset)
Use quando:
- Os dados são naturalmente ordenados
- Paginação transparente é preferida
- O desempenho é importante
Não use quando:
- A ordem de ordenação é complexa
- Múltiplos campos de ordenação são necessários
Recomendação da Modern PetstoreAPI: Use paginação baseada em cursor para APIs públicas e grandes conjuntos de dados.
Conclusão
A paginação é crítica para APIs que retornam grandes conjuntos de dados. A paginação por offset é simples, mas não escala. A paginação baseada em cursor oferece desempenho consistente e resultados confiáveis para milhões de registros.
A Modern PetstoreAPI implementa paginação baseada em cursor com tokens opacos, links HATEOAS e metadados apropriados. Este design escala eficientemente e proporciona uma ótima experiência ao desenvolvedor.
Teste sua implementação de paginação com o Apidog para garantir que ela lide com casos extremos, valide limites e retorne resultados consistentes.
Principais pontos:
- Evite paginação por offset para grandes conjuntos de dados
- Use paginação baseada em cursor para escalabilidade
- Encapsule coleções com metadados e links
- Teste a paginação completamente com o Apidog
- Siga os padrões de paginação da Modern PetstoreAPI
Perguntas Frequentes
Por que não apenas retornar todos os resultados sem paginação?
Retornar milhões de registros em uma única resposta causa problemas de memória, transferência de rede lenta e uma experiência de usuário ruim. A paginação é essencial para grandes conjuntos de dados.
Os clientes podem pular para uma página específica com paginação por cursor?
Não, a paginação por cursor exige acesso sequencial. Se o acesso aleatório for necessário, considere a paginação por offset para pequenos conjuntos de dados ou implemente busca/filtragem em vez disso.
Como eu lido com a paginação com filtragem?
Inclua parâmetros de filtro nas requisições de paginação: GET /pets?status=AVAILABLE&cursor={token}&limit=20. O cursor codifica tanto a posição quanto o estado do filtro.
Devo incluir a contagem total (total count) nas respostas de paginação?
Apenas se os clientes precisarem e seu conjunto de dados for pequeno. Calcular a contagem total é caro para grandes conjuntos de dados (requer uma consulta COUNT separada).
Como eu implemento a paginação por cursor em SQL?
Use uma cláusula WHERE com o valor do cursor: SELECT * FROM pets WHERE id > ? ORDER BY id LIMIT 20. Certifique-se de ter um índice na coluna de ordenação.
E se meus tokens de cursor se tornarem inválidos?
Retorne 400 Bad Request com uma mensagem de erro. Os cursores podem se tornar inválidos se os dados forem excluídos ou se o estado da paginação expirar.
Por quanto tempo os cursores devem permanecer válidos?
Os cursores da Modern PetstoreAPI são válidos indefinidamente, desde que o recurso referenciado exista. Algumas APIs expiram os cursores após 24 horas.
Posso usar paginação por cursor com múltiplos campos de ordenação?
Sim, mas o cursor deve codificar todos os campos de ordenação. Isso torna os cursores mais complexos. Considere usar uma única chave de ordenação composta em vez disso.
