DEV Community

Cover image for İmleç Bazlı Sayfalama vs Ofset Sayfalama: API'niz Hangisini Kullanmalı?
Tobias Hoffmann
Tobias Hoffmann

Posted on Originally published at apidog.com

İmleç Bazlı Sayfalama vs Ofset Sayfalama: API'niz Hangisini Kullanmalı?

Her liste bitiş noktası aynı soruyla karşılaşır: 2 milyon siparişi istemcinin adım adım gezebileceği sayfalara nasıl bölersiniz? Offset paginasyonu basit SQL ve kullanıcıların anlayabileceği sayfa numaraları sunar. İmleç tabanlı paginasyon ise istikrarlı sonuçlar ve her derinlikte tutarlı gecikme sağlar; karşılığında “sayfa 47’ye git” özelliğinden vazgeçersiniz.

Apidog'u bugün deneyin

API Paginasyonu: Offset mi, İmleç mi?

Çoğu ekip offset’i seçer çünkü öğreticilerde varsayılan yöntemdir. Ancak siparişler tablosu birkaç milyon satıra ulaştığında 4.000. sayfa zaman aşımına uğrayabilir ve kullanıcılar kaydırırken aynı kaydı iki kez görebilir.

Bu rehberde:

  • Offset ve imleç tabanlı paginasyonun nasıl çalıştığını
  • Offset’in üretimde neden sorun çıkardığını
  • Stripe ve Slack’in neden imleç kullandığını
  • Apidog’da zincirleme isteklerle her iki yaklaşımın nasıl test edileceğini

inceleyeceğiz.

Daha geniş bir karşılaştırma için API paginasyon rehberimize bakabilirsiniz. Bu yazı, en önemli iki yönteme odaklanır.

Offset paginasyon nasıl çalışır?

Offset paginasyonu doğrudan SQL’e eşlenir. İstemci sayfa numarası ve sayfa boyutu gönderir; sunucu bunları LIMIT ve OFFSET değerlerine çevirir:

SELECT id, customer_id, total_cents, created_at
FROM orders
ORDER BY created_at DESC
LIMIT 25 OFFSET 50;
Enter fullscreen mode Exit fullscreen mode

Bu sorgu, sipariş listesinin sayfa başına 25 satırla 3. sayfasını döndürür:

GET /v1/orders?page=3&per_page=25
Enter fullscreen mode Exit fullscreen mode

Tipik 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
}
Enter fullscreen mode Exit fullscreen mode

Offset’in avantajları açıktır:

  • İstemci herhangi bir sayfaya atlayabilir.
  • Sunucu toplam kayıt ve sayfa sayısını döndürebilir.
  • Küçük bir yönetici tablosu için hızlıca uygulanabilir.

Ancak üretimde iki yapısal sorun ortaya çıkar:

  1. Sayfa kayması
  2. Derin offset’lerde artan sorgu maliyeti

Problem 1: Sayfa kayması

Offset, sıralanmış sonucun en üstünden satırları sayar; istemcinin daha önce hangi satırları gördüğünü bilmez. İstekler arasında satırlar eklenir veya silinirse sayfalar istemcinin altından kayar.

Örneğin kullanıcı, en yeni siparişlere göre sıralanmış ilk 25 satırı alıyor olsun. Kullanıcı okurken 3 yeni sipariş eklenirse, istemci OFFSET 25 ile ikinci sayfayı istediğinde ilk yanıttaki 23, 24 ve 25. satırlar 26, 27 ve 28. pozisyonlara taşınır. Kullanıcı aynı kayıtları tekrar görür.

Silme işlemi ters etki yaratır. İlk sayfadan 3 satır silinirse OFFSET 25, kullanıcının henüz görmediği 3 satırı atlar. Böylece sessiz veri kaybı oluşur.

Bu sorun, gerçek zamanlı olarak gezilmeyen aylık raporlarda önemsiz olabilir. Ancak etkinlik akışları, senkronizasyon bitiş noktaları ve yazma işlemleri devam ederken sayfa sayfa taranan veri kümeleri için tekrarlar veya eksik kayıtlar üretir.

Problem 2: Derin offset’ler atlanan satırları tarar

OFFSET 500000, veritabanını doğrudan 500.001. satıra götürmez. Veritabanı dizinde yarım milyon girdiyi tarar, bunları atar ve ardından istenen 25 satırı döndürür. Maliyet derinlikle doğrusal artar: O(n).

2 milyon satırlı bir PostgreSQL orders tablosunda ve created_at diziniyle:

  • LIMIT 25 OFFSET 0: 25 dizin girdisi okunur; birkaç milisaniye.
  • LIMIT 25 OFFSET 100000: 100.025 girdi okunur ve 100.000’i atılır; onlarca milisaniye.
  • LIMIT 25 OFFSET 1500000: 1,5 milyon girdi okunur; yüzlerce milisaniyeye ulaşılır, arabellekler tutulur ve tek bir sayfa için CPU harcanır.

Use The Index, Luke’taki offset’siz paginasyon yazısı, sorgu planları üzerinden bu maliyeti gösterir.

Üretimde bu durum genellikle yüksek offset isteklerinin domine ettiği yavaş sorgu günlükleri olarak görünür. En yaygın kaynaklardan biri, genel API’nizin tüm sayfalarını sırayla gezen bir tarayıcıdır. Tek bir istemci bile p99 gecikmenizi iki katına çıkarabilir.

İmleç tabanlı paginasyon nasıl çalışır?

Anahtar kümesi paginasyonu olarak da bilinen imleç tabanlı paginasyon, satır sayacını ortadan kaldırır. İstemci “50 satır atla” demek yerine “bu belirli kayıttan sonraki satırları getir” der.

SQL, OFFSET yerine sıralama anahtarında 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;
Enter fullscreen mode Exit fullscreen mode

Burada iki sütunlu karşılaştırma önemlidir. created_at tek başına benzersiz değildir; aynı milisaniyede birden fazla sipariş gelebilir. Benzersiz olmayan bir sıralama anahtarı, sayfa sınırlarında kayıtların atlanmasına veya tekrarlanmasına neden olur.

id değerini beraberlik bozucu olarak eklemek sıralamayı deterministik hale getirir. (created_at, id) üzerinde bileşik dizinle veritabanı doğrudan sınıra gider ve 25 girdi okur. Böylece 1. sayfa ile 60.000. sayfanın maliyeti aynıdır.

Opak imleç kullanın

API, ham sıralama değerlerini açığa çıkarmamalıdır. Bunun yerine sıralama anahtarını genellikle Base64 ile kodlanmış opak bir belirteç olarak döndürün:

GET /v1/orders?limit=25&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0zMFQxNDoyMjowN1oiLCJpZCI6Im9yZF84ODIxIn0
Enter fullscreen mode Exit fullscreen mode

Opaklık istemcilerin URL’leri manuel olarak oluşturmasını engeller. Bu sayede sıralama anahtarını değiştirebilir, belirtece sürüm bilgisi ekleyebilir veya depolama motorunu istemcileri bozmadan değiştirebilirsiniz.

Takas açıktır: “47. sayfaya git” özelliği yoktur. İstemci her seferinde bir sonraki sayfaya ilerler. Toplam kayıt sayısı da ayrıca hesaplanmalıdır. Milyonlarca kayıt için API paginasyonu tasarlama rehberimiz ölçeklendirme ayrıntılarını ele alır.

Offset ve imleç karşılaştırması

Boyut Offset paginasyonu İmleç tabanlı paginasyon
Herhangi bir sayfaya atlama Evet, herhangi bir sayfa numarası Hayır, yalnızca sıralı gezinme
Toplam sayı / sayfa sayısı Dahil etmek ucuz Ayrı sayım sorgusu gerekir
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 Bir satıra sabitlenmiş stabil gezinme
Geliştirme maliyeti Düşük Orta: kodlama, beraberlik bozucular ve dizin tasarımı
Sıralama gereksinimi Her ORDER BY çalışabilir Benzersiz ve dizinlenmiş sıralama anahtarı gerekir
Sayfa URL’lerini önbelleğe alma Kolay, URL’ler tahmin edilebilir Daha zor, imleçler gezinmeye göre değişir
İstemci karmaşıklığı Düşük Zarf temizse düşük

İmleç paginasyonu deterministik sıralama gerektirir. Bitiş noktanız istemcilerin status gibi değiştirilebilir veya benzersiz olmayan bir sütuna göre sıralama yapmasına izin veriyorsa anahtar kümesi mantığı sorun çıkarabilir. Offset özensiz sıralamayı tolere eder; imleçler etmez.

Hangisini seçmelisiniz?

Stili verinin nasıl tüketildiğine göre seçin.

Yönetici tabloları ve panolar

Offset kullanın. Birkaç bin satırlı dahili araçlarda insanlar sayfa numaralarına tıklar ve “1.848 sonuç” gibi toplamları görmek ister. Kayma önemsiz, derinlik sınırlı ve sayfaya atlama gerçek bir özelliktir.

Sonsuz kaydırma akışları

İmleç kullanın. Kullanıcılar bir akışın 47. sayfasına gitmez; yalnızca “daha fazla” yükler. Yazma işlemleri sürerken tekrarlar görünür ve kullanıcı deneyimini bozar.

Herkese açık API’ler

İmleç kullanın. Tüketicilerinizi kontrol edemezsiniz. Bir istemci mutlaka her sayfayı gezen bir döngü yazacaktır. Offset ile derin sayfaların maliyeti gece yarısı sizin sorununuz olur. İmleçler her sayfayı ucuz tutar ve iç mekanizmaları opak belirtecin arkasında değiştirmenizi sağlar.

Parametre ve başlık kuralları için REST API’lerinde paginasyon rehberimize bakabilirsiniz.

Dışa aktarımlar ve senkronizasyon işleri

İmleç kullanın. 2 milyon siparişi çeken bir toplu işin iki garantisi olmalıdır:

  1. Eşzamanlı yazmalara rağmen kayıtların atlanmaması
  2. Her sayfanın sabit maliyetle işlenmesi

Offset bu garantileri sağlamaz. İmleç ayrıca işlem 1,4 milyonuncu satırda durduğunda doğal bir devam noktası sunar.

Genel kural:

Küçük, insanlar tarafından gezilen ve sayım odaklı arayüzler için offset; büyük, canlı veya herkese açık sistemler için imleç kullanın.

Gerçek API’ler bu yaklaşımı nasıl kullanıyor?

  • Stripe tamamen imleç tabanlıdır. Liste bitiş noktaları starting_after ve limit kabul eder; yanıtlar has_more içerir. Sonraki sayfa için alınan son nesnenin kimliği gönderilir. Stripe paginasyon dokümanları bu deseni gösterir ve toplam sayının bulunmadığını belirtir.
  • GitHub REST API çoğu bitiş noktasında hâlâ page ve per_page parametrelerini sunar. Link başlıkları sonraki ve son sayfalara işaret eder. GitHub paginasyon dokümanları, istemcilerin sayfa URL’leri oluşturmak yerine Link başlığını takip etmesini önerir. Daha yeni bitiş noktaları, büyük depolarda derin offset gezintisinin maliyeti nedeniyle imleçlere geçmiştir.
  • Slack Web API, imleç tabanlı paginasyona geçmiştir. conversations.history gibi yöntemler response_metadata.next_cursor döndürür. Boş imleç dizgisi sona ulaşıldığını gösterir; bu kural Slack paginasyon dokümanlarında açıklanır.

Bu üç yüksek trafikli API’nin ortak yönü, giderek imleçlere yönelmeleridir.

Yanıt zarfını tasarlama

İmleç API’leri açık ve tutarlı bir yanıt zarfına ihtiyaç duyar:

{
  "data": [
    {
      "id": "ord_8846",
      "customer_id": "cus_2201",
      "total_cents": 12900,
      "created_at": "2026-08-30T16:01:44Z"
    }
  ],
  "has_more": true,
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0zMFQxNjowMTo0NFoiLCJpZCI6Im9yZF84ODQ2In0"
}
Enter fullscreen mode Exit fullscreen mode

Sağlam bir sözleşme için:

  • Her zaman has_more döndürün. Filtreleme sonrasında kısa bir sayfa oluşabileceği için istemciler sona yalnızca kayıt sayısına bakarak karar vermemelidir.
  • Son sayfada next_cursor: null döndürün ve bu davranışı belgeleyin. Slack gibi boş dizgi kullanmak da mümkündür; tek bir kural seçip tutarlı kalın.
  • Geçersiz imleçleri boş bir 200 yanıtıyla gizlemek yerine 400 ile reddedin.
  • İmleç yükü sıralama anahtarlarından fazlasını kodluyorsa imleci imzalayın veya sürümleyin.

Her iki stili Apidog’da test etme

Paginasyon hataları çoğunlukla sınır durumlarında ortaya çıkar:

  • Son sayfa
  • Boş sayfa
  • Geçersiz imleç
  • Silinmiş çapa satırı

Manuel tıklama bu durumları güvenilir biçimde yakalamaz. Apidog’da zincirleme isteklerden oluşan bir test senaryosu oluşturun.

İmleç bitiş noktaları

  1. İlk isteği çağırın.
  2. Yanıt sonrası işlemle $.next_cursor JSONPath değerini çıkarıp nextCursor değişkenine kaydedin. JSONPath ile iddialar ayarlama ve değişkenler çıkarma rehberindeki yaklaşımı kullanabilirsiniz.
  3. Sonraki isteği bir ForEach veya döngü adımına sarın.
  4. {{nextCursor}} değerini cursor parametresi olarak gönderin.
  5. Her yinelemede $.next_cursor değerini yeniden çıkarın.
  6. has_more false olduğunda döngüyü sonlandırın.
  7. Önceki sayfalardaki hiçbir id değerinin tekrarlanmadığını ve sayfa boyutunun limit değerini aşmadığını doğrulayın.

Offset bitiş noktaları

Aynı yapıyı sayaç değişkeniyle uygulayın:

  • page değerini artırın.
  • Son sayfaya kadar data uzunluğunun per_page değerine eşit olduğunu doğrulayın.
  • Gezinme boyunca total değerinin değişmediğini kontrol edin.

Kenar durumları

Her durumu ayrı bir test adımı ve açık doğrulamalarla ekleyin:

  • Boş sayfa: Sıfır kayıt döndüren bir filtre isteyin. data: [], has_more: false ve HTTP 200 doğrulamalarını yapın.
  • Geçersiz imleç: cursor=not-a-real-cursor gönderin. HTTP 400 ve makine tarafından okunabilir bir hata kodu bekleyin.
  • Silinen çapa satırı: Bir sipariş oluşturun, ona sabitlenmiş bir imleç alın, siparişi silin ve imleci yeniden kullanın. Gezinmenin hata vermek yerine doğru konumdan devam ettiğini doğrulayın.

Anahtar kümesi karşılaştırmaları çapa satırının hâlâ var olmasını gerektirmez:

WHERE (created_at, id) < (?, ?)
Enter fullscreen mode Exit fullscreen mode

Senaryoyu yerel ortamda doğruladıktan sonra her birleştirme işleminde CI’da çalıştırın. Apidog’u ücretsiz indirin ve döngüler ile doğrulamaları içeren tam imleç gezinme senaryosunu yarım saatten kısa sürede oluşturun.

SSS

İmleç paginasyonu her zaman daha mı iyidir?

Hayır. Kullanıcıların sayfa numaralarına, toplam sayılara ve mütevazı veri kümelerinde rastgele erişime ihtiyaç duyduğu dahili yönetici araçlarında offset daha uygundur.

Veri kümesi büyükse, yazma işlemleri sık gerçekleşiyorsa veya API herkese açıksa imleçleri tercih edin. En kötü yaklaşım, genel bir liste bitiş noktasında varsayılan olarak offset kullanıp üretimde O(n) maliyetini keşfetmektir.

İmleç paginasyonunda toplam sayıyı nasıl alırım?

Aynı filtrelerle ayrı bir SELECT COUNT(*) sorgusu çalıştırın. Bunu ayrı bir bitiş noktası veya include_count=true gibi isteğe bağlı bir sorgu parametresi olarak sunabilirsiniz.

Sayım sonucunu agresif biçimde önbelleğe alın. Dakikada bir yenilenen yaklaşık bir değer çoğu kullanıcı arayüzü için yeterlidir. Stripe’ın toplam sayıları tamamen atlaması, istemcilerin bu bilgiye gerçekte ne kadar ihtiyaç duyduğunu gösterir.

Tek bir bitiş noktasında iki paginasyon stilini de sunabilir miyim?

Teknik olarak evet. GitHub geçiş sırasında bunu etkin biçimde yapar. Ancak yeni API’lerde mümkünse kaçının. İki stil:

  • İki farklı kenar durum kümesi
  • İki farklı test matrisi
  • İstemciler için seçim karmaşası

anlamına gelir.

Bitiş noktasına bir stil seçin. Sözleşmeyi sıfırdan tasarlıyorsanız REST API paginasyon rehberimizdeki tutarlı parametre adlandırma desenlerini kullanın.

İmlecin çapa satırı silinirse ne olur?

Anahtar kümesi paginasyonunda 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 gezinmeye devam eder.

Bu, “satır araması olarak imleç” tasarımlarına göre önemli bir avantajdır. Silinmiş çapa senaryosunu tüketicileriniz bulmadan önce Apidog testlerinde doğrulayın.

Top comments (0)