GH GambleHub

Paginasiya və kursorlar

1) Niyə paginasiya lazımdır

Paqinasiya müştəri tərəfindən ötürülən və renderlənən məlumatların həcmini məhdudlaşdırır, anbarlara/şəbəkələrə yükü azaldır və kolleksiya üzrə determinik «gəzinti» üsulunu təyin edir. Real sistemlərdə paginasiya yalnız 'page = 1 & limit = 50' deyil, protokol müqavilələri və uyğunluq invariantları toplusudur.

Tipik məqsədlər:
  • Tələb üçün gizli və yaddaş nəzarət.
  • Data dəsti dəyişdikdə sabit naviqasiya (insert/silmə).
  • Yerdən bərpa etmək imkanı (resumption).
  • Caching və ön yükləmə (prefetch).
  • Sui-istifadədən qorunma (rate limiting, backpressure).

2) Paginasiya modelləri

2. 1 OFFSET/LIMIT (səhifə)

Fikir: «N sətir buraxın, M qaytarın».
Üstünlüklər: sadəlik, demək olar ki, hər hansı bir SQL/NoSQL ilə uyğun gəlir.

Mənfi cəhətləri:
  • Xətti deqradasiya: böyük OFFSET tam scan/skip-cost səbəb olur.
  • Sorğular arasında əlavə/silinmə zamanı qeyri-sabitlik (yerdəyişmə «üzür»).
  • Dəqiq «bərpa olunma» təmin etmək çətindir.
SQL nümunəsi:
sql
SELECT
FROM orders
ORDER BY created_at DESC, id DESC
OFFSET 1000 LIMIT 50;

2. 2 Cursor/Keyset/Seek-pagination

Fikir: «K açarından davam et». Kursor sıralanmış dəstdəki mövqedir.

Üstünlüklər:
  • O (1) indeks olduqda davam etmək imkanı.
  • Kolleksiyanın dəyişməsində sabitlik.
  • Dərin «səhifələrdə» ən yaxşı gizlilik.
Mənfi cəhətləri:
  • Ciddi şəkildə müəyyən edilmiş, unikal və monoton çeşidləmə açarları lazımdır.
  • Həyata keçirmək və düzəltmək daha çətindir.
SQL-nümunə (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 (qeyri-şəffaf tokenlər)

Fikir: server «mövqe» kodlaşdırılmış opaque tokenini qaytarır (və bəlkə də şard/filtrlərin vəziyyəti). Müştəri daxili hissələri başa düşmür və sadəcə növbəti səhifə üçün tokeni geri qaytarır.
Üstünlüklər: çeviklik, API qırılmadan sxemi dəyişdirmək imkanı.
Mənfi cəhətləri: tokenlərin ömrünü idarə etmək, deplolarda uyğunluq.

2. 4 Müvəqqəti və məntiqi kursorlar

Time-based: «T qədər bütün qeydlər», kursor - vaxt işarəsi (append-only axınlar üçün uyğun).
Log-sequence/offset-based: kursor - yerdəyişmə (Kafka offset, journal seq).
Global monotonic IDs: sabit seek üçün çeşidlənmiş açarlar kimi Snowflake/UUIDv7.

3) Kursların və tokenlərin layihələndirilməsi

3. 1 Yaxşı kursor xüsusiyyətləri

Opaklıq (opaque): müştəri formatından asılı deyil.
Müəlliflik/bütövlük: HMAC imzası dəyişdirmə/manipulyasiya qarşısını almaq üçün.
Kontekst: çeşidləmə, filtrlər, sxem versiyası, tenant/shard daxildir.
Ömrü: TTL və «toxunulmazlıq» (non-replay) indeksləri/giriş hüquqları dəyişdirilərkən.
Ölçüsü: kompakt (<= 1-2 KB), URL üçün uyğun.

3. 2 Token formatı

Tövsiyə olunan yığın: JSON → sıxılma (zstd/deflate) → Base64URL → HMAC.

Yük strukturu (nümunə):
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
}

Yuxarıdan 'mac = HMAC (secret, payload)' əlavə olunur və hamısı bir sətirli tokenə kodlanır.

3. 3 Təhlükəsizlik

İmzala (HMAC/SHA-256).
Həssas dəyərlər (PII) olduqda (AES-GCM) əlavə şifrələyin.
Serverdə validasiya: versiya, TTL, istifadəçi səlahiyyətləri (RBAC/ABAC).

4) Uyğunluq və invariantlar

4. 1 Sabit çeşidləmə

Tam determinizmdən istifadə edin: 'ORDER BY ts DESC, id DESC'.
Çeşidləmə açarı unikal olmalıdır ('id' tiebreaker kimi əlavə edin).
İndeks çeşidləməyə uyğun olmalıdır (covering index).

4. 2 Şəkillər (snapshot) və izolyasiya

«Qaçmayan» səhifələr üçün read-consistent snapshot (MVCC/txid) istifadə edin.
Snapshot məqsədəuyğun deyilsə (bahalı/çox məlumat), müqaviləni tərtib edin: «kursor mövqedən əvvəl elementləri geri qaytarır». Bu, xəbər lentləri üçün təbiidir.

4. 3 Səhifələr arasında əlavələr/silinmə

Seek modeli «dublikatlar/boşluqları» minimuma endirir.
Silmə/dəyişdirmə zamanı davranışı sənədləşdirin: səhifələr arasında nadir «deşiklərə» icazə verilir, lakin «zamanda geri» deyil.

5) Indeksləşdirmə və identifikator sxemləri

Kompozit indekslər sıralanma qaydasına uyğundur: '(created_at DESC, id DESC)'.
Monoton ID: Snowflake/UUIDv7 vaxt sifariş verir → seek sürətləndirir.
Qaynar açarlar: shard-key (məsələn, 'tenant _ id', 'region') vasitəsilə paylayın və kard daxilində sıralayın.
ID generatorları: NTP atlamaları zamanı zaman sinxronizasiyası, «reqressiya» - «gələcəkdən saat» və toqquşmalardan çəkinin.

6) Xaç-şard paginasiyası

6. 1 Sxemlər

Scatter-Gather: bütün toplara paralel sorğular, yerli seek kursları, sonra aqreqatorda k-way merge.
Per-Shard Cursors: token hər şard üçün mövqelər ehtiva edir.
Bounded fan-out: bir addımda şard sayını məhdudlaşdırın (rate limiting/timeout budget).

6. 2 multi-shard üçün tokenlər

{shard _ id, last_pos}'. Növbəti addımda hər bir aktiv kard üçün davam edin və qlobal sıralanmış səhifə verərək yenidən saxlayın.

7) Protokol müqavilələri

7. 1 REST

Sorğu:

GET /v1/orders? limit=50&cursor=eyJ2IjoiMyIsInNvcnQiOiJjcmVh... (opaque)
Cavab:
json
{
"items": [ /... / ],
"page": {
"limit": 50,
"next_cursor": "eyJ2IjozLCJwb3MiOiJjcmVh...==",
"has_more": true
}
}
Tövsiyələr:
  • 'limit' (məsələn, max = 200).
  • 'next _ cursor', əgər 'has _ more = false' yoxdursa.
  • GET idempotentliyi, «next _ cursor» olmadan cavabların önbelləklənməsi (sabit filtrlər və snapshot ilə birinci səhifə).

7. 2 GraphQL (Relay-yanaşma)

Tipik 'connection' müqaviləsi:
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' qeyri-şəffaf və imzalanmalıdır; HMAC olmadan «xam Base64 (id)» istifadə etməyin.

7. 3 gRPC

'page _ size' və 'page _ token' istifadə edin:
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 Streams və WebSockets

Davamlı lentlər üçün: «son görülən offset/ts» kimi kursor.

Rekonektdə 'resume _ from' dəstəkləyin:
json
{ "action":"subscribe", "topic":"orders", "resume_from":"2025-10-31T12:00:00Z#987654321" }

8) Caching, ön yükləmə, CDN

ETag/If-None-Match sabit filtrlərlə ilk səhifə üçün.
Qısa TTL ilə Cache-Control (məsələn, 5-30 s) ictimai siyahılar üçün.
Prefetch: 'next _ cursor' və ipuçlarını ('Link: rel =' next '') qaytarın, müştəri növbəti səhifəni yükləyə bilər.
Varyasyonlar: 'filter/sort/locale/tenant' cache hissəsinin açarı kimi nəzərə alın.

9) Yükün idarə edilməsi və məhdudlaşdırılması

Yuxarı sərhəd 'limit', məsələn 200.
Server-side backpressure: sorğu vaxtı> budget olarsa, cavabda 'limit' azaldın (və müştəriyə səhifənin həqiqi ölçüsünü açıq şəkildə bildirin).
Rate limits/token/tenant.
Timeout/Retry: eksponent fasilə, idempotent təkrar sorğular.

10) UX aspektləri

Scroll vs nömrələmə: sonsuz fırlanma → kursorlar; → offset nömrə səhifələri (lakin məlumatların yenilənməsi zamanı qeyri-dəqiqliyi izah edin).
«Yerə qayıtmaq» düyməsi: müştərinin kursor yığınını saxlayın.
Boş səhifələr: əgər 'has _ more = false' varsa, «Daha çox» düyməsini göstərməyin.
Sabit sərhədlər: Yalnız ucuz olduqda dəqiq 'total' göstərin (əks halda təxmini 'approx _ total').

11) Test və edge-cases

Çek vərəqləri:
  • Sabit çeşidləmə: eyni 'ts "olan elementlər" yanıb-sönmür ".
  • Əlavələr/silmələr: Səhifələrin qovşağında təkrarlamalar görünmür.
  • Səhifələr arasında filtrələrin dəyişdirilməsi: token «köhnəlmiş/uyğun olmayan» kimi kənara çıxmalıdır.
  • TTL token: vaxtından sonra düzgün səhv.
  • Böyük dərinlik: gecikmə xətti artmır.
  • Multişard: düzgün ölçü qaydası, starvation «yavaş» şardların olmaması.
property-based test nümunəsi (psevdokod):
python
Generate N entries with random inserts between calls
Verify that all pages are merged = = whole ordered fetch

12) Müşahidə və SLO

Metriklər:
  • 'list _ request _ latency _ ms' (P50/P95/P99) səhifənin uzunluğuna görə.
  • 'seek _ index _ hit _ ratio' (əhatə edən indeks üzrə gedən sorğuların payı).
  • 'next _ cursor _ invalid _ rate' (validasiya xətaları/TTL/imzalar).
  • 'merge _ fanout' (səhifədə iştirak edən şardların sayı).
  • 'duplicates _ on _ boundary' və 'gaps _ on _ boundary'.
Log/Trace:
  • Log 'larda 'cursor _ id' korrelyasiya edin, payload maskası.
  • Spanları etiketləyin: 'page _ size', 'source _ shards', 'db _ index _ used'.
SLO nümunəsi:
  • Mövcudluq: 99. 9% 'List' metodlarında.
  • Gecikmə: P95 <200 ms üçün 'page _ size <= 50' lokal sürətdə.
  • Token səhvi: <0. Ümumi zənglərin 1% -i.

13) Miqrasiya və uyğunluq

Tokendə 'v' daxil edin və N həftənin köhnə versiyalarını saxlayın.
Çeşidləmə açarlarını dəyişdirərkən, '409 Conflict' -in «yumşaq» səhvini göstərici olmadan təzə bir siyahı ilə göndərin.
Fəlakətli hal (bütün tokenlərin revakı): 'signing _ key _ id' dəyişdirin və köhnələrini rədd edin.

14) Həyata keçirilmə nümunələri

14. 1 Token istehsalı (psevdokod)

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

14. 2 Token validasiyası

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 kompozit açar ilə Seek-sorğu

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) Təhlükəsizlik və uyğunluq

PII-nin çıxarıla biləcəyi xam sahələri tokenlərə daxil etməyin.
TTL-i imzalayın və məhdudlaşdırın.
Tokenləri istifadəçilər arasında dözülməz etməyə çalışın (payload-a 'sub/tenant/roles' yazın və təsdiqlənərkən yoxlayın).
Yalnız tokenlərin heşlərini qeyd edin.

16) Tez-tez səhvlər və anti-nümunələr

Base64 (id) kursor kimi: saxtalaşdırılması/seçilməsi asandır, çeşidləmə dəyişdirilərkən müqaviləni pozur.
Tie-breaker olmaması: 'ORDER BY ts DESC' no 'id' → təkrarlanan/atlama.
Token əlilliyi olmadan səhifələr arasında filtrlərin dəyişdirilməsi.
Dərin OFFSET: yavaş və gözlənilməz.
TTL versiyasız tokenlər.

17) Mini giriş yoxlama siyahısı

1. Sıralamanı təyin edin və unikal tie-breaker əlavə edin.
2. Bu sifariş üçün örtük indeksi yaradın.
3. Model seçin: seek + qeyri-şəffaf token.
4. Tokenin imzasını (və lazım olduqda şifrələməni) həyata keçirin.
5. TTL və version qoyun.
6. 'has _ more', 'next _ cursor' müqavilələrini formalaşdırın və sənədləşdirin.
7. Xaç-şard sxemi (lazım olduqda) və k-way merge üzərində düşünün.
8. Metrik, alert və SLO əlavə edin.
9. property-based test səhifə sərhədləri əhatə edir.
10. Tokenlərin miqrasiya strategiyasını təsvir edin.

18) Yanaşma seçimi üçün qısa tövsiyələr

Kataloqlar/axtarışlar, burada «səhifə nömrəsi» və təxmini total vacibdir: məsələn 'OFFSET/LIMIT' + cache; ümumi təxmini olduğunu bildirin.
Lentlər, analitika, dərin siyahılar, yüksək RPS: yalnız cursor/seek.
Charded/paylanmış kolleksiyalar: per-shard cursors + merge token.
Axınlar/CDC: kursorlar offsets/ts yeniləmə ilə.

19) API razılaşma nümunəsi (xülasə)

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

Cavab həmişə 'page daxildir. limit`, `page. has_more', isteğe bağlı 'page. next_cursor`.
Kursor qeyri-şəffaf, imzalanmış, c TTL.
Çeşidləmə müəyyən edilmişdir: 'ORDER BY created_at DESC, id DESC'.
Dəsti dəyişdikdə davranış: elementlər kursora nisbətən «geri qayıtmır».
Metriklər və səhvlər standartlaşdırılmışdır: 'invalid _ cursor', 'expired _ cursor', 'mismatch _ filters'.

Bu məqalə hətta böyük data, charding və aktiv dəyişən qeydlər dəsti şəraitində sürətli, proqnozlaşdırıla bilən və təhlükəsiz olaraq qalan paqinasiyanı dizayn etmək üçün memarlıq prinsipləri və hazır nümunələr verir.

Contact

Bizimlə əlaqə

Hər hansı sualınız və ya dəstək ehtiyacınız varsa — bizimlə əlaqə saxlayın.Həmişə köməyə hazırıq!

İnteqrasiyaya başla

Email — məcburidir. Telegram və ya WhatsApp — istəyə bağlıdır.

Adınız istəyə bağlı
Email istəyə bağlı
Mövzu istəyə bağlı
Mesaj istəyə bağlı
Telegram istəyə bağlı
@
Əgər Telegram daxil etsəniz — Email ilə yanaşı orada da cavab verəcəyik.
WhatsApp istəyə bağlı
Format: ölkə kodu + nömrə (məsələn, +994XXXXXXXXX).

Düyməyə basmaqla məlumatların işlənməsinə razılıq vermiş olursunuz.