- Katılım
- 21 May 2023
- Mesajlar
- 496
- Tepki
- 17
- Puan
- 18
Bir liste endpoint’inde ilk sayfa doğru görünüp bütün kayıtlar çekildiğinde sayı tutmuyorsa sorun her zaman veritabanında değildir. İki istek arasında koleksiyon değişmiş, sıralama yeterince belirgin kurulmamış ya da istemci sunucunun verdiği devam bilgisini yanlış yorumlamış olabilir.
Özellikle arka planda yeni sipariş, mesaj veya olay eklenen sistemlerde “ikinci sayfa, birincinin devamıdır” varsayımı kendiliğinden doğru olmaz. Devam noktasının neye göre belirlendiğini bilmek gerekir.
Aşağıdaki küçük örnek Python 3.12.3 ile çalıştırıldı. Kimlikleri büyükten küçüğe sıralanmış bir API listesi gibi düşünüyoruz; sayfa boyutu iki:
Gerçek çıktı:
İlk istekten sonra listenin başına
Silme ters yönde hata üretir. Aynı ilk sayfadan sonra
Ancak canlı akışta üç maliyeti vardır:
Bu nedenle yalnız
Cursor yaklaşımı “son gördüğüm kayıttan devam et” der. Fakat yalnız
Pratikte toplam sıralama, benzersiz bir bağlayıcıyla kurulur:
İlk sayfanın son kaydı
Buradaki karşılaştırma azalan sıra içindir; artan sırada yön değişir. Asıl kural, cursor içinde sıralamada kullanılan bütün alanların bulunması ve son bağlayıcının sonucu benzersiz yapmasıdır.
Cursor yeni eklemenin eski sayfalardaki konumları itmesi sorununu azaltır; koleksiyonu dondurmaz. İlk istekten sonra cursor’ın gerisine yeni bir kayıt eklenirse o kayıt taramaya girebilir. Bir kaydın sıralama alanı değişirse daha önce görülen bölgeden görülmeyen bölgeye taşınabilir.
İstenen sözleşme baştan açık olmalıdır:
“Cursor kullanıyoruz, dolayısıyla hiçbir kayıt kaybolmaz” demek bu ayrım yapılmadan fazla güçlü bir vaattir. Veri kaynağı eventual consistency sunuyorsa bunun da API sözleşmesinde belirtilmesi gerekir.
Google’ın AIP-158 sayfalama kılavuzu, devam token’ının opak ve URL için güvenli olmasını ister. Yalnızca okunabilir JSON’u Base64’e çevirmek yeterli opaklık sayılmaz. İstemci token’ı çözmeye, içindeki offset’i artırmaya veya parçalarını değiştirmeye çalışmamalıdır.
Token sunucunun devam konumunu taşır; yetkilendirme bileti değildir. Her sonraki istekte kullanıcı ve kaynak yetkileri yeniden kontrol edilir. Filtre, sıralama, tenant/parent gibi ilk çağrının diğer parametreleri de aynı kalmalıdır. AIP-158’e göre servis, koleksiyonun sonunda olmadığı hâlde istenenden az, hatta sıfır sonuç döndürebilir; bitişin güvenilir işareti boş
AIP-132 List yöntemi de istek tarafında
Her servis aynı alan adını kullanmaz. Bu yüzden genel bir istemci, “dönen kayıt sayısı limitten azsa bittik” varsayımı kurmamalıdır.
Ortak fikir basit: Sunucunun verdiği devam işaretini aynen kullanın, ilk isteğin filtre ve sırasını koruyun, bitişi o API’nin açık sinyalinden anlayın.
Toplam sonuç sayısı canlı koleksiyonda doğal olarak değişebilir; bazı API’lerde tahmin de olabilir. Daha sağlam denetim için her turda şu bilgileri tutun:
Aynı test veri setinde üç olayı özellikle deneyin: ilk sayfadan sonra başa kayıt ekleme, ilk sayfadaki kaydı silme ve sıralama alanını değiştirme. Offset ile cursor’ın davranışını ayrı ayrı kaydedin. “On bin kayıt geldi” yerine hangi kimlik aralığının atlandığını göstermek, sunucu ve istemci ekiplerinin aynı hatayı konuşmasını sağlar.
API sözleşmesi, hata yönetimi ve rate limit tarafını birlikte kurmak için API entegrasyonu rehberine bakabilirsiniz. Temel kavramlar için web uygulamalarında API entegrasyonları, servis katmanını daha geniş çerçevede görmek için web servisleri konusu tamamlayıcıdır.
Sizde tekrar eden ilk kimlik ile kaybolduğunu düşündüğünüz aralığı; kullanılan filtre, sıra ve devam yöntemiyle birlikte paylaşırsanız sorun offset kayması mı, kararsız sıralama mı, yoksa API’nin consistency sözleşmesi mi daha hızlı ayrılır.
Özellikle arka planda yeni sipariş, mesaj veya olay eklenen sistemlerde “ikinci sayfa, birincinin devamıdır” varsayımı kendiliğinden doğru olmaz. Devam noktasının neye göre belirlendiğini bilmek gerekir.
Beş kimlikle hatayı görünür yapalım
Aşağıdaki küçük örnek Python 3.12.3 ile çalıştırıldı. Kimlikleri büyükten küçüğe sıralanmış bir API listesi gibi düşünüyoruz; sayfa boyutu iki:
Kod:
kayitlar = [50, 40, 30, 20, 10]
birinci = kayitlar[:2]
kayitlar.insert(0, 60)
ikinci_offset = kayitlar[2:4]
cursor = birinci[-1]
ikinci_cursor = [x for x in kayitlar if x < cursor][:2]
print("ilk sayfa:", birinci)
print("60 eklendikten sonra offset=2:", ikinci_offset)
print("cursor=40 sonrasında:", ikinci_cursor)
Gerçek çıktı:
Kod:
ilk sayfa: [50, 40]
60 eklendikten sonra offset=2: [40, 30]
cursor=40 sonrasında: [30, 20]
İlk istekten sonra listenin başına
60 geldi. offset=2 hâlâ “ilk iki konumu atla” dediği için daha önce alınmış 40 yeniden döndü. Cursor sorgusu ise “40’tan küçük ilk iki kimlik” dedi ve 30, 20 ile devam etti.Silme ters yönde hata üretir. Aynı ilk sayfadan sonra
50 silinirse yeni liste 40, 30, 20, 10 olur. offset=2 bu kez 20, 10 döndürür; henüz alınmamış 30 arada kaybolur.Kısa teşhis' Alıntı:Offset kayıtları değil, o anki listedeki konumları sayar. Liste iki çağrı arasında kayarsa konum da kayar.
Offset yanlış değil, hareketli listede kırılgan
?limit=20&offset=40 biçimi anlaşılırdır, sayfa numarasına çevrilmesi kolaydır ve küçük, nadiren değişen yönetim listelerinde yeterli olabilir. Kullanıcı “7. sayfaya git” dediğinde doğrudan atlama olanağı da verir.Ancak canlı akışta üç maliyeti vardır:
- Konum kayması: Ön tarafa ekleme veya silme olduğunda tekrar ve eksik kayıt görülebilir.
- Kararsız sıra: Açık bir
ORDER BYyoksa aynı sorgunun satır sırası garanti edilmez. Yalnızca eşit değerler üreten bir alana göre sıralama da toplam bir düzen kurmaz. - Derin sayfa maliyeti: Büyük offset değerlerinde sunucu atlayacağı satırları yine bulmak zorunda kalabilir.
Bu nedenle yalnız
page=3 değerini loglamak teşhis için yetmez. Filtre, sıralama, sayfa boyutu, isteğin zamanı ve o sırada dönen ilk-son kimlik de kaydedilmelidir.Cursor için benzersiz ve kararlı bir sıra gerekir
Cursor yaklaşımı “son gördüğüm kayıttan devam et” der. Fakat yalnız
created_at kullanılırsa aynı saniyede oluşturulan iki kayıt eşit kalabilir. Sunucu bunların yerini çağrılar arasında değiştirdiğinde yine atlama yaşanabilir.Pratikte toplam sıralama, benzersiz bir bağlayıcıyla kurulur:
Kod:
ORDER BY created_at DESC, id DESC
İlk sayfanın son kaydı
(2026-08-16T10:00:00Z, 40) ise devam sorgusunun mantığı şöyledir:
Kod:
WHERE (created_at, id) < (:last_created_at, :last_id)
ORDER BY created_at DESC, id DESC
LIMIT 20
Buradaki karşılaştırma azalan sıra içindir; artan sırada yön değişir. Asıl kural, cursor içinde sıralamada kullanılan bütün alanların bulunması ve son bağlayıcının sonucu benzersiz yapmasıdır.
updated_at gibi değişebilen bir alanla sayfalama yapılabilir, ancak kayıt güncellendiğinde listenin başka yerine taşınır. Tarama boyunca mümkün olduğunca değişmez bir sıra anahtarı kullanmak bu hareketi azaltır.Cursor da tek başına anlık görüntü garantisi değildir
Cursor yeni eklemenin eski sayfalardaki konumları itmesi sorununu azaltır; koleksiyonu dondurmaz. İlk istekten sonra cursor’ın gerisine yeni bir kayıt eklenirse o kayıt taramaya girebilir. Bir kaydın sıralama alanı değişirse daha önce görülen bölgeden görülmeyen bölgeye taşınabilir.
İstenen sözleşme baştan açık olmalıdır:
- Canlı tarama: İstek anlarındaki güncel liste izlenir. Yeni kayıtların bu turda görülmesi veya sonraki tura kalması belgelenir; istemci kimliğe göre tekrarları ayrıca sayabilir.
- Başlangıç anının tam envanteri: Sunucunun bir snapshot ya da
as_ofsınırı tutması ve devam token’ını buna bağlaması gerekir.
“Cursor kullanıyoruz, dolayısıyla hiçbir kayıt kaybolmaz” demek bu ayrım yapılmadan fazla güçlü bir vaattir. Veri kaynağı eventual consistency sunuyorsa bunun da API sözleşmesinde belirtilmesi gerekir.
Page token’ı istemciye ait bir veri yapısı sanmayın
Google’ın AIP-158 sayfalama kılavuzu, devam token’ının opak ve URL için güvenli olmasını ister. Yalnızca okunabilir JSON’u Base64’e çevirmek yeterli opaklık sayılmaz. İstemci token’ı çözmeye, içindeki offset’i artırmaya veya parçalarını değiştirmeye çalışmamalıdır.
Token sunucunun devam konumunu taşır; yetkilendirme bileti değildir. Her sonraki istekte kullanıcı ve kaynak yetkileri yeniden kontrol edilir. Filtre, sıralama, tenant/parent gibi ilk çağrının diğer parametreleri de aynı kalmalıdır. AIP-158’e göre servis, koleksiyonun sonunda olmadığı hâlde istenenden az, hatta sıfır sonuç döndürebilir; bitişin güvenilir işareti boş
next_page_token değeridir.AIP-132 List yöntemi de istek tarafında
page_size ve page_token, yanıt tarafında next_page_token düzenini tanımlar. API üreticisi token’a şema sürümü, son sıralama tuple’ı, filtre özeti ve gerekiyorsa snapshot kimliği koyabilir; bunlar sunucunun uygulama ayrıntısı olarak kalır.Resmî API’lerde devam işaretini izleyin
Her servis aynı alan adını kullanmaz. Bu yüzden genel bir istemci, “dönen kayıt sayısı limitten azsa bittik” varsayımı kurmamalıdır.
- GitHub REST: Resmî sayfalama belgesi, yanıttaki
Linkbaşlığında verilenrel="next"URL’sinin izlenmesini anlatır. GitHub’ın iyi uygulamalar belgesi de istemcinin sonraki sayfa adresini elle üretmemesini önerir. - Stripe v1: Liste yöntemleri mevcut nesne kimliğini alan
starting_afterveyaending_beforecursor’larını vehas_moresonucunu kullanır. İki yön parametresi aynı istekte birlikte kullanılmaz. - Stripe v2: v2 genel bakışı, sonraki sayfa için sunucunun verdiği
next_page_urldeğerini kullanır ve ilk çağrıdan sonra filtrelerin değiştirilemeyeceğini belirtir.
Ortak fikir basit: Sunucunun verdiği devam işaretini aynen kullanın, ilk isteğin filtre ve sırasını koruyun, bitişi o API’nin açık sinyalinden anlayın.
Eksik kaydı kanıtlamak için toplam sayı yetmez
Toplam sonuç sayısı canlı koleksiyonda doğal olarak değişebilir; bazı API’lerde tahmin de olabilir. Daha sağlam denetim için her turda şu bilgileri tutun:
- İstek kimliği, zaman, filtre ve sıralamanın normalize edilmiş hâli
- Her sayfanın ilk ve son kayıt kimliği
- Sunucudan gelen next token veya next URL’nin özeti; gizli token’ın tamamını herkese açık loga koymadan
- Görülen benzersiz kimlik sayısı ve tekrar sayısı
- Bitiş sinyali ile son yanıtın durumu
Aynı test veri setinde üç olayı özellikle deneyin: ilk sayfadan sonra başa kayıt ekleme, ilk sayfadaki kaydı silme ve sıralama alanını değiştirme. Offset ile cursor’ın davranışını ayrı ayrı kaydedin. “On bin kayıt geldi” yerine hangi kimlik aralığının atlandığını göstermek, sunucu ve istemci ekiplerinin aynı hatayı konuşmasını sağlar.
API sözleşmesi, hata yönetimi ve rate limit tarafını birlikte kurmak için API entegrasyonu rehberine bakabilirsiniz. Temel kavramlar için web uygulamalarında API entegrasyonları, servis katmanını daha geniş çerçevede görmek için web servisleri konusu tamamlayıcıdır.
Sizde tekrar eden ilk kimlik ile kaybolduğunu düşündüğünüz aralığı; kullanılan filtre, sıra ve devam yöntemiyle birlikte paylaşırsanız sorun offset kayması mı, kararsız sıralama mı, yoksa API’nin consistency sözleşmesi mi daha hızlı ayrılır.
Dijital Dünyanıza Yön Veren Pusula