API Pagination Desenleri: Offset, Cursor ve Keyset Pagination
Büyük veri kümelerini API'ler üzerinden sunmak, performans ve kullanıcı deneyimi açısından önemli zorluklar getirir. Tüm veriyi tek bir yanıtta döndürmek, hem ağ bant genişliğini gereksiz yere tüketir hem de sunucu üzerinde yük oluşturur. Pagination (Sayfalama), veriyi daha küçük, yönetilebilir parçalara bölerek bu sorunu çözer. Bu yazıda, en yaygın üç pagination desenini (offset, cursor, keyset) derinlemesine inceleyecek, güçlü ve zayıf yönlerini karşılaştıracak ve .NET'te nasıl uygulanacağını göstereceğiz.
1. Offset Pagination (Sayfa Numarası ve Boyutu)
Nasıl Çalışır?
İstemci, page (sayfa numarası) ve limit (sayfa başına öğe sayısı) parametreleri ile sunucuyu çağırır. Sunucu, veritabanında OFFSET ve LIMIT (veya SKIP ve TAKE) kullanarak verinin ilgili dilimini getirir.
Örnek:
text
GET /api/products?page=2&limit=20
sql
SELECT * FROM Products ORDER BY Id OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY;
Avantajları:
-
Basitlik: Anlaşılması ve uygulanması en kolay yöntemdir.
-
Esneklik: İstemci, istediği herhangi bir sayfaya doğrudan atlayabilir (random access).
Dezavantajları:
-
Performans Sorunu: Büyük OFFSET değerleri, veritabanının tüm önceki satırları taramasına (scan) neden olur. Örneğin,
OFFSET 1000000ile yapılan bir sorgu, milyonlarca satırı okumak zorundadır. -
Tutarsızlık (Inconsistency): Sayfalar arasında veri eklenir veya silinirse, aynı sayfa numarası farklı veriler döndürebilir veya bazı kayıtlar atlanabilir.
-
Büyük Veri Kümeleri İçin Uygun Değil: Milyonlarca kayıt içeren tablolarda performans hızla düşer.
Ne Zaman Kullanılır?
-
Küçük ve orta ölçekli veri kümeleri (< 10.000 kayıt).
-
Kullanıcı arayüzünde sayfa numaralarına göre gezinme (1, 2, 3, ...) gerekiyorsa.
-
Veri setinin sık değişmediği durumlar.
2. Cursor Pagination (İmleç / Sonraki Sayfa İmleci)
Nasıl Çalışır?
İstemci, bir sonraki sayfayı almak için, sunucunun bir önceki yanıtta döndüğü bir cursor (imleç) değerini kullanır. Cursor, genellikle son kaydın benzersiz bir tanımlayıcısıdır (ör. Id, CreatedAt). Sunucu, bu cursor'dan sonraki kayıtları getirir.
Örnek:
text
GET /api/products?cursor=123&limit=20
sql
SELECT * FROM Products WHERE Id > 123 ORDER BY Id LIMIT 20;
Avantajları:
-
Yüksek Performans:
OFFSETkullanmadığı için, veritabanı indeks üzerinde doğrudan arama yapar (index seek). Büyük veri kümelerinde bile performans sabit kalır. -
Tutarlılık: Sayfalar arasında veri eklenmesi veya silinmesi, cursor'ın doğru çalışmasını etkilemez (çünkü her zaman belirli bir referans noktasından sonrasını getirir).
-
Sonsuz Kaydırma (Infinite Scroll): Sosyal medya akışları, ürün listeleri gibi sonsuz kaydırma (infinite scroll) deneyimleri için idealdir.
Dezavantajları:
-
Rastgele Sayfa Atlama Yok: İstemci, doğrudan 5. sayfaya atlayamaz. Sadece "sonraki" veya "önceki" sayfaya gidebilir.
-
Karmaşıklık: İstemci tarafında cursor yönetimi (sonraki sayfa için cursor'ı saklama) gerektirir.
-
Sıralama Kısıtı: Sıralama genellikle cursor sütununa göre yapılır. Farklı bir sıralama isteniyorsa, cursor'ın buna göre tasarlanması gerekir.
Ne Zaman Kullanılır?
-
Çok büyük veri kümeleri (milyonlarca kayıt).
-
Sonsuz kaydırma (infinite scroll) UX deseni.
-
Gerçek zamanlı veya sık değişen veri setleri.
-
Yüksek performans gereksinimleri.
3. Keyset Pagination (Seek Method / Keyset Pagination)
Nasıl Çalışır?
Cursor pagination'a benzer, ancak birden fazla sütuna göre sıralama yapılmasına olanak tanır. İstemci, son kaydın sıralama anahtarlarını (keys) sunucuya gönderir. Sunucu, bu anahtarları kullanarak bir sonraki sayfayı getirir.
Örnek:
text
GET /api/products?last_name=Smith&last_id=123&limit=20
sql
SELECT * FROM Products WHERE (Name > 'Smith') OR (Name = 'Smith' AND Id > 123) ORDER BY Name, Id LIMIT 20;
Avantajları:
-
Cursor Pagination'ın Tüm Avantajları: Yüksek performans, tutarlılık.
-
Çoklu Sütun Sıralama: İstemci, birden fazla sütuna göre sıralama yapabilir (ör.
ORDER BY Name, Id).
Dezavantajları:
-
Karmaşıklık: Hem sunucu hem de istemci tarafında daha karmaşık mantık gerektirir.
-
Rastgele Sayfa Atlama Yok: Cursor pagination gibi, doğrudan sayfa atlaması mümkün değildir.
-
NULL Değer Yönetimi: NULL değerler içeren sütunlarda sıralama yapmak zordur.
Ne Zaman Kullanılır?
-
Cursor pagination'ın tüm avantajlarının yanı sıra, çoklu sütun sıralama ihtiyacı varsa.
-
Örneğin, ürün listesini önce kategoriye, sonra fiyata göre sıralamak.
4. Karşılaştırma Tablosu
| Özellik | Offset | Cursor | Keyset |
|---|---|---|---|
| Performans (Büyük Veri) | 🔴 Zayıf | 🟢 Güçlü | 🟢 Güçlü |
| Rastgele Sayfa Atlama | ✅ Evet | ❌ Hayır | ❌ Hayır |
| Tutarlılık | 🔴 Zayıf | 🟢 Güçlü | 🟢 Güçlü |
| Çoklu Sütun Sıralama | ✅ Evet | ❌ Hayır (tek sütun) | ✅ Evet |
| Uygulama Karmaşıklığı | 🟢 Düşük | 🟡 Orta | 🔴 Yüksek |
| Kullanıcı Deneyimi | Sayfa Numaraları | Sonsuz Kaydırma | Sonsuz Kaydırma |
5. .NET'te Uygulama Stratejileri
A. Offset Pagination (EF Core ile)
csharp
public async Task<PagedResult<Product>> GetProductsAsync(int page, int pageSize)
{
var query = _context.Products.AsNoTracking();
var totalCount = await query.CountAsync();
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return new PagedResult<Product>(items, page, pageSize, totalCount);
}
B. Cursor Pagination (EF Core ile)
csharp
public async Task<IEnumerable<Product>> GetProductsAsync(int? cursor, int limit)
{
var query = _context.Products.AsNoTracking();
if (cursor.HasValue)
{
query = query.Where(p => p.Id > cursor.Value);
}
return await query
.OrderBy(p => p.Id)
.Take(limit)
.ToListAsync();
}
C. Keyset Pagination (EF Core ile)
csharp
public async Task<IEnumerable<Product>> GetProductsAsync(string? lastName, int? lastId, int limit)
{
var query = _context.Products.AsNoTracking();
if (!string.IsNullOrEmpty(lastName) && lastId.HasValue)
{
query = query.Where(p =>
string.Compare(p.Name, lastName) > 0 ||
(p.Name == lastName && p.Id > lastId.Value));
}
return await query
.OrderBy(p => p.Name)
.ThenBy(p => p.Id)
.Take(limit)
.ToListAsync();
}
6. En İyi Pratikler ve Öneriler
-
Varsayılan Olarak Cursor Kullanın: Büyük veri kümeleri için cursor pagination, offset'e göre çok daha iyi performans gösterir.
-
Cursor için Benzersiz ve Sıralı Bir Sütun Seçin:
Id,CreatedAtgibi benzersiz ve artan sütunlar idealdir. -
İndeksleri Optimize Edin: Cursor ve keyset pagination, kullanılan sütunlarda indeks olmasını gerektirir.
-
Toplam Sayıyı (Total Count) İsteğe Bağlı Yapın: Özellikle cursor pagination'da, toplam kayıt sayısını hesaplamak ek maliyet getirir. İstemci ihtiyacına göre sunun.
-
Yanıtta Cursor Bilgisini Döndürün:
next_cursor,prev_cursorgibi alanlarla istemcinin bir sonraki/önceki sayfayı kolayca talep etmesini sağlayın. -
Offset Kullanıyorsanız Limit Koyun: Maksimum sayfa boyutunu (ör. 100) sınırlayarak aşırı büyük sorguları engelleyin.
Sonuç
API pagination, modern web uygulamalarının vazgeçilmez bir parçasıdır. Offset pagination, küçük veri kümeleri ve basit kullanım senaryoları için hala geçerlidir. Ancak, büyük ve dinamik veri setleri için cursor pagination, performans ve tutarlılık açısından açık ara en iyi seçenektir. Keyset pagination ise, çoklu sütun sıralama ihtiyacı olan özel durumlarda tercih edilmelidir. Doğru deseni seçmek, API'nizin ölçeklenebilirliğini ve kullanıcı deneyimini doğrudan etkiler.