GH GambleHub

Paginazione e puntatori

1) Perché la paginazione

La paginazione limita la quantità di dati trasmessi e resi dal cliente, riduce il carico di storage/rete e definisce un metodo definito per «passeggiare» nella raccolta. Nei sistemi reali, la paginazione non è solo «page = 1 & limit = 50», ma una serie di contratti protocollari e invarianti di coerenza.

Obiettivi tipici:
  • Controllo della latitanza e della memoria per la richiesta.
  • Navigazione stabile quando il set di dati (incolla/elimina) viene modificato.
  • Possibilità di riprendere dalla posizione.
  • Disconnessione e preordine (prefetch).
  • Protezione dagli abusi (rate limiting, backpressure).

2) Modelli di paginazione

2. 1 OFFSET/LIMIT (pagina)

«Salta le righe N, restituisci la M».
I vantaggi sono semplici, compatibili con quasi tutte le SQL/NoSQL.

Contro:
  • Degrado lineare: i grandi OFFSET conducono alla scansione completa/skip-cost.
  • Instabilità durante l'inserimento o l'eliminazione tra le query (offset fluttuanti).
  • È difficile garantire una rinnovabile precisa.
Esempio SQL:
sql
SELECT
FROM orders
ORDER BY created_at DESC, id DESC
OFFSET 1000 LIMIT 50;

2. 2 Cursor/Keyset/Seek-paginazione

«Continua con la chiave K». Il cursore è una posizione in un set ordinato.

Vantaggi:
  • O (1) accesso al proseguimento in presenza di un indice.
  • Stabilità delle modifiche alla raccolta.
  • La miglior latitanza delle pagine profonde.
Contro:
  • Servono chiavi di ordinamento rigorosamente definite, uniche e monouso.
  • Più difficile da realizzare e debugger.
Esempio SQL (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 Continuation tokens (token opachi)

Idea: il server restituisce l'opache-token in cui è codificata la posizione (e forse lo stato dei chard/filtri). Il cliente non capisce le interiorità e restituisce il token per la pagina successiva.
I vantaggi sono la flessibilità, la possibilità di cambiare schema senza l'astinenza dell'API.
Contro: gestione della durata dei token, compatibilità con i depositi.

2. 4 Cursori temporanei e logici

Time-based: «tutti i record fino a T», il cursore è un'etichetta temporale (appropriata per i flussi append-only).
Log-sequence/offset-based - Puntatore - Offset (Kafka offset, journal seq).
Global monotonic IDs: Snowflake/UUIDv7 come chiave ordinabile per seek stabile.

3) Progettazione di corsi e token

3. 1 Proprietà buon cursore

Opacità - Il client non dipende dal formato.
Copyright/integrità: firma HMAC per impedire la sostituzione/manipolazione.
Il contesto include ordinamento, filtri, versione dello schema, tenant/shard.
Durata della vita: TTL e Non-Replay quando cambiano indice/privilegi di accesso.
Dimensioni compatte (<= 1-2 KB) adatte all'URL.

3. 2 Formato token

La pila consigliata è la compressione JSON (zstd/deflate) da Base64URL a HMAC.

Struttura del carico utile (esempio):
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
}

In alto viene aggiunto «mac = HMAC (secret, payload)» e tutto viene codificato in un tocco di stringa.

3. 3 Sicurezza

Firma (HMAC/SHA-256).
Crittografia opzionale (AES-GCM) in presenza di valori sensibili (PII).
Convalida sul server: versione, TTL, autorizzazioni utente (RBAC/ABAC).

4) Coerenza e invarianti

4. 1 Ordinamento stabile

Usa il determinismo completo: 'ORDER BY ts DESC, id DESC'.
La chiave di ordinamento deve essere univoca (aggiungi «id» come tiebreaker).
L'indice deve corrispondere all'ordinamento (covering index).

4. 2 Snapshot e isolamento

Per le pagine «inattive», utilizzare il read-consistent snapshot (MVCC/txid).
Se non è appropriato (costoso/pieno di dati), formulare il contratto: «Il cursore restituisce gli elementi, strettamente precedentemente posizioni». È naturale per i notiziari.

4. 3 Inserisci/rimuovi tra le pagine

Il modello seek minimizza i «duplicati/omessi».
Documentare il comportamento durante la rimozione/modifica: sono consentiti rari buchi tra le pagine, ma non indietro nel tempo.

5) Indicizzazione e schemi di identificatori

Gli indici compositi sono rigorosamente in ordine di ordinamento: '(created _ at DESC, id DESC)'.
ID monotonici: Snowflake/UUIDv7 dà ordine di tempo per accelerare il seek.
Chiavi hot: distribuisci shard-key (ad esempio tenant _ id, region) e ordina all'interno dello shard.
Generatori ID: evitare collusioni e «orologi dal futuro» - sincronizzazione del tempo, «regressione» nei salti NTP.

6) Paginazione cross-shard

6. 1 Schemi

Scatter-Gather - Richieste parallele a tutti gli shard, corsi di seek locali, quindi k-way merge sull'aggregatore.
Per-Shard Cursors - Il token contiene posizioni per ogni shard.
Bounded fan-out - Limitare il numero di sardi per passo (rate limiting/timeout budget et).

6. 2 Token per multi-shard

Memorizza l'array «{shard _ id, last _ pos}». Al passo successivo, riprendete per ogni shard attivo e mentite di nuovo, dando una pagina ordinata globalmente.

7) Contratti di protocollo

7. 1 REST

Richiesta:

GET /v1/orders? limit=50&cursor=eyJ2IjoiMyIsInNvcnQiOiJjcmVh... (opaque)
Risposta:
json
{
"items": [ /... / ],
"page": {
"limit": 50,
"next_cursor": "eyJ2IjozLCJwb3MiOiJjcmVh...==",
"has_more": true
}
}
Raccomandazioni:
  • «limit» con limite superiore (ad esempio max = 200).
  • «next _ cursor» non esiste se «ha _ more = false».
  • Idampotenza GET, cache delle risposte senza «next _ cursor» (prima pagina per filtri fissi e snapshot).

7. 2 GraphQL (approccio Relay)

Tipico contratto «connection»:
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 essere opaco e firmato. non utilizzare il crudo Base64 (id) senza HMAC.

7. 3 gRPC

Usa «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 Flussi e WebSockets

Per i nastri continui, il cursore è come «ultimo offset/ts visto».

Supporta «resume _ from» durante la riconnessione:
json
{ "action":"subscribe", "topic":"orders", "resume_from":"2025-10-31T12:00:00Z#987654321" }

8) Cache, pre-caricamento, CDN

ETAG/If-None-Match per la prima pagina con filtri stabili.
Cache-Control con TTL breve (ad esempio 5-30 c) per gli elenchi pubblici.
Prefetch: restituisci «next _ cursor» e suggerimenti («Link: rel =» next «») e il client può predisporre la pagina seguente.
Variazioni: considerate'filter/fort/locale/tenante'come chiave della parte della cache.

9) Controllo del carico e limitazione

Limite superiore, ad esempio 200.
Server-side backpressure: se il tempo di query> budget, ridurre il'limit'nella risposta (e chiaramente comunicare al cliente le dimensioni effettive della pagina).
Rate limits per utente/token/tenant.
Timeout/Retry: pausa esponenziale, ripetizioni idipotenti.

10) Aspetti UX

Scroll contro numerazione: scorrimento infinito dei cursori; pagine di targa offset (ma spieghi l'imprecisione durante l'aggiornamento dei dati).
Pulsante Ritorna alla posizione - Memorizza lo stack dei cursori del client.
Pagine vuote: se «ha _ more = false», non mostrare il pulsante «ancora».
Limiti stabili: mostra «total» preciso solo se è economico (altrimenti è approssimativamente «approx _ total»).

11) Test e valigette edge

Assegno fogli:
  • Ordinamento stabile: gli elementi con lo stesso «ts» non «lampeggiano».
  • Inserisci/rimuovi - Non vengono visualizzate ripetizioni nel giunto delle pagine.
  • Modifica dei filtri tra le pagine: il token deve essere rifiutato comè obsoleto/incompatibile ".
  • TTL token: errore corretto alla scadenza.
  • Grande profondità, la latitanza non cresce lineare.
  • Ordine merge corretto, assenza di starvation «lenti».
Esempio di test property-based (pseudocode):
python
Generate N entries with random inserts between calls
Verify that all pages are merged = = whole ordered fetch

12) Osservabilità e SLO

Metriche:
  • 'list _ sollest _ latency _ ms' (P50/P95/P99) in base alla lunghezza della pagina.
  • 'seek _ index _ hit _ ratio' (percentuale di richieste che sono uscite dall'indice di copertura).
  • 'next _ cursor _ invalid _ rate' (errori di convalida/TTL/firme).
  • «merge _ fanout» (le schede coinvolte alla pagina).
  • «duplicates _ on _ boundary» e «gaps _ on _ boundary».
Logi/tracking:
  • Correlare «cursor _ id» nei logi, mascherare payload.
  • Tag span: 'page _ size', 'source _ shards', 'db _ index _ used'.
SLO-esempio:
  • Disponibilità: 99. 9% su «List».
  • Latitudine: P95 <200 ms per'page _ size <= 50 'con sciarpa locale.
  • Errore del token: <0. 1% del totale delle chiamate.

13) Migrazioni e compatibilità

Abilita «v» nel token e supporta le versioni precedenti N settimane.
Se cambi le chiavi di ordinamento, invia l'errore «morbido» «409 Conflict» con il suggerimento di eseguire il listino fresco senza il cursore.
Caso catastrofico (ruota di tutti i token): cambia «signing _ key _ id» e rifiuta i vecchi.

14) Esempi di implementazione

14. 1 Generazione di token (pseudocode)

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

14. 2 Validazione 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 Seek query con chiave composita

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) Sicurezza e conformità

Non includere nei token i campi crudi da cui è possibile estrarre il PII.
Firmare e limitare la TTL.
Cerca di rendere i token intolleranti tra gli utenti (inserisci «sub/tenant/roles» in payload e incrocia durante la validazione).
Logica solo gli hashtag dei token.

16) Errori frequenti e anti-pattern

Base64 (id) come cursore: facile da contraffazione/selezione, rompe il contratto quando cambia ordinamento.
Nessun tie-breaker: «ORDER BY ts DESC» senza «id» è duplicato/salto.
Cambia i filtri tra le pagine senza disabilità del token.
OFFSET profondo: lento e imprevedibile.
Token senza versione e TTL.

17) Mini-listino di implementazione

1. Definire l'ordinamento e aggiungere un tie-breaker univoco.
2. Creare un indice di copertura sotto questo ordine.
3. Selezionate seek + token opaco.
4. Firmare (e crittografare, se necessario) il token.
5. Piazzate TTL e versioning.
6. Formare e documentare i contratti «ha _ more», «next _ cursor».
7. Elaborare lo schema cross-shard (se necessario) e k-way merge.
8. Aggiungete metriche, alert e SLO.
9. Coprire con test property-based i bordi delle pagine.
10. Descrivete la strategia migratoria dei token.

18) Brevi suggerimenti per la scelta dell'approccio

Directory/ricerca in cui il numero di pagina e il totale approssimativo sono importanti: consentiamo OFFSET/LIMIT + cache; Mi dica che il totale è approssimativo.
Nastri, analisi, elenchi profondi, RPS alto: solo cursor/seek.
Raccolte sharded/distribuite: per-shard cursors + merge token.
Flusso/CDC: cursori come offsets/ts con ripresa.

19) Esempio di accordo API (curriculum)

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

La risposta è sempre "page. limit`, `page. ha _ more ', opzionale'page'. next_cursor`.
Il puntatore è opaco, firmato, con TTL.
Ordinamento definito: 'ORDER BY created _ at DESC, id DESC'.
Comportamento delle modifiche al set: gli elementi non tornano indietro rispetto al cursore.
Le metriche e gli errori sono standardizzati: 'invalid _ cursor', 'expired _ cursor', 'mismatch _ filters'.

Questo articolo fornisce principi architettonici e pattern pronti per progettare una paginazione che rimane veloce, prevedibile e sicura anche in condizioni di grandi dati, charding e set di record in forte evoluzione.

Contact

Mettiti in contatto

Scrivici per qualsiasi domanda o richiesta di supporto.Siamo sempre pronti ad aiutarti!

Avvia integrazione

L’Email è obbligatoria. Telegram o WhatsApp — opzionali.

Il tuo nome opzionale
Email opzionale
Oggetto opzionale
Messaggio opzionale
Telegram opzionale
@
Se indichi Telegram — ti risponderemo anche lì, oltre che via Email.
WhatsApp opzionale
Formato: +prefisso internazionale e numero (ad es. +39XXXXXXXXX).

Cliccando sul pulsante, acconsenti al trattamento dei dati.