Cursor Pagination, sıralı bir kayıt kümesinde gezinmek için benzersiz bir imleç kullanan sayfalı veri yükleme yöntemidir. GraphQL Specification (2025)'a göre, imleç tabanlı sayfalama, dinamik verilerle çalışan API'ler için önerilen standarttır. Cursor pagination, Offset yaklaşımının ana dezavantajlarını ortadan kaldırır: eklemeler sırasında kararsızlık ve büyük offsetlerde performans düşüşü.
Önemli Noktalar
Cursor Pagination, sunucunun verilerle birlikte özel bir işaretçi — bir imleç — döndürdüğü sayfalı bir yükleme yöntemidir. İstemci, bir sonraki kayıt grubunu almak için bu imleci sonraki istekte kullanır. Imleç, geçerli sayfadaki son öğenin benzersiz tanımlayıcısıdır.
İstemcinin “bana 20 kayıtlı 5. sayfayı ver” dediği Offset sayfalamasının aksine, imleç sayfalama farklı çalışır: “bana ID = 83 olan kayıttan sonra 20 kayıt ver.” Sunucu, WHERE id > 83 ve LIMIT 20 ile bir sorgu çalıştırır. Bu yaklaşım, eklemelerden bağımsız olarak her kaydın tam olarak bir sayfaya düşmesini garanti eder.
Imleç sayfalama kavramı, Relay Connection (GraphQL) spesifikasyonu sayesinde geniş çapta benimsenmiş ve imleç tabanlı sayfalamayı modern API'ler için standart haline getirmiştir. Relay, yanıt biçimini tanımlar: edges (imleçlerle kayıt dizisi), pageInfo (hasNextPage, hasPreviousPage, startCursor, endCursor).
Imleç sayfalama yeni bir teknik değildir — web'den çok önce veritabanlarında kullanılıyordu. SQL'de buna keyset pagination veya seek method denir. Yöntem, 2015 yılında Relay spesifikasyonunun yayınlanmasından sonra API'lerde popüler hale geldi ve HTTP taşımasında tekdüzelik için imleç biçimini base64 kodlu bir dize olarak resmileştirdi.
Imleç sayfalamanın temel prensibi, sorgunun konumlandırma için bir offset yerine dizinlenmiş bir alanda WHERE koşulu kullanmasıdır. İleri yön için WHERE id > last_id kullanılır; geri yön için WHERE id < first_id kullanılır. B-tree indeksi, imleçten sonraki ilk kaydı O(log n)'de bularak kararlı yanıt süresi sağlar.
-- İmleç '83' sonrası 20 kayıt al
SELECT id, title, created_at
FROM posts
WHERE id < 83
ORDER BY id DESC
LIMIT 20;
-- İmleç '83' ÖNCESİ 20 kayıt al (geri)
SELECT id, title, created_at
FROM posts
WHERE id > 83
ORDER BY id ASC
LIMIT 20;
Bir imleç basit (bir ID değeri) veya karmaşık (birden çok alandan oluşan) olabilir. Basit imleçler bir kaydın birincil anahtarıdır, örneğin otomatik artan id veya UUID. Bileşik imleçler, benzersiz olmayan alanlara göre sıralama için kullanılır, örneğin (created_at, id), burada id aynı zaman damgalarında benzersizliği garanti eder.
Tipik bir API biçimi, base64 kodlu bir dize olarak imleçtir. Sunucu imleci çözer, değeri çıkarır ve SQL sorgusunu oluşturur. Base64 kodlaması, imlecin iç yapısını istemciden gizler ve geriye dönük uyumluluğu bozmadan biçim değişikliğine izin verir. İstemci, yanıtın endCursor alanında imleçleri alır ve sonraki istekte dize olarak iletir.
Imleç sayfalama, çift yönlü gezinmeyi destekler. İleri hareket (next) için geçerli sayfadaki son öğenin imleci kullanılır; geri (previous) için ilk öğenin imleci kullanılır. İstekteki after ve before parametreleri yönü belirler: after imleçten sonraki kayıtları alır, before imleçten önceki kayıtları alır.
Imleç ve Offset sayfalama arasındaki seçim, bir API tasarlarken önemli mimari kararlardan biridir. Her yöntemin uygulanabilirliğini belirleyen güçlü ve zayıf yönleri vardır. Cursor pagination dinamik verili senaryolarda kazanır; Offset isteğe bağlı gezinme senaryolarında kazanır.
| Özellik | Cursor | Offset |
|---|---|---|
| Eklemede kararlılık | Yüksek (yinelenen yok) | Düşük (sayfa kayması) |
| Büyük kümelerde performans | O(log n) — kararlı | O(n) — büyümeyle düşer |
| Sayfa numarasına göre gezinme | Hayır | Evet (page=5) |
| Uygulama karmaşıklığı | Orta | Düşük |
| REST desteği | cursor/before/after | page/offset |
| GraphQL desteği | Relay standardı | Önerilmez |
Offset sayfalama, OFFSET konumuna kadar tam tablo taraması yapar. offset=100000'de, LIMIT 20 olsa bile veritabanı 100000 satırı okur ve atlar. MySQL ve PostgreSQL OFFSET'i optimize edemez — bu, SQL'de LIMIT/OFFSET uygulamasının bir özelliğidir. Imleç sayfalama, konumu O(log n)'de bulan bir B-tree indeksi kullanır.
Offset'in ek bir sorunu, geriye doğru sayfalarken kayıtları “atlamasıdır”. Bir kullanıcı 5. sayfayı yüklediyse ve o anda yeni kayıtlar eklendiyse, 6. sayfayı istediğinde 5. sayfadaki kaydı tekrar görecek veya yenilerini kaçıracaktır. Cursor pagination bu senaryoyu tamamen ortadan kaldırır: imleç kümede belirli bir yeri işaret eder ve eklemeler konumu değiştirmez.
Imleç sayfalama uygulamasını backend (Kotlin + Spring) ve istemcide (Android + Retrofit) inceleyelim. Sunucu after, before, limit parametrelerini kabul eder ve imleçler ve pageInfo ile birlikte kayıt listesi döndürür. Tipik bir yanıt, sayfalama arayüzünü yönetmek için hasNextPage ve hasPreviousPage içerir.
@GetMapping("/posts")
fun getPosts(
@RequestParam after: Long?,
@RequestParam(defaultValue = "20") limit: Int
): CursorResponse<Post> {
val cursor = after ?: Long.MAX_VALUE
val posts = repository.findByIdLessThanOrderByIdDesc(
cursor, PageRequest.of(0, limit)
)
val endCursor = posts.lastOrNull()?.id
return CursorResponse(
data = posts,
pageInfo = PageInfo(
hasNextPage = posts.size == limit,
endCursor = endCursor
)
)
}
İstemcide, imleç sayfalama Paging 3'ten PagingSource aracılığıyla uygulanır ve anahtar bir imleçtir (Long). PagingSource.load, LoadParams.key'i alır — son yüklenen kaydın imleci. LoadResult.Page verileri ve nextKey'i — sonraki sayfanın imleci — döndürür. nextKey = null olduğunda sayfalama tamamlanır.
// Retrofit API
interface PostApi {
@GET("posts")
suspend fun getPosts(
@Query("after") after: Long?,
@Query("limit") limit: Int = 20
): CursorResponse<Post>
}
// Imleç anahtarıyla PagingSource
class PostPagingSource(
private val api: PostApi
) : PagingSource<Long, Post>() {
override suspend fun load(
params: LoadParams<Long>
): LoadResult<Long, Post> = try {
val response = api.getPosts(
after = params.key,
limit = params.loadSize
)
val nextKey = response.pageInfo.endCursor
LoadResult.Page(
data = response.data,
prevKey = null,
nextKey = nextKey
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
GraphQL'de, imleç sayfalama Relay Connection deseni aracılığıyla uygulanır. Her türün bir Connection (pageInfo ve edges ile) ve bir Edge (node + cursor) vardır. Sorgu, first, after, last, before parametrelerini iletir. Sunucu imleçlerle birlikte edges dizisi ve hasNextPage/hasPreviousPage ile pageInfo döndürür.
Imleç sayfalama, kayıtların sık sık eklendiği veya silindiği dinamik verilerle çalışan API'ler için önerilir. Klasik örnekler: sosyal ağdaki haber akışı, sohbet mesajları, işlem geçmişi, gönderi yorumları. Tüm bu senaryolarda, tutarlılık ve yinelenen olmaması önemlidir.
Offset sayfalamanın daha uygun olduğu senaryolar vardır: sayfa numarasına göre gezinme gerektiren yönetim panelleri; sonuçların değişebileceği sayfalamalı arama; 5. sayfaya sabit bağlantı gerektiren raporlar ve analitik. Bu durumlarda, imlecin avantajları uygulama karmaşıklığını aşmaz.
Imleç sayfalama, rastgele bir sayfaya “atlamayı” desteklemez — kullanıcı “Sayfa 5”e tıklayıp oraya gidemez. Bu bir mimari sınırlamadır: toplam sayfa sayısını saymak ayrı bir COUNT sorgusu gerektirir ve büyük tablolar için pahalı olabilir. Bu gibi durumlarda hibrit bir yaklaşım: veriler için imleç + sayfalama için count.
Sıkça Sorulan Sorular
Imleç, veri kümesinde bir konumu işaret eden benzersiz bir kayıt tanımlayıcısıdır. Basit (bir kayıt ID'si) veya bileşik (birden çok alan) olabilir. İstemci, sayfadaki son kaydın imlecini alır ve sonraki grubu almak için sonraki istekte iletir.
Cursor pagination, yeni kayıtlar eklendiğinde kaymaya maruz kalmaz — her öğe tam olarak bir sayfaya düşer. Ayrıca ilk n satırı taramak yerine indeksleri kullanarak büyük hacimlerde hızı korur. Offset daha basittir ancak dinamik veriler için kararsızdır.
Evet, imleç sayfalama GraphQL'e bağlı değildir. Herhangi bir REST API'de imleci sorgu parametresi ?after=83&limit=20 olarak ileterek uygulanabilir. Yanıt, endCursor ve hasNextPage ile pageInfo içermelidir — bu, istemcinin iç imleç yapısını bilmeden yüklemeyi yönetmesini sağlar.
Otomatik artan ID en uygun seçimdir: tekdüze artar, değişmez, verimli bir şekilde indekslenir. UUID v7 (zaman sıralı) da uygundur. Zaman damgaları aynı anda yinelenenler üretebilir, bu nedenle ID ile birleştirin: (created_at, id) imleç benzersizliğini garanti etmek için.
Imleç sayfalama toplam sayfa sayısını sağlamaz — bu onun sınırlamasıdır. Toplam bilgiye ihtiyacınız varsa, aynı filtrelerle ayrı bir COUNT sorgusu çalıştırın. Büyük tablolar için EXPLAIN aracılığıyla yaklaşık sayma veya analitikten önbelleğe alınmış toplam kullanın.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun