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
reltuplesistatistigi. - 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.