Пагинация жана курсорлор
1) Эмне үчүн пагинация керек
Пагинация кардар тарабынан берилген жана рендерилген маалыматтардын көлөмүн чектейт, сактоочу жайларга/тармактарга жүктү азайтат жана коллекция боюнча детерминацияланган "сейилдөө" ыкмасын белгилейт. Чыныгы системаларда пагинация - бул 'page = 1 & limit = 50' гана эмес, протоколдук контракттардын жана ырааттуулуктун инварианттарынын жыйындысы.
Типтүү максаттары:- өтүнүч боюнча жашыруун жана эс контролдоо.
- маалымат топтому өзгөргөндө туруктуу багыттоо (киргизүү/алып салуу).
- калыбына келтирүү мүмкүнчүлүгү (resumption).
- кэш жана алдын ала жүктөө (prefetch).
- кыянаттык менен коргоо (rate limiting, backpressure).
2) Пагинация моделдери
2. 1 OFFSET/LIMIT (барактык)
Идея: "N саптар сагынам, кайра M".
Артыкчылыктары: жөнөкөйлүк, дээрлик ар кандай SQL/NoSQL менен шайкеш келет.
- Сызыктуу деградация: чоң OFFSET толук сканерлөө/skip-cost алып келет.
- Суроо-талаптардын ортосундагы киргизүү/алып салууда туруксуздук (жылыштар "калкып").
- Так "кайра жаралуучулукту" камсыз кылуу кыйын.
sql
SELECT
FROM orders
ORDER BY created_at DESC, id DESC
OFFSET 1000 LIMIT 50;
2. 2 Cursor/Keyset/Seek-Pagination
Идея: "K ачкычы менен улантуу". Курсор - бул иреттелген топтомдогу позиция.
Артыкчылыктары:- O (1) индекс болгондо улантуу мүмкүнчүлүгү.
- Коллекция өзгөргөндө туруктуулук.
- терең "беттерде" мыкты жашыруун.
- Катуу аныкталган, уникалдуу жана монотондук сорттоо ачкычтары керек.
- Ишке ашыруу жана оңдоо кыйыныраак.
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 (тунук эмес токендер)
Идея: сервер "позиция" коддолгон opaque токенин кайтарып берет (жана балким, шардалардын/фильтрлердин абалы). Кардар ичин түшүнбөйт жана жөн гана кийинки бет үчүн токенди кайтарып берет.
Артыкчылыктары: ийкемдүүлүк, API сындыруусуз схемасын өзгөртүү мүмкүнчүлүгү.
Минустары: Токендердин жашоо мөөнөтүн башкаруу, деплояларда шайкештиги.
2. 4 Убакыт жана логикалык курсорлор
Time-based: "Т чейин бардык жазуулар", курсор - убакыт белгиси (append-only агымдарда ылайыктуу).
Log-sequence/offset-based: курсор - логиндеги жылыш (Kafka offset, journal seq).
Global monotonic ID: Snowflake/UUIDv7 туруктуу seek үчүн сорттолгон ачкычтар катары.
3) Курстарды жана токендерди долбоорлоо
3. 1 жакшы курсор касиеттери
Opacity (opaque): кардар формат көз каранды эмес.
Authority/бүтүндүгү: HMAC кол алмаштыруу/манипуляция алдын алуу.
Контекст: сорттоо кирет, чыпкалар, схема версия, tenant/shard.
Өмүр: TTL жана "кол тийбестик" (non-replay) индекстерин/кирүү укугун өзгөртүү.
Көлөмү: компакт (<= 1-2 KB), URL үчүн жарактуу.
3. 2 Токендин форматы
Сунушталган стек: JSON → кысуу (zstd/deflate) → Base64URL → HMAC.
Пайдалуу жүктүн түзүлүшү (мисал):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
}
Жогоруда 'mac = HMAC (secret, payload)' кошулат жана баары бир саптуу токенге коддолот.
3. 3 Коопсуздук
кол коюу (HMAC/SHA-256).
Кошумча шифрлөө (AES-GCM) сезгич баалуулуктар (PII) болгондо.
Серверде валидация: версия, TTL, колдонуучунун ыйгарым укуктары (RBAC/ABAC).
4) Ырааттуулук жана инварианттар
4. 1 Туруктуу сорттоо
Толук детерминизмди колдонуңуз: 'ORDER BY ts DESC, id DESC'.
сорттоо ачкычы уникалдуу болушу керек ('id' tiebreaker катары кошуу).
Индекс сорттоого туура келиши керек (covering index).
4. 2 Сүрөттөр (snapshot) жана изоляция
"Кысылбаган" барактар үчүн read-consistent snapshot (MVCC/txid) колдонуңуз.
Эгерде снапшот максатка ылайыксыз болсо (кымбат/көп маалыматтар), келишимди түзүңүз: "курсор позициядан мурда элементтерди кайтарат". Бул кабар тасмалар үчүн табигый нерсе.
4. 3 Беттердин ортосундагы кошумчалар/өчүрүүлөр
Seek модели минималдаштырат "дубликат/пропуск".
Алып салуу/өзгөртүү учурунда жүрүм-турумун документтештирүү: беттердин ортосунда сейрек кездешүүчү "тешиктерге" жол берилет, бирок "убакыттын өтүшү менен артка" эмес.
5) Индекстөө жана идентификатор схемалары
Композициялык индекстер катуу сорттоо тартибинде: '(created_at DESC, id DESC)'.
Monoton ID: Snowflake/UUIDv7 убакыт тартибин берет → тез издөө.
Hot Keys: shard-key боюнча бөлүштүрүү (мисалы, 'tenant _ id', 'region') жана баш оона ичинде сорттоо.
ID генераторлору: кагылышуулардан жана "келечектеги сааттардан" (clock skew) - убакытты синхрондоштуруу, NTP секирүү учурунда "регрессия".
6) Кросс-шард пагинация
6. 1 Схемалар
Scatter-Gather: бардык шар параллелдүү суроолор, жергиликтүү seek курстар, андан кийин k-way өлчөө агрегатору боюнча.
Per-Shard Cursors: Токен ар бир шард боюнча кызмат орундарын камтыйт.
Bounded fan-out: бир кадам үчүн шардалардын санын чектөө (rate limiting/timeout budget).
6. 2 multi-shard үчүн токендер
'{shard _ id, last_pos}' массивин сактаңыз. Кийинки кадамда, ар бир активдүү чард үчүн уланта бериңиз жана глобалдык сорттолгон баракты берип, кайра кармап туруңуз.
7) Протоколдук келишимдер
7. 1 REST
Суроо-талап:
GET /v1/orders? limit=50&cursor=eyJ2IjoiMyIsInNvcnQiOiJjcmVh... (opaque)
Жооп:
json
{
"items": [ /... / ],
"page": {
"limit": 50,
"next_cursor": "eyJ2IjozLCJwb3MiOiJjcmVh...==",
"has_more": true
}
}
Сунуштар:
- 'limit' жогорку чеги менен (мисалы, max = 200).
- 'next _ cursor' эгер 'has _ more = false' болсо жок.
- GET ыктымалдыгы, жооптордун кэшдүүлүгү 'next _ cursor' (белгиленген чыпкалар жана snapshot менен биринчи бет).
7. 2 GraphQL (Relay-мамиле)
Типтүү келишим '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' ачык эмес жана кол коюлушу керек; HMAC жок "чийки Base64 (id)" колдонбогула.
7. 3 gRPC
'page _ size' жана '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 агымдары жана WebSockets
үзгүлтүксүз тасмалар үчүн: курсор катары "акыркы көргөн offset/ts".
reconnect 'resume _ from' колдоо:json
{ "action":"subscribe", "topic":"orders", "resume_from":"2025-10-31T12:00:00Z#987654321" }
8) Кэш, алдын ала жүктөө, CDN
ETag/If-None-Match туруктуу чыпкалар менен биринчи бет үчүн.
кыска TTL менен Cache-Control (мисалы, 5-30 с) коомдук тизмелер үчүн.
Prefetch: кайтаруу 'next _ cursor' жана жардам ('Link: rel = "next"'), кардар кийинки баракты жүктөп алат.
Вариация: эске алуу 'filter/sort/locale/tenant' кэш бөлүгүнүн ачкычы катары.
9) Жүктү башкаруу жана чектөө
Жогорку чеги 'limit', мисалы, 200.
Server-side backpressure: суроо-убакыт> budget болсо, жооп 'limit' кыскартуу (жана так кардарга чыныгы бет өлчөмүн билдирүүгө).
Rate limits/token/tenant.
Timeout/Retry: экспоненциалдык тыныгуу, демпотенттик кайра суроо.
10) UX аспектилери
Scroll vs номерлөө: чексиз жылдыруу → курсор; саны барактар → offset (бирок маалыматтарды жаңыртуу учурунда так эместигин түшүндүрүп).
"Жерге кайтуу" баскычы: кардардын курсорлорунун стегин сактаңыз.
Бош беттер: эгер 'has _ more = false' болсо, "Дагы" баскычын көрсөтпөңүз.
Туруктуу чек: ал арзан болсо гана так 'total' көрсөтүү (башкача - болжолдуу 'approx _ total').
11) Тестирлөө жана edge учурларда
Чек-баракчалар:- Туруктуу сорттоо: ошол эле 'ts' бар элементтер "жарк этпейт".
- Кошумчалар/өчүрүүлөр: барактардын кесилишинде кайталоолор пайда болбойт.
- Барактардын ортосундагы чыпкалардын өзгөрүшү: токен "эскирген/шайкеш келбеген" деп четке кагылышы керек.
- TTL токен: туура ката мөөнөтү бүткөндөн кийин.
- Чоң тереңдик: жашыруун сызыктуу өсөт.
- Multishard: туура өлчөө тартиби, starvation "жай" Чард жоктугу.
python
Generate N entries with random inserts between calls
Verify that all pages are merged = = whole ordered fetch
12) Байкоо жана SLO
Метрикасы:- 'list _ request _ latency _ ms' (P50/P95/P99) барактын узундугу боюнча.
- 'seek _ index _ hit _ ratio' (жабуучу индекс боюнча кеткен суроо-талаптардын үлүшү).
- 'next _ cursor _ invalid _ rate' (валидация каталары/TTL/кол тамгалар).
- 'merge _ fanout' (баракка тартылган шардалардын саны).
- 'duplicates _ on _ boundary' жана 'gaps _ on _ boundary' (кардар телеметриясындагы детал).
- Логдордо 'cursor _ id' менен байланышып, payload маскасын.
- Тегдер: 'page _ size', 'source _ shards', 'db _ index _ used'.
- Жеткиликтүү: 99. 9% боюнча 'List' ыкмалары.
- Латенттүүлүк: P95 <200 ms үчүн 'page _ size <= 50' жергиликтүү чардда.
- Токендин катасы: <0. чалуулардын жалпы санынын 1%.
13) Миграция жана шайкештик
Токенге 'v' киргизип, N жуманын эски версияларын сактаңыз.
сорттоо ачкычтарын өзгөртүп жатканда - "жумшак" ката жөнөтүү '409 Conflict' курьерсиз жаңы листингди аткаруу үчүн.
Каргашалуу окуя (бардык токендердин жылышы): 'signing _ key _ id' өзгөртүп, эскилерин четке кагыңыз.
14) Ишке ашыруу мисалдары
14. 1 Токендин генерациясы (псевдокод)
python payload = json. dumps({...}). encode()
compressed = zlib. compress(payload)
mac = hmac_sha256(signing_key, compressed)
token = base64url_encode(mac + compressed)
14. 2 Токендин валидациясы
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-суроо
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) Коопсуздук жана шайкештик
Токендерге чийки талааларды киргизбеңиз, алардан PII чыгарса болот.
Кол коюу жана TTL чектөө.
Токендерди колдонуучулардын ортосунда чыдагыс кылууга аракет кылыңыз ('sub/tenant/roles' жазыңыз жана валидация учурунда текшериңиз).
Токендердин хешилерин гана логин.
16) Тез-тез каталар жана анти-үлгүлөрү
Base64 (id) курсор катары: жасалмалоо/алуу оңой, сорттоо өзгөргөндө келишимди бузат.
tie-breaker жоктугу: 'ORDER BY ts DESC' no 'id' → дубликаттар/ат чабуулар.
Токендин майыптыгы жок беттердин ортосундагы фильтрлерди алмаштыруу.
Deep OFFSET: жай жана күтүүсүз.
нускасы жана TTL жок токендер.
17) Mini текшерүү киргизүү
1. сорттоо аныктоо жана уникалдуу tie-breaker кошуу.
2. Бул тартипте жабуу индексин түзүү.
3. Моделди тандоо: seek + тунук эмес токен.
4. Токендин колтамгасын (жана, зарыл болсо, шифрлөө) ишке ашырыңыз.
5. TTL жана чыгаруу койду.
6. Контракттарды түзүү жана документтештирүү 'has _ more', 'next _ cursor'.
7. кросс-шард схемасы (керек болсо) жана k-way merge ойлонуп.
8. Метрика, алерт жана SLO кошуу.
9. property-based тесттер беттердин чектерин жаап.
10. Токендердин миграциялык стратегиясын сүрөттөп бериңиз.
18) Ыкманы тандоо боюнча кыскача сунуштар
Каталогдор/издөө маанилүү жерде "барактын номери" жана болжолдуу total: мисалы, 'OFFSET/LIMIT' + кэш; жалпы болжолдуу экенин билдириңиз.
тасмалар, аналитика, терең тизмелери, жогорку RPS: гана cursor/seek.
Charded/бөлүштүрүлгөн чогултуу: per-shard cursors + merge токен.
Агымдар/CDC: offsets/ts сыяктуу курсорлор кайра.
19) Мисалы келишим API (резюме)
`GET /v1/items? limit=50&cursor=...`
Жооп ар дайым 'page камтыйт. limit`, `page. has_more', кошумча 'page. next_cursor`.
Курсор тунук эмес, кол коюлган, TTL менен.
сорттоо аныкталган: 'ORDER BY created_at DESC, id DESC'.
Терүү өзгөргөндө жүрүм-турум: элементтер курсорго карата "артка кайтпайт".
Көрсөткүчтөр жана каталар стандартташтырылган: 'invalid _ cursor', 'expired _ cursor', 'mismatch _ filters'.
Бул макала архитектуралык принциптерди жана даяр үлгүлөрдү берет, ал тургай чоң маалыматтар, шардана жана активдүү өзгөрүп турган жазуулар топтомунун шарттарында да тез, алдын ала жана коопсуз бойдон калууда.