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.
- 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
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.
- 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
-- 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ı.
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 'larda 'cursor _ id' korrelyasiya edin, payload maskası.
- Spanları etiketləyin: 'page _ size', 'source _ shards', 'db _ index _ used'.
- 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.