GH GambleHub

Paginação e cursores

1) Por que precisa de paginação

A paginação limita a quantidade de dados transmitidos e rendidos pelo cliente, reduz a pressão sobre o armazenamento/rede e define uma forma determinada de «caminhar» na coleção. Em sistemas reais, a paginação não é apenas 'page = 1 & limit = 50', mas sim um conjunto de contratos protocolares e invariantes de coerência.

Alvos típicos:
  • Controle de latência e memória para consulta.
  • Navegação estável quando você altera o conjunto de dados (inserir/remover).
  • Possibilidade de reabrir do local.
  • Acoplamento e pré-teste (prefetch).
  • Proteção contra abuso (rate limiting, backpressure).

2) Modelos de paginação

2. 1 OFFSET/LIMIT (página)

A ideia é: «Deixa escapar as linhas N, devolve a M».
Vantagens: simplicidade, compatível com quase qualquer SQL/NoSQL.

Contras:
  • Degradação linear: grandes OFFSET resultam em digitalização completa/skip-cost.
  • Instabilidade nas inserções ou remoções entre as solicitações (os deslocamentos «flutuam»).
  • É difícil garantir a renovabilidade.
SQL-exemplo:
sql
SELECT
FROM orders
ORDER BY created_at DESC, id DESC
OFFSET 1000 LIMIT 50;

2. 2 Cursor/Keyset/Seek-paginação

A ideia é «continua com a chave K». O cursor é uma posição em um conjunto ordenado.

Benefícios:
  • O (1) acesso à continuação quando o índice estiver disponível.
  • Estabilidade nas alterações na coleção.
  • Melhor latência em páginas profundas.
Contras:
  • Precisamos de chaves de triagem rigorosamente definidas, únicas e monótonas.
  • Mais difícil de realizar e depurar.
SQL-exemplo (seek):
sql
-- Resumption after steam (created_at, id) = (:last_ts,:last_id)
SELECT
FROM orders
WHERE (created_at, id) < (:last_ts,:last_id)
ORDER BY created_at DESC, id DESC
LIMIT 50;

2. 3 Continuação tocens (tocantes opacos)

Ideia: o servidor devolve opaque-token que codifica «posição» (e possivelmente o estado dos chardes/filtros). O cliente não compreende as entranhas e apenas devolve o token para a próxima página.
Vantagens: flexibilidade, capacidade de alterar o padrão sem quebrar a API.
Contras: gerenciamento da vida dos tokens, compatibilidade com os depósitos.

2. 4 Cursores temporários e lógicos

Time-based: «Todas as entradas até T», o cursor é uma marca de tempo (adequada para os fluxos de append-only).
Jobs-sequence/offset-based: cursor - deslocamento no logue (Kafka offset, journal seq).
Global monotonic IDs: Snowflake/UUIDv7 como chave para seek estável.

3) Engenharia de cursos e tokens

3. 1 Propriedades de bom cursor

Opacidade (opaque): o cliente não depende do formato.
Autoria/integridade: assinatura HMAC para impedir a troca/manipulação.
Contexto: inclui triagem, filtros, versão de circuito, tenant/shard.
Tempo de vida: TTL e «não-usabilidade» (não-replay) quando você muda de índice/permissão.
Tamanho: compacto (<= 1-2 KB) adequado ao URL.

3. 2 Formato de token

A pilha recomendada é JSON → compressão (zstd/deflate) → Base64URL → HMAC.

Estrutura de carga útil (exemplo):
json
{
"v": 3 ,//token version
"sort": ["created_at:desc","id:desc"],
"pos": {"created_at":"2025-10-30T12:34:56. 789Z","id":"987654321"},
"filters": {"user_id":"42","status":["paid","shipped"]},
"tenant": "eu-west-1",
"shards": [{"s ": "a, "" hi":"..."}] ,//optional: cross-shard context
"issued_at": "2025-10-31T14:00:00Z",
"ttl_sec": 3600
}

Por cima, é adicionado 'mac = HMAC (segredo, payload)' e tudo é codificado em um tocador de linha.

3. 3 Segurança

Assinar (HMAC/SHA-256).
Criptografar opcionalmente (AES-GCM) com valores sensíveis (PII).
Validação no servidor: versão, TTL, permissões do usuário (RBAC/ABAC).

4) Coerência e invariantes

4. 1 Ordenação estável

Use o determinismo completo: 'ORDER BY ts DESC, id DESC'.
A chave de triagem deve ser única (adicione 'id' como tiebreaker).
O índice deve corresponder à triagem (covering index).

4. 2 Instantâneos (snapshot) e isolamento

Use o snapshot read-consistent (MVCC/txid) para as páginas «indefectíveis».
Se não for apropriado (caro/grande número de dados), formule o contrato: «O cursor devolve itens estritamente posição anterior». É natural para as notícias.

4. 3 Inserções/remoções entre páginas

O modelo seek minimiza «duplicados/omissões».
Documente o comportamento de remoção/alteração: os raros «buracos» entre as páginas são permitidos, mas não «para trás no tempo».

5) Indexação e esquemas de identificadores

Os índices compostos estão estritamente na ordem de triagem «(created _ at DESC, id DESC)».
ID monótono: Snowflake/UUIDv7 dá ordem de tempo → acelera seek.
Chaves quentes: Distribua em shard-key (por exemplo, «tenant _ id», «region») e arrume dentro do shard.
Geradores de ID: evite conflitos e «relógios do futuro» - sincronização do tempo, «regressão» em saltos NTP.

6) Paginação cross-chard

6. 1 Esquemas

Scatter-Gather: solicitações paralelas para todos os cursos de seek local, então k-way merge no agregador.
Per-Shard Cursors: O token contém posições para cada chard.
Bounded fan-out: limite o número de chardes por etapa (rate limiting/timeout budet).

6. 2 Tokens para multi-shard

Guarde a matriz '

7) Contratos de protocolo

7. 1 REST

Consulta:

GET /v1/orders? limit=50&cursor=eyJ2IjoiMyIsInNvcnQiOiJjcmVh... (opaque)
Resposta:
json
{
"items": [ /... / ],
"page": {
"limit": 50,
"next_cursor": "eyJ2IjozLCJwb3MiOiJjcmVh...==",
"has_more": true
}
}
Recomendações:
  • 'limit' com limite superior (por exemplo, max = 200).
  • 'next _ cursor' está ausente se 'has _ more = falso'.
  • Idempotidade do GET, armazenabilidade das respostas sem 'next _ cursor' (primeira página com filtros fixos e snapshot).

7. 2 GraphQL (Abordagem Relay)

Contrato típico de 'connation':
graphql type Query {
orders(first: Int, after: String, filter: OrderFilter): OrderConnection!
}

type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
}

type OrderEdge {
node: Order!
cursor: String! // opaque
}

type PageInfo {
hasNextPage: Boolean!
endCursor: String
}

'cursor' deve ser opaco e assinado; não use «Base64 cru (id)» sem HMAC.

7. 3 gRPC

Use 'page _ size' e 'page _ token':
proto message ListOrdersRequest {
string filter = 1;
int32 page_size = 2;
string page_token = 3; // opaque
}

message ListOrdersResponse {
repeated Order items = 1;
string next_page_token = 2; // opaque bool has_more = 3;
}

7. 4 Fluxos e WebSockets

Para as fitas contínuas, o cursor é o «último offset/ts visto».

Suporte 'resume _ from' no recôncavo:
json
{ "action":"subscribe", "topic":"orders", "resume_from":"2025-10-31T12:00:00Z#987654321" }

8) Caçamento, pré-processamento, CDN

ETag/If-None-Match para a primeira página com filtros estáveis.
Cachê-controle com TTL curto (por exemplo, 5-30 c) para listas públicas.
Prefetch: devolva 'next _ cursor' e dicas ('Link: rel =' next') e o cliente pode antecipar a seguinte página.
Variações: leve em conta 'filter/port/local/tenant' como a chave da parte do caju.

9) Controle de carga e limitação

Limite superior, por exemplo, 200.
Server-side backpressure: Se o tempo de consulta> budget, reduza 'limit' na resposta (e comunique claramente ao cliente o tamanho real da página).
Rate limits por usuário/token/tenant.
Timeout/Retry: pausa exponencial, solicitações idepotentes.

10) Aspectos UX

Scroll contra numeração: rolagem sem fim → cursores; páginas de matrícula → offset (mas explique a imprecisão na atualização de dados).
Botão voltar ao local: guarde a pilha de cursores do cliente.
Páginas em branco: Se 'has _ more = falso', não mostre o botão 'Mais'.
Limites estáveis: mostre o exato 'total' apenas se for barato (senão, o aproximado 'approx _ total').

11) Testes e malas edge

Folhas de cheque:
  • Ordem estável: itens com o mesmo 'ts' não 'piscam'.
  • Inserções/remoções: não aparecem repetições na junção de páginas.
  • Alterar filtros entre páginas: O token deve ser desviado como «obsoleto/incompatível».
  • TTL token: erro correto após o prazo.
  • Grande profundidade, a latência não cresce linearmente.
  • Ordem merge correta, falta de starvation «lentos».
Exemplo de teste property-based (pseudocode):
python
Generate N entries with random inserts between calls
Verify that all pages are merged = = whole ordered fetch

12) Observabilidade e SLO

Métricas:
  • 'lista _ request _ latency _ ms' (P50/P95/P99) por extensão de página.
  • 'seek _ index _ hit _ ratio' (proporção de solicitações do índice de cobertura).
  • 'next _ cursor _ invalid _ rate' (erros de validação/TTL/assinaturas).
  • 'merge _ fanout' (com as barras de página envolvidas).
  • 'duplicates _ on _ boundary' e 'gaps _ on _ boundary' (detecção na telemetria do cliente).
Logi/Training:
  • Correlacione 'cursor _ id' nos logs, disfarce o payload.
  • Coloque spans: 'page _ size', 'fonte _ shards', 'db _ index _ used'.
SLO-exemplo:
  • Disponibilidade: 99. 9% em 'Folha' técnicas.
  • Latência: P95 <200 ms para 'page _ size <= 50' em um selo local.
  • Erro no token: <0. 1% do total de chamadas.

13) Migração e compatibilidade

Inclua 'v' no token e suporte as versões antigas N semanas.
Ao alterar as chaves de triagem, envie um erro «macio» de '409 Conflict' com uma dica para fazer uma listagem recente sem o cursor.
Caso catastrófico (todos os tokens): mude 'signing _ key _ id' e rejeite os antigos.

14) Exemplos de implementação

14. 1 Geração de token (pseudocode)

python payload = json. dumps({...}). encode()
compressed = zlib. compress(payload)
mac = hmac_sha256(signing_key, compressed)
token = base64url_encode(mac + compressed)

14. 2 Validação do token

python raw = base64url_decode(token)
mac, compressed = raw[:32], raw[32:]
assert mac == hmac_sha256(signing_key, compressed)
payload = json. loads(zlib. decompress(compressed))
assert now() - payload["issued_at"] < payload["ttl_sec"]
assert payload["filters"] == req. filters

14. 3 Consulta Seek com chave composta

sql
-- Page # 1
SELECT FROM feed
WHERE tenant_id =:t
ORDER BY ts DESC, id DESC
LIMIT:limit;

-- Next pages, continued after (ts0, id0)
SELECT FROM feed
WHERE tenant_id =:t
AND (ts <:ts0 OR (ts =:ts0 AND id <:id0))
ORDER BY ts DESC, id DESC
LIMIT:limit;

15) Segurança e conformidade

Não inclua campos crus no tocador, dos quais você pode tirar o PII.
Assine e limite o TTL.
Tente tornar os tokens inaceitáveis entre os usuários (encaixe 'sub/tenant/roles' no payload e verifique na validação).
Só enxergue as hashtags de tokens.

16) Erros frequentes e anti-pattern

Base64 (id) como um cursor: fácil de falsificar/selecionar, quebra o contrato ao mudar de ordem.
Sem tie-breaker: 'ORDER BY ts DESC' sem 'id' → duplicados/saltados.
Altere os filtros entre páginas sem deficiência do token.
OFFSET profundo: lento e imprevisível.
Tokens sem versão ou TTL.

17) Miniclist de implementação

1. Defina a triagem e adicione um tie-breaker único.
2. Crie um índice de cobertura para esta ordem.
3. Selecione seek + token opaco.
4. Execute a assinatura (e criptografia, se necessário) do token.
5. Coloque a TTL e a versionização.
6. Configure e documente os contratos 'has _ more', 'next _ cursor'.
7. Pense no padrão de cross-shard (se necessário) e k-way merge.
8. Adicione métricas, alertas e SLO.
9. Cubra com testes de borda de página property-based.
10. Descreva a estratégia migratória dos tokens.

18) Resumidas recomendações para a escolha da abordagem

Diretórios/buscas onde o «número de página» e total aproximado são importantes: permitamos 'OFFSET/LIMIT' + dinheiro; informe que o total é aproximado.
Fitas, analista, listas profundas, RPS alto: apenas cursor/seek.
Coleções chardadas/distribuídas: per-shard cursors + merge token.
Threads/CDC: cursores como offsets/ts com retomada.

19) Exemplo de acordo API (resumo)

`GET /v1/items? limit=50&cursor=...`

A resposta sempre inclui 'page. limit`, `page. ha _ more ', opcionalmente' page. next_cursor`.
O cursor é opaco, assinado, com TTL.
ORDEM BY created _ at DESC, id DESC.
Comportamento das alterações de conjunto: os itens não «voltam» em relação ao cursor.
As métricas e erros são padrão: 'invalid _ cursor', 'expired _ cursor', 'mismatch _ filters'.

Este artigo fornece princípios arquitetônicos e pattern prontos para projetar uma paginação que permanece rápida, previsível e segura, mesmo em um ambiente de grandes dados, charding e conjuntos de registros que mudam ativamente.

Contact

Entrar em contacto

Contacte-nos para qualquer questão ou necessidade de apoio.Estamos sempre prontos para ajudar!

Iniciar integração

O Email é obrigatório. Telegram ou WhatsApp — opcionais.

O seu nome opcional
Email opcional
Assunto opcional
Mensagem opcional
Telegram opcional
@
Se indicar Telegram — responderemos também por lá.
WhatsApp opcional
Formato: +indicativo e número (ex.: +351XXXXXXXXX).

Ao clicar, concorda com o tratamento dos seus dados.