İmleç sayfalandırma, veri değiştiğinde listelerin sabit kalmasını sağlar. Offset sayfalandırmanın ekleme ve silmelerde neden bozulduğunu ve temiz imleçlerin nasıl uygulanacağını öğrenin.

Bir akışı açarsınız, biraz kaydırırsınız ve her şey normal görünür — ta ki normal olmaz. Aynı öğeyi iki kez görürsünüz. Orada olduğuna emin olduğunuz bir şey kaybolmuştur. Dokunmak üzere olduğunuz satır yer değiştirir ve yanlış detay sayfasına ulaşırsınız.
Bunlar, API yanıtlarınız tek başına “doğru” görünse bile kullanıcıya açık hatalardır. Yaygın semptomlar kolayca fark edilir:
Bu mobilde daha kötü olur. İnsanlar duraklar, uygulamalar arasında geçiş yapar, bağlantı kaybeder, sonra devam eder. Bu süre içinde yeni öğeler gelir, eski olanlar silinir ve bazıları düzenlenir. Uygulamanız “sayfa 3” demeye devam ederse ve offset kullanıyorsa, kullanıcı kaydırırken sayfa sınırları kayabilir. Sonuç, kararsız ve güvenilmez görünen bir akıştır.
Amaç basit: kullanıcı ileri doğru kaydırmaya başladığında, liste bir anlık görüntü gibi davranmalı. Yeni öğeler var olabilir, ama kullanıcının halihazırda sayfalandığı şeyi yeniden düzenlememeliler. Kullanıcı düz, öngörülebilir bir sıra almalıdır.
Hiçbir sayfalandırma yöntemi kusursuz değildir. Gerçek sistemlerde eşzamanlı yazmalar, düzenlemeler ve birden fazla sıralama seçeneği olur. Ancak imleç tabanlı sayfalandırma, hareketli bir satır sayısından ziyade kararlı bir sıradaki konumdan sayfalandığı için genellikle offset'ten daha güvenlidir.
Offset sayfalandırma, listeyi sayfa sayısına göre değil "N kadar atla, M al" şeklinde sayfalandırır. API'ye kaç satır atlayacağını (offset) ve kaç satır döndüreceğini (limit) söylersiniz. limit=20 ile sayfa başına 20 öğe alırsınız.
Kavramsal olarak:
GET /items?limit=20&offset=0 (ilk sayfa)GET /items?limit=20&offset=20 (ikinci sayfa)GET /items?limit=20&offset=40 (üçüncü sayfa)Yanıt genellikle öğeleri ve bir sonraki sayfayı istemek için yeterli bilgiyi içerir.
{
"items": [
{"id": 101, "title": "..."},
{"id": 100, "title": "..."}
],
"limit": 20,
"offset": 20,
"total": 523
}
Tablolara, yönetici listelerine, arama sonuçlarına ve basit akışlara güzel uyduğu için popülerdir. SQL'de LIMIT ve OFFSET kullanarak uygulaması da kolaydır.
Yakalama gereken nokta, gizli varsayımdır: veri seti kullanıcı sayfalandırırken sabit kalır. Gerçek uygulamalarda yeni satırlar eklenir, satırlar silinir ve sıralama anahtarları değişir. İşte "gizemli hatalar" bu noktada başlar.
Offset sayfalandırma, istekler arasında listenin sabit kaldığını varsayar. Ancak gerçek listeler hareket eder. Liste kaydığında, “20 atla” gibi bir offset artık aynı öğelere işaret etmez.
Örneğin created_at desc (yeniden eskiye) sıralanan bir akış hayal edin, sayfa boyutu 3.
offset=0, limit=3 ile 1. sayfayı yüklersiniz ve [A, B, C] elde edersiniz.
Şimdi yeni bir öğe X oluşturulur ve en üste gelir. Liste artık [X, A, B, C, D, E, F, ...] olur. offset=3, limit=3 ile 2. sayfayı yüklersiniz. Sunucu [X, A, B] öğelerini atlar ve [C, D, E] döndürür.
Böylece C'yi tekrar görmüş olursunuz (yinelenme) ve ileride bir öğeyi kaçırırsınız çünkü her şey aşağı kaydı.
Silinmeler tersine çalışır. Başlangıçta [A, B, C, D, E, F, ...] olsun. 1. sayfayı alırsınız: [A, B, C]. 2. sayfa gelmeden önce B silinirse, liste [A, C, D, E, F, ...] olur. offset=3 olan 2. sayfa [A, C, D] öğelerini atlar ve [E, F, G] döndürür. D hiç alınmayan bir boşluk haline gelir.
Yeni gelen öğelerin hep en üste bulunduğu newest-first akışlarda, eklemeler tam da sonraki offsetleri kaydırır.
“Karlı liste” kullanıcıların beklediğidir: ileri kaydırırken öğeler zıplamaz, tekrarlanmaz veya sebepsizce yok olmaz. Bu, zamanı dondurmaktan çok, sayfalandırmayı öngörülebilir kılmakla ilgilidir.
İki fikir sıklıkla karıştırılır:
created_at ve bir tie-breaker olarak id) böylece aynı girdilerle iki istek aynı sırayı döndürür.Yenileme ve ileri kaydırma farklı eylemlerdir. Yenileme “şimdi ne yeni?” demektir, bu yüzden en üst değişebilir. İleri kaydırma ise “nerede kalmıştım, devam et” demektir; bu yüzden kaydırma sınırlarını kaydıran tekrarlar veya beklenmeyen boşluklar olmamalıdır.
Çoğu sayfalandırma hatasını önleyen basit bir kural: ileri kaydırma asla tekrar göstermemelidir.
İmleç sayfalandırma, liste içinde sayfa numarası yerine bir yer imi ile ilerler. “3. sayfayı ver” demek yerine istemci “buradan devam et” der.
Sözleşme basittir:
Bu, imleç sayfalandırmayı insert ve delete durumlarına karşı daha toleranslı yapar çünkü imleç, satır sayısına değil sıralamadaki bir konuma sabitlenir.
Zorunlu gereksinim deterministik bir sıralama kuralıdır. Stabil bir sıralama kuralı ve tutarlı bir tie-breaker gerekir; aksi halde imleç güvenilir bir yer imi olmaz.
İnsanların listeyi nasıl okuduğuna uyan bir sıralama seçin. Akışlar, mesajlar ve aktivite günlükleri genellikle en yeni ilk (newest-first) olur. Fatura veya denetim kayıtları gibi geçmişler genelde en eski ilk daha kolaydır.
Bir imleç, o sıradaki bir pozisyonu benzersiz şekilde tanımlamalıdır. İki öğe aynı imleç değerini paylaşabiliyorsa, sonunda tekrarlar veya boşluklar alırsınız.
Yaygın seçenekler ve dikkat edilecekler:
created_at: basit ama aynı zaman damgasını paylaşan çok satır varsa güvensizdir.id: monotonik ID'ler varsa güvenlidir ama ürün tarafındaki istediğiniz sıralamayı vermeyebilir.created_at + id: genellikle en iyi karışımdır (zaman damgası ürün sırasını verir, id tie-breaker olur).updated_at: sonsuz kaydırma için risklidir çünkü düzenlemeler öğeleri sayfalar arasında hareket ettirebilir.Birden fazla sıralama seçeneği sunuyorsanız, her sıralama modu kendi imleç kurallarıyla ayrı bir liste gibi ele alınmalıdır. Bir imleç yalnızca tam olarak tek bir sıralama için anlamlıdır.
API yüzeyini küçük tutabilirsiniz: iki girdi, iki çıktı.
İstediğiniz öğe sayısını (limit) ve isteğe bağlı cursoru gönderin (nereden devam etmek istediğiniz). Eğer imleç yoksa sunucu ilk sayfayı döndürür.
Örnek istek:
GET /api/messages?limit=30&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0xNlQxMDowMDowMFoiLCJpZCI6Ijk4NzYifQ==
Öğeleri ve bir next_cursor döndürün. Eğer sonraki sayfa yoksa next_cursor: null döndürün. İstemciler imleci bir token olarak ele almalı, düzenlememelidir.
{
"items": [ {"id":"9876","created_at":"2026-01-16T10:00:00Z","subject":"..."} ],
"next_cursor": "...",
"has_more": true
}
Sunucu tarafı mantığı basitçe: kararlı bir sırada sırala, imleci kullanarak filtrele, sonra limiti uygula.
Eğer (created_at DESC, id DESC) ile en yeni ilk sıralıyorsanız, imleci (created_at, id) olarak decode edin, sonra (created_at, id) çiftinden daha küçük olan satırları alın, aynı sıralamayı uygulayın ve limit kadar alın.
İmleci base64 JSON blob olarak kodlayabilirsiniz (kolay) veya imzalı/şifreli bir token yapabilirsiniz (daha çok iş). OPAK yaklaşım daha güvenlidir çünkü iç yapıyı daha sonra değiştirme esnekliği verir.
Ayrıca makul varsayılanlar belirleyin: mobil için genelde 20–30 arası, web için genelde 50, ve sunucuda tek bir hatalı istemcinin 10.000 satır isteyememesi için sert bir üst sınır koyun.
Kararlı bir akış esas olarak bir vaatle ilgilidir: kullanıcı ileri kaydırmaya başladıktan sonra, başkalarının kaydettiği yeni/silinecek/düzenlenecek kayıtlar kullanıcının zaten dolaştığı öğeleri zıplatmamalıdır.
İmleç sayfalandırmada eklemeler en kolay olandır. Yeni kayıtlar yenilemede görünmeli, zaten yüklenmiş sayfaların ortasında belirmemelidir. Eğer (created_at DESC, id DESC) ile sıralıyorsanız, yeni öğeler doğal olarak ilk sayfadan önce yer alır; bu yüzden mevcut imleciniz daha eski öğelere devam eder.
Silinmeler listeyi yeniden düzenlememelidir. Bir öğe silindiyse, o öğe sadece gelecekteki sorgularda dönmez. Sayfa boyutunu sabit tutmak istiyorsanız, görünür limit kadar öğe toplayana kadar çekmeye devam edebilirsiniz.
Düzenlemeler takımdan ekipman kazandıran hataların tekrar ortaya çıkmasına neden olur. Temel soru: bir düzenleme sıralama pozisyonunu değiştirebilir mi?
Kaydırma listeleri için genelde snapshot tarzı davranış en iyisidir: created_at gibi değişmez bir anahtarla sayfalandırın. Düzenlemeler içeriği değiştirebilir ama öğe yeni bir pozisyona sıçramaz.
Canlı akış davranışı edited_at gibi bir alana göre sıralarsa öğeler sıçrayabilir (eski bir öğe düzenlenip en üste gelebilir). Bunu seçerseniz, listeyi sürekli değişen olarak tasarlayın ve UX’i yenilemeye göre kurgulayın.
İmleci “bu kesin satırı bul”a dayandırmayın. Pozisyonu {created_at, id} gibi değerlerle kodlayın. Sonra sonraki sorgu değerlere dayansın:
WHERE (created_at, id) < (:created_at, :id)id) dahil edin ki tekrarlar olmasınİleri sayfalandırma kolay kısımdır. Daha zor UX soruları geri sayfalandırma, yenileme ve rastgele erişimdir.
Geri sayfalandırma için iki yaklaşım işe yarar:
next_cursor daha eski öğeler için, prev_cursor daha yeni öğeler için) ve ekranda tek bir sıralama tutun.Rastgele atlama imleçlerle daha zordur çünkü “20. sayfa” veri değiştiğinde sabit bir anlam taşımaz. Gerçekten atlama gerekiyorsa, bir sayfa indeksine atlamak yerine “bu zaman damgası etrafı” veya “bu mesaj id'sinden başla” gibi bir demir noktasına atlayın.
Mobilde önbellekleme önemlidir. İmleçleri her liste durumu (sorgu + filtreler + sıralama) için saklayın ve her sekme/ görüntüyü kendi listesi olarak ele alın. Bu, “sekme değiştirince her şey karıştı” davranışını önler.
Çoğu imleç sayfalandırma sorunu veritabanından değil, istekler arasındaki küçük tutarsızlıklardan kaynaklanır ve ancak gerçek trafikle görünür.
En büyük suçlular:
created_at), bağlar tekrarlar veya eksik öğelere yol açar.next_cursor'ın aslında son döndürülen öğeyle eşleşmemesi.Koder.ai gibi platformlarda uygulama geliştirirken bu uç durumlar hızlıca ortaya çıkar çünkü web ve mobil istemciler genellikle aynı uç noktayı paylaşır. Tek bir açık imleç sözleşmesi ve deterministik bir sıralama kuralı hem istemcileri uyumlu tutar hem de sürprizleri azaltır.
Sayfalandırmayı “tamamlandı” demeden önce eklemeler, silmeler ve yeniden denemeler altında davranışı doğrulayın.
next_cursor gerçekten döndürülen son satırdan alınıyorlimit için güvenli bir maksimum ve belgelenmiş bir varsayılan varYenileme için net bir kural seçin: kullanıcı yenileme yapmak için çekiş hareketiyle en üsteki yeni öğeleri getirir veya düzenli olarak “ilk öğemden daha yeni bir şey var mı?” diye kontrol edip bir “Yeni öğeler” butonu gösterirsiniz. Tutarlılık, listeyi hayalet değil kararlı hissettiren şeydir.
Bir destek gelen kutusu hayal edin: ajanlar webde kullanıyor, yönetici mobilde aynı gelen kutusunu kontrol ediyor. Liste en yeni ilk olarak sıralanmış. Beklenen tek şey: ileri kaydırırken öğeler zıplamaz, tekrar etmez veya kaybolmaz.
Offset ile bir ajan 1. sayfayı (1–20) alır, sonra offset=20 ile 2. sayfaya kaydırır. Okurken iki yeni mesaj en üste gelir. Artık offset=20 kısa süre önce işaret ettiği yere değil farklı bir yere işaret eder. Kullanıcı tekrarlar görür veya mesajları kaçırır.
İmleç sayfalandırma ile uygulama “bu imleçten sonraki 20 öğeyi” ister; imleç, kullanıcının gerçekten gördüğü son öğeye dayalıdır (genelde (created_at, id)). Yeni mesajlar her an gelebilir ama sonraki sayfa yine kullanıcının son gördüğü mesajın hemen ardından başlar.
Yayına almadan önce basit bir test yöntemi:
Hızlı prototip için, Koder.ai uç nokta ve istemci akışlarını sohbet isteminden iskeletleyip Planning Mode, snapshot ve rollback ile güvenle yineleyerek sayfalandırma değişikliği testi yapmanıza yardımcı olabilir.
Offset sayfalandırma “N satırı atla” mantığına dayanır; yeni satırlar eklendiğinde veya eski satırlar silindiğinde satır sayısı kayar. Aynı offset kısa süre içinde farklı öğelere işaret edebilir; bu da kullanıcıların kaydırma sırasında tekrarlar ve boşluklar görmesine yol açar.
İmleç sayfalandırma, “son gördüğüm öğeden sonra buradan devam et” şeklinde bir yer imi kullanır. Bir sonraki istek bu konumdan deterministik bir sırada devam eder; böylece en üste eklenen kayıtlar veya ortadaki silmeler offset'lerde olduğu gibi sayfa sınırını hareket ettirmez.
Tie-breaker içeren deterministik bir sıralama kullanın; çoğunlukla en iyi seçim (created_at, id) ikilisidir. created_at ürün açısından mantıklı bir sıra verir, id ise aynı zaman damgasına sahip satırları benzersizleştirir ve tekrar/atlamayı önler.
updated_at ile sıralama, öğeler düzenlendiğinde sayfaların yer değiştirmesine neden olabilir ve “ileriye sabit kaydırma” beklentisini bozar. Eğer gerçekten canlı olarak “en son güncellenenler” görünümü gerekiyorsa, UX’i yenileme üzerine kurun ve yeniden sıralamayı kabul edin.
Bir next_cursor olarak opak bir token döndürün ve istemcinin onu değiştirmeden geri göndermesini isteyin. Basit bir yaklaşım, son öğenin (created_at, id) ikilisini base64 JSON olarak kodlamaktır; fakat önemli olan, istemciye tokenı opak bir değer olarak ele almaktır ki iç yapıyı daha sonra değiştirebilesiniz.
İmlecin tanımladığı konumu satır bazlı olarak aramaktan kaçının; bunun yerine {created_at, id} gibi değerleri saklayın. Son öğe silinmiş olsa bile bu değerler pozisyonu tanımlar, böylece aynı sırada strictly less than (veya uygun yön) filtresiyle güvenle devam edebilirsiniz.
Sıkı (< veya >) karşılaştırma kullanın, benzersiz bir tie-breaker ekleyin ve next_cursor'ı gerçekten döndürülen son öğeden alın. Çoğu tekrar hatası <= kullanmaktan, tie-breaker atmaktan veya next_cursor'ı yanlış satırdan üretmekten kaynaklanır.
Açık bir kural belirleyin: yenileme en üstteki yeni öğeleri getirir; kaydırarak ileri gitme ise mevcut imleçten daha eski öğelere devam eder. Yenileme mantığını aynı imleç akışına karıştırmayın; aksi halde kullanıcılar yeniden sıralama görür ve liste güvenilmez görünür.
İmleç yalnızca tam olarak aynı sıralama ve filtre seti için geçerlidir. İstemci sıralama modunu, aramayı veya filtreleri değiştirirse yeni bir sayfalandırma oturumu başlatmalı ve imleçleri her liste durumu için ayrı saklamalıdır.
İmleç sayfalandırma ardışık gezinme için uygundur, ama “20. sayfaya atla” gibi sabit atlamalar için veri değiştiğinde stabil bir anlamı yoktur. Gerçekten atlama gerekiyorsa, “bu zaman damgası etrafı” veya “bu id'den sonra başla” gibi bir demir noktasına atlayın, sonra oradan imleçlerle sayfalandırın.
Her istek aynı filtreleri ve sıralama alanlarını tekrar etmelidir; aksi halde istemciler farklı sınırlar alır. Ayrıca next_cursor son döndürülen satırdan alınmalı, limit için güvenli bir üst sınır ve belgelenmiş bir varsayılan belirlemelisiniz.