Her liste bitiş noktası eninde sonunda aynı soruyla karşılaşır: 2 milyon siparişi bir istemcinin adım adım gezebileceği sayfalara nasıl bölersiniz? Offset paginasyonu seçerseniz basit SQL ve kullanıcıların anlayabileceği sayfa numaraları elde edersiniz. İmleç tabanlı paginasyonu seçerseniz istikrarlı sonuçlar ve herhangi bir derinlikte tutarlı gecikme elde edersiniz, ancak "sayfa 47'ye git" özelliğinden vazgeçersiniz.
Çoğu ekip offset'i seçer çünkü her öğreticide varsayılan budur. Ardından siparişler tablosu birkaç milyon satıra ulaştığında, 4.000. sayfa zaman aşımına uğramaya başlar ve kullanıcılar kaydırırken aynı kaydı iki kez gördüklerini bildirirler. Bu rehber, her iki stilin nasıl çalıştığını, offset'in nerede sorun çıkardığını, Stripe ve Slack'in neden imleçleri kullandığını ve Apidog'da zincirleme isteklerle her iki stilin nasıl test edileceğini kapsar. Sonunda, bitiş noktanıza hangisinin tam olarak uyduğunu bileceksiniz.
Önce daha geniş resmi görmek isterseniz, API paginasyon rehberimiz her stratejiyi yan yana kapsar. Bu makale, en önemli iki tanesine derinlemesine odaklanır.
Offset paginasyon nasıl çalışır
Offset paginasyon doğrudan SQL'e eşlenir. İstemci bir sayfa numarası ve bir sayfa boyutu gönderir; sunucu bunları LIMIT ve OFFSET'e çevirir.
SELECT id, customer_id, total_cents, created_at
FROM orders
ORDER BY created_at DESC
LIMIT 25 OFFSET 50;
Bu sorgu, sipariş listenizin sayfa başına 25 satırla 3. sayfasını döndürür. İstek şöyle görünür:
GET /v1/orders?page=3&per_page=25
Ve tipik bir yanıt:
{
"data": [
{
"id": "ord_8821",
"customer_id": "cus_1932",
"total_cents": 4599,
"created_at": "2026-08-30T14:22:07Z"
}
],
"page": 3,
"per_page": 25,
"total": 1848203,
"total_pages": 73929
}
Cazibesi ortada. İstemciler herhangi bir sayfaya atlayabilir. Sunucu toplam sayıyı döndürebilir. Herhangi bir geliştirici bunu bir öğleden sonra inşa edebilir. Küçük bir yönetici tablosu için bu doğru bir yaklaşımdır ve REST API'lerinde paginasyon hakkındaki adım adım rehberimiz, tam bir offset yapısını anlatmaktadır.
Ancak offset, iki yapısal sorun barındırır ve bunların hiçbiri geliştirme aşamasında ortaya çıkmaz. Her ikisi de üretimde kendini gösterir.
Problem 1: sayfa kayması
Offset, sıralanmış sonucun en üstünden satırları sayar. İstemcinin hangi satırları zaten gördüğü hakkında hiçbir şey bilmez. Bu nedenle, istekler arasında satırlar eklendiğinde veya silindiğinde, sayfalar istemcinin altından kayar.
Diyelim ki bir kullanıcı en yeniye göre sıralanmış siparişlerin 1. sayfasını (1'den 25'e kadar satırlar) yüklüyor. Okurken 3 yeni sipariş geliyor. OFFSET 25 olan 2. sayfayı istiyorlar. İlk yanıttaki 23, 24 ve 25 numaralı satırlar şimdi 26'dan 28'e kadar pozisyonlara itilmiş oluyor. Kullanıcı onları tekrar görüyor. Yinelenenler.
Silme işlemi bunu tersine çevirir. Kullanıcı okurken 1. sayfadan 3 satır kaldırın ve OFFSET 25 şimdi kullanıcının hiç görmediği 3 satırı atlar. Sessiz veri kaybı ve kimse bir hata almaz.
Kimsenin gerçek zamanlı olarak kaydırmadığı aylık bir rapor için kayma zararsızdır. Bir etkinlik akışı, bir senkronizasyon bitiş noktası veya yazma işlemleri devam ederken bir betiğin sayfa sayfa gezdiği herhangi bir şey için, kayma yinelenen veya eksik kayıtlar anlamına gelir. Tüketiciler bunu fark eder.
Problem 2: derin offset'ler atladıkları her şeyi tarar
OFFSET 500000, 500.001. satıra ışınlanmaz. Veritabanı dizini yarım milyon girdiye kadar tarar, bunları atar ve sonra sizin 25 satırınızı döndürür. Maliyet derinlikle doğrusal olarak artar: n offset olmak üzere O(n).
Somut sayılar bunu gerçek kılar. 2 milyon satırlı bir Postgres sipariş tablosunda ve created_at üzerinde bir dizinle:
LIMIT 25 OFFSET 025 dizin girdisi okur. Birkaç milisaniye.LIMIT 25 OFFSET 100000100.025 girdiyi okur ve 100.000'ini atar. Onlarca milisaniye.LIMIT 25 OFFSET 15000001.5 milyon girdiyi okur. Şimdi yüzlerce milisaniyelik derinliklere iniyorsunuz, arabellekleri tutuyor ve bir sayfa için CPU yakıyorsunuz.
Markus Winand'ın Use The Index, Luke'taki offset'siz yazısı, bu maliyeti sorgu planlarıyla göstermekte ve tamamını okumaya değerdir. Üretimdeki desen, yüksek offset istekleri tarafından domine edilen yavaş sorgu günlükleridir; genellikle genel API'nizin her sayfasını özenle gezen bir tarayıcıdan gelirler. Tek bir istemci ve p99'unuz iki katına çıkar.
İmleç tabanlı paginasyon nasıl çalışır
Anahtar kümesi paginasyonu olarak da adlandırılan imleç tabanlı paginasyon, satır sayacını kaldırır. İstemci "50 satırı atla" yerine "bana bu belirli kayıttan sonraki satırları ver" der. İmleç, istemcinin gördüğü son satırı tanımlar, böylece sunucu doğrudan bir sonraki bloğa geçebilir.
SQL, OFFSET yerine sıralama anahtarında bir satır karşılaştırması kullanır:
SELECT id, customer_id, total_cents, created_at
FROM orders
WHERE (created_at, id) < ('2026-08-30T14:22:07Z', 'ord_8821')
ORDER BY created_at DESC, id DESC
LIMIT 25;
İki sütunlu karşılaştırmaya dikkat edin. Yalnızca created_at benzersiz değildir; iki sipariş aynı milisaniyede gelebilir ve benzersiz olmayan bir sıralama anahtarı, sayfa sınırlarında satırların atlanması veya tekrarlanması anlamına gelir. id'yi bir beraberlik bozucu olarak eklemek, sıralamayı eksiksiz ve paginasyonu tam yapar. (created_at, id) üzerinde bir bileşik dizinle, veritabanı doğrudan sınıra gider ve 25 girdi okur. Sayfa 1 ve sayfa 60.000 aynı maliyete sahiptir.
Ancak API bu ham değerleri açığa çıkarmamalıdır. Gerçek uygulamalar, sıralama anahtarını opak bir belirteç olarak, genellikle base64 ile kodlar:
GET /v1/orders?limit=25&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0zMFQxNDoyMjowN1oiLCJpZCI6Im9yZF84ODIxIn0
Opaklık, kendi başına bir karmaşıklık değil, bir tasarım kararıdır. İmleci ayrıştıramayan istemciler URL'leri manuel olarak oluşturamaz, bu da sıralama anahtarını değiştirmekte, bir parça ipucu eklemekte veya depolama motorlarını kimseyi bozmadan değiştirmekte özgür bırakır. Sözleşme "size verdiğimizi geri verin"den ibaret olur, fazlası değil.
Takas: 47. sayfa yok. Bir imleç sadece "bu satırdan sonra" bilgisini bilir, bu yüzden istemciler her seferinde bir sayfa ileri (veya önceki bir imleç verirseniz geri) ilerler. Toplam sayılar da ücretsiz gelmez; sayma ayrı bir sorgudur. Veri setinin kendisi çok büyük olduğu tasarımlar için, milyonlarca kayıt için API paginasyonu tasarlama rehberimiz ölçeklendirme tarafını daha derinlemesine kapsar.
Bir bakışta ödünleşmeler
| Boyut | Offset paginasyonu | İmleç tabanlı paginasyon |
|---|---|---|
| Herhangi bir sayfaya atlama | Evet, herhangi bir sayfa numarası | Hayır, sadece sıralı gezinme |
| Toplam sayı / sayfa sayısı | Dahil etmesi ucuz | Ayrı sayım sorgusu |
| Derin sayfa performansı | O(n), derinlikle kötüleşir | Her derinlikte sayfa başına O(1) |
| Yazma işlemleri altında kararlılık | Kaymalar: tekrarlar ve boşluklar | Stabil, bir satıra sabitlenmiş |
| Geliştirme maliyeti | Önemsiz | Orta: kodlama, beraberlik bozucular, dizin tasarımı |
| Sıralama gereksinimleri | Herhangi bir ORDER BY çalışır | Benzersiz, dizinlenmiş bir sıralama anahtarı gerektirir |
| Sayfa URL'lerini önbelleğe alma | Kolay, URL'ler tahmin edilebilir | Daha zor, imleçler her gezinmede değişir |
| İstemci karmaşıklığı | Düşük | Düşük, zarf temizse |
Bu tablodaki bir incelik vurgulanmayı hak ediyor: imleç paginasyonu deterministik bir sıralama gerektirir. Eğer bitiş noktanız istemcilerin status gibi değiştirilebilir, benzersiz olmayan bir sütuna göre sıralamasına izin veriyorsa, anahtar kümesi mantığı hızla acı verici hale gelir. Offset özensiz sıralamayı tolere eder; imleçler bunu cezalandırır.
Hangisini seçmelisiniz?
Stili, verinin nasıl tüketildiğine göre eşleştirin.
Yönetici tabloları ve panoları: offset. Birkaç bin satırlı dahili araçlar, sayfa numaralarına tıklayan insanlar ve görünür "1.848 sonuç" sayısı. Kayma önemli değil, derinlik sığ kalır ve sayfaya atlama gerçek bir özelliktir. Offset, geliştirme maliyetinde kazanır.
Sonsuz kaydırma akışları: imleç. Kimse bir akışın 47. sayfasına atlamaz. Kullanıcılar sadece "daha fazla" yükler, yazma işlemleri sürekli devam eder ve tekrarlar görünür ve utanç vericidir. Bu, ders kitabı niteliğinde bir imleç durumudur.
Herkese açık API'ler: imleç. Tüketicilerinizi kontrol edemezsiniz. Birisi her sayfayı gezen bir döngü yazacaktır ve offset ile derin sayfalar sabaha karşı 3'te sizin sorununuz haline gelir. İmleçler her sayfayı ucuz tutar ve opak belirtecin arkasındaki iç mekanizmaları geliştirmenize olanak tanır. REST API paginasyon rehberimiz URL ve başlık kurallarını ayrıntılı olarak kapsar.
Dışa aktarımlar ve senkronizasyon işleri: imleç. Tüm 2 milyon siparişi çeken bir toplu işin iki garantisi olması gerekir: eşzamanlı yazmalara rağmen kaçırılan satır olmaması ve sayfa başına sabit maliyet. Offset bunların hiçbirini sağlamaz. Bir imleç ayrıca, iş 1.4 milyonuncu satırda durduğunda size ücretsiz bir devam noktası sunar.
Dürüst bir genel kural: küçük, insanlar tarafından gezilen, sayım ağırlıklı arayüzler için offset; büyük, canlı veya herkese açık her şey için imleçler.
Gerçek API'ler bunu nasıl ele alır
Stripe tamamen imleç tabanlıdır. Her liste bitiş noktası starting_after (bir nesne kimliği) ve limit kabul eder ve yanıtlar has_more içerir. Bir sonraki ücret sayfasını almak için, aldığınız son ücretin kimliğini geçirirsiniz. Stripe paginasyon dokümanları bu deseni gösterir; yazma hacimlerinde kasıtlı bir eksiklik olan hiçbir yerde toplam sayının bulunmadığına dikkat edin.
GitHub'ın REST API'si çoğu bitiş noktasında hala page ve per_page'i açığa çıkarır, Link başlıkları sonraki ve son sayfalara işaret eder. Ancak GitHub paginasyon dokümanlarını dikkatlice okuyun: istemcilere sayfa URL'leri oluşturmak yerine Link başlığını kelimesi kelimesine takip etmelerini söylerler ve daha yeni bitiş noktaları imleçlere geçmiştir, çünkü devasa depolarda derin offset gezintileri zarar vericidir.
Slack Web API'sini imleç paginasyonuna geçirdi ve şimdi tüm yeni yöntemlerin kullandığı yaklaşım olarak işaretliyor. conversations.history gibi yöntemler response_metadata.next_cursor döndürür ve boş bir imleç dizgisi, Slack paginasyon dokümanlarında açıklandığı gibi sona ulaştığınız anlamına gelir.
Üç yüksek trafikli API ve gidiş yönü tek bir yola doğru: imleçlere.
Yanıt zarfını tasarlama
Bir imleç API'si, zarfı üzerinde yaşar veya ölür. Onu sıkıcı ve tahmin edilebilir tutun:
{
"data": [
{
"id": "ord_8846",
"customer_id": "cus_2201",
"total_cents": 12900,
"created_at": "2026-08-30T16:01:44Z"
}
],
"has_more": true,
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0zMFQxNjowMTo0NFoiLCJpZCI6Im9yZF84ODQ2In0"
}
Dört kural onu sağlamlaştırır:
- Her zaman
has_moredöndürün. İstemciler kısa bir sayfadan sonu çıkarmamalıdır; bir sayfa, getirdikten sonra filtreleme yaparsanız akışın ortasında kısa olabilir. - Son sayfada
next_cursor: nulldöndürün ve bunu belgeleyin. Slack'in boş dizgi kuralı da işe yarar; birini seçin ve asla karıştırmayın. - Geçersiz imleçleri boş bir 200 ile değil, 400 ile reddedin. Bozuk bir imleç bir istemci hatasıdır ve bunu gizlemek birine bir günlük hata ayıklama maliyetine neden olur.
- İmleç yükünü imzalayın veya sürümünü belirtin eğer sıralama anahtarlarından başka bir şey kodluyorsa. Bir sonraki şema geçişinde kendinize teşekkür edeceksiniz.
Her iki stili Apidog'da test etme
Paginasyon hataları sınırlarda saklanır: son sayfa, boş sayfa, çapa satırı silinmiş imleç. Manuel tıklama bunları yakalamaz, ancak zincirleme bir test senaryosu yakalar ve Apidog iş akışındaki yerini burada kazanır.
İmleç bitiş noktaları için, iki adımlı bir test senaryosu oluşturun:
- Bitiş noktasını çağırın ve imleci çıkarın. İlk isteğe
$.next_cursorJSONPath'li bir işlem sonrası ekleyin ve bununextCursorgibi bir değişkende saklayın. Apidog, JSONPath'i doğrudan yanıt panelinden kopyalamanıza olanak tanır; tam anlatım JSONPath ile iddialar ayarlama ve değişkenler çıkarma bölümündedir. - Sonraki sayfa isteğini döngüye alın. İkinci bir isteği ForEach veya döngü adımına sarın,
{{nextCursor}}'ı imleç parametresi olarak geçirin, her yinelemede$.next_cursor'ı yeniden çıkarın vehas_moreyanlış olduğunda çıkın. Her geçişte önceki sayfadan hiçbirid'nin tekrarlanmadığını ve sayfa boyutunun aslalimit'i aşmadığını doğrulayın.
Offset bitiş noktaları için, aynı yapı bir sayaç değişkeniyle uygulanır: page'i artırın, son sayfaya kadar data uzunluğunun per_page'e eşit olduğunu doğrulayın ve gezinme boyunca total'in tutarlı kaldığını doğrulayın.
Ardından kenar durumlarını kendi adımları olarak ekleyin, her biri açık doğrulamalarla birlikte:
- Boş sayfa: sıfır satırla eşleşen bir filtre isteyin;
data'nın[]olduğunu,has_more'un yanlış olduğunu ve durumun 200 olduğunu doğrulayın. - Geçersiz imleç:
cursor=not-a-real-cursorgönderin; durumun 400 olduğunu ve makine tarafından okunabilir bir hata kodunu doğrulayın. - Silinen çapa satırı: bir sipariş oluşturun, ona sabitlenmiş bir imleç alın, siparişi silin, ardından imleci kullanın; hataya düşmek yerine gezinmenin doğru konumdan devam ettiğini doğrulayın. Anahtar kümesi karşılaştırmaları bunu doğal olarak ele alır ve test bunu kanıtlar.
Senaryo yerel olarak geçtiğinde, her birleştirme işleminde CI'da çalıştırın. Apidog'u ücretsiz indirin ve döngüler ve doğrulamalar dahil olmak üzere tam imleçle gezinme senaryosunu yarım saatten kısa sürede çalıştırabilirsiniz.
SSS
İmleç paginasyonu her zaman daha mı iyidir?
Hayır. Kullanıcıların sayfa numaralarına, toplam sayılara ve mütevazı bir veri kümesi üzerinde rastgele erişime ihtiyaç duyduğu durumlarda (ki bu çoğu dahili yönetici aracını tanımlar) offset daha iyi uyar. Veri kümesi büyük olduğunda, yazma işlemleri sık olduğunda veya API herkese açık olduğunda imleçler daha iyidir. Başarısızlık modu, genel bir liste bitiş noktası için varsayılan olarak offset kullanmak ve lansmandan sonra O(n) maliyetini keşfetmektir.
İmleç paginasyonu ile toplam sayıyı nasıl alırım?
Aynı filtrelerle ayrı bir SELECT COUNT(*) çalıştırın; bunu ayrı bir bitiş noktası olarak veya include_count=true gibi isteğe bağlı bir sorgu parametresi olarak yapabilirsiniz. Bunu agresif bir şekilde önbelleğe alın; her dakika yenilenen yaklaşık bir sayım neredeyse her kullanıcı arayüzünü tatmin eder. Stripe, toplam sayıları tamamen atlar ki bu da istemcilerin bunlara ne kadar sıklıkla gerçekten ihtiyaç duyduğunu gösterir.
Tek bir bitiş noktasında her iki paginasyon stilini de sunabilir miyim?
Yapabilirsiniz ve GitHub geçişi sırasında bunu etkin bir şekilde yapar, ancak yeni API'lerde bundan kaçının. İki stil, iki ayrı kenar durum seti, iki test matrisi ve hangisini kullanacağı konusunda istemci karışıklığı anlamına gelir. Bitiş noktası başına birini seçin. Sözleşmeyi sıfırdan tasarlıyorsanız, REST API paginasyon rehberimizdeki desenler, tüm yüzeyinizde parametre adlandırmasını tutarlı tutacaktır.
İmlecin çapa satırı silinirse ne olur?
Anahtar kümesi paginasyonu ile hiçbir şey bozulmaz. WHERE (created_at, id) < (?, ?) karşılaştırması çapa satırının var olmasını gerektirmez; sınır konumuna gider ve devam eder. Bu, "satır araması olarak imleç" tasarımlarına göre gerçek bir avantajdır ve bir tüketicinin sizin için bulmasından önce Apidog test senaryonuzda doğrulamaya değer kenar durumudur.
