TB
← Tüm yazılar

Pagination standartlari

Büyük veri setlerinde sayfalama stratejileri, offset vs cursor tabanli yaklasimlar ve performans etkileri.

API'ler büyük koleksiyonlari tek yanitta donmek performans ve bellek acisindan surdurulemez. Sayfalama (pagination), veriyi parcalara bolerek istemci ve sunucu uzerindeki yuku sınırlar. Offset/limit, cursor/keyset, seek ve link-based pagination gibi stratejiler farklı trade-off'lar sunar. Standart bir sayfalama sozlesmesi istemci SDK'larini basitlestirir ve veritabanı performansini korur. Tutarsiz parametre isimleri (page vs pageNumber vs offset) entegrasyonu gereksiz yere zorlastirir.

Offset pagination

En tanidik model: GET /products?page=3&pageSize=20 veya offset=40&limit=20.

{
  "items": [ ... ],
  "page": 3,
  "pageSize": 20,
  "totalCount": 1542,
  "totalPages": 78
}

Avantaj: rastgele sayfaya atlama mümkün; sayfa numarali UI ile uyumlu. Dezavantaj: büyük offset'te veritabanı tüm atlanan satirlari tarar. Veri eklenip silindikce sayfa kaymasi (duplicate veya eksik kayıt) olusabilir. Admin panelleri ve küçük veri setlerinde kabul edilebilir.

Cursor ve keyset pagination

Cursor genelde son kaydin siralanmis alanlaridir:

GET /products?cursor=eyJpZCI6MTIzfQ&limit=20

{
  "items": [ ... ],
  "nextCursor": "eyJpZCI6MTQzfQ",
  "hasMore": true
}

Sorgu: WHERE id > @cursor ORDER BY id LIMIT 20. Index dostu, tutarlı iterasyon. Keyset pagination composite sort kullanır: ORDER BY createdAt DESC, id DESC. Cursor opaque base64 JSON olmali; istemci parse etmemeli. N. sayfaya dogrudan atlama zor veya imkansizdir.

Link header pagination (RFC 8288)

Link: </products?cursor=abc&limit=20>; rel="next",
      </products?cursor=xyz&limit=20>; rel="prev",
      </products?cursor=start&limit=20>; rel="first"

GitHub API tarzi. Govdede yalnizca items; navigasyon header'da. Hypermedia seven ekipler icin uygundur. Istemci SDK Link header parse edebilir veya govdede mirror link alanlari sunulabilir.

totalCount maliyeti

SELECT COUNT(*) büyük tablolarda pahali. Çözümler:

  • Tahmini count: PostgreSQL reltuples istatistigi.
  • hasMore only: limit+1 kayıt cek, fazlasi varsa hasMore true.
  • Ayri endpoint: Count yalnizca gerektiginde.
  • Cached count: Kisa TTL ile aggregate tablo.

totalCount zorunlu degilse hasMore yeterlidir; sonsuz scroll ve mobil feed senaryolarinda tercih edilir.

pageSize limitleri

Varsayilan 20-50, maksimum 100-250. Istemci asiri limit gonderirse clamp veya 400. Dokumantasyonda açık yazilmali. Büyük pageSize DoS vektoru olabilir; rate limit ile birlikte dusunulmelidir.

Siralama, filtre ve cursor uyumu

Sort parametresi whitelist: sort=createdAt,-price. Filtre degisince önceki cursor gecersiz olabilir; istemci yeni sorgu baslatmali. Cursor icinde sort state encode edilebilir. Ascending ve descending icin ayri index stratejisi gerekir.

ASP.NET Core cursor ornegi

public sealed record CursorPage<T>(
    IReadOnlyList<T> Items,
    string? NextCursor,
    bool HasMore);

public async Task<CursorPage<ProductDto>> ListAsync(
    string? cursor, int limit, CancellationToken ct)
{
    limit = Math.Clamp(limit, 1, 100);
    var query = _db.Products.AsNoTracking().OrderBy(p => p.Id);
    if (cursor is not null)
    {
        var id = CursorCodec.Decode(cursor);
        query = query.Where(p => p.Id > id);
    }
    var rows = await query.Take(limit + 1).ToListAsync(ct);
    var hasMore = rows.Count > limit;
    if (hasMore) rows.RemoveAt(rows.Count - 1);
    var next = hasMore ? CursorCodec.Encode(rows[^1].Id) : null;
    return new CursorPage<ProductDto>(
        rows.Select(x => x.ToDto()).ToList(), next, hasMore);
}

GraphQL Relay connection pattern

{
  products(first: 20, after: "YXJyYXljb25uZWN0aW9uOjE=") {
    edges {
      node { id name price }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Relay spec cursor opaque ve base64 encoded. REST API'ler benzer modeli JSON govdesi ile uygulayabilir. GraphQL'de totalCount opsiyonel ve pahali olabilir.

Performans karsilastirmasi ve güvenlik

Offset sayfa 1000'de yavaslar; cursor sabit maliyetli. Write-heavy listelerde offset tutarsizlik riski yüksek. Feed ve timeline cursor tercih edilir. Cursor imzali (HMAC) olabilir; manipulasyon onlenir. Cursor icinde hassas veri açık metin olmamali.

OpenAPI, test ve karar rehberi

Parametreler: page/pageSize veya cursor/limit. Response: items, nextCursor/hasMore veya totalCount/totalPages. Test: bos liste, tek sayfa, cok sayfa, gecersiz cursor, concurrent insert ile offset tutarsizligi. Karar: rastgele sayfa gerekli mi? Büyük veri mi? totalCount sart mi? Bu uc soru strateji secer.

Hibrit modeller ve arama motoru entegrasyonu

Elasticsearch gibi arama motorlarinda offset hala kullanılır ancak derin sayfalama icin search_after API'si cursor benzeri çalışır. Iliskisel veritabanı ID listesi ile arama sonucu birlestirilirken sayfalama tutarlılığı zorlasir; tek kaynak tercih edilmeli. Export senaryolarinda cursor ile streaming response (chunked transfer) bellek kullanımını dusurur.

Istemci SDK soyutlamasi

SDK IAsyncEnumerable<T> veya async iterator ile tüm sayfalari otomatik iterate edebilir. Kullanıcı yalnizca await foreach yazar; cursor yönetimi gizlenir. Hata durumunda kaldigi cursor ile devam edilebilir. Telemetri her sayfa icin latency olcer; yavas sayfalar tespit edilir.

Seek method ve aralik sorgulari

Seek pagination WHERE (createdAt, id) > (@lastCreated, @lastId) ile composite index kullanır. Zaman bazli feed'lerde saat dilimi ve saat kaymasi dikkate alinmalidir. Aralik sorgusu (since, until) cursor ile birlestirildiginde açık dokumantasyon sarttir.

Stable sort garantisi

Siralama belirsiz oldugunda sayfalar arasi kayıt kaymasi olur. Primary key her zaman tie-breaker olarak eklenmelidir. Nullable sort alanlarinda NULLS FIRST/LAST davranisi SQL ve API'de ayni olmalidir. Dokumantasyonda null siralama açıkça yazilir.

Export ve streaming

Büyük CSV export cursor ile chunk'lanir; tek HTTP yaniti bellek patlatmamak icin streaming kullanılır. Export işlemi ayri asenkron job olarak modellenebilir: POST /exports, GET /exports/{id}/download. Job tamamlaninca imzali URL ile indirme sunulur.

Nested collection pagination

/orders/{id}/lines gibi alt koleksiyonlarda sayfalama bagimsiz cursor tasir. Ust kaynak silindiginde alt liste 404 donmelidir. Ic ice sayfalama parametreleri (linesCursor, linesLimit) ust parametrelerle karismamali. GraphQL'de field bazli pagination bu sorunu farklı şekilde cozer.

Veri degisimi sirasinda tutarlılık

Canli auction veya borsa verisinde cursor aninda gecersizlesmez; ancak yeni kayitlar siraya eklendiginde tekrar gorulebilir. Snapshot isolation gerektiren raporlarda cursor oluşturma anindaki transaction ID saklanabilir. Bu ileri seviye pattern dokumante edilmelidir.

JSON:API pagination profili

JSON:API spesifikasyonu page[offset], page[limit] ve links alanini standartlastirir. Uyumlu istemci kutuphaneleri otomatik sayfa gezer. Kendi API'niz JSON:API kullanmasa bile links ve meta alanlari icin referans alinabilir.

Index tasarımı ve EXPLAIN analizi

Cursor pagination performansi index'e baglidir. Sort alani ve tie-breaker birlikte composite index olusturulmali. EXPLAIN ANALYZE ile derin sayfa sorgulari olculmeli. Covering index sorgu maliyetini dusurur. Offset pagination'da bile filtre + sort icin uygun index seçimi sarttir.

API gateway ve cache katmanı

Liste yanitlari gateway'de cache'lenebilir ancak cursor parametreli URL'ler cache key cesitliligini artirir. Offset sayfalama CDN cache ile daha uyumludur; cursor feed'ler genelde cache'lenmez. Vary header sort ve filter parametrelerine gore ayarlanir.

Sayfalama hata sozlesmesi

Gecersiz cursor icin 400 Bad Request ve Problem Details donulmelidir. Limit asimi clamp yerine açık hata tercih edilebilir. Bos sayfa (items: [], hasMore: false) 200 ile donmeli; 404 yalnizca ust kaynak yoksa kullanılır. Tutarlı hata mesajlari istemci SDK'da otomatik retry veya kullanıcı bilgilendirmesini kolaylastirir. Örnek hata tipi: invalid-cursor. Dokumantasyonda desteklenen maksimum limit, geçerli sort alanlari ve cursor formati açıkça listelenmelidir. Örnek istekler her sayfalama modu icin OpenAPI examples bolumunde verilmelidir ve contract testlerinde dogrulanmalidir.