# VC-092 · Liste sorgusu bütün kayıtları getiriyor, sayfa boyutu sınırı uygulanmıyor

Geliştirme verisi küçükken bütün kayıtları getiren liste sorunsuz görünüyor. Veri büyüyünce her istek gereksiz aktarım ve bellek tüketiyor, arayüzde birkaç satır göstermek sunucunun yaptığı işi azaltmıyor.

- Önem: ORTA. Etkisi orta. Trafik artınca ya da istek tekrarlanınca tetiklenir.
- Önem notu: Kayıt sayısı artan ve sık okunan listenin sınırsız sonuç döndürmesi temel alınır. Gerçekten küçük ve üstten sınırlı sözlük tablosunda etki düşer. Büyük satır veya eşzamanlı yüksek istek sayısı bellek ve aktarım maliyetini artırır.
- Eksen ve kategori: Sağlamlık, 14 Performans ve ölçek
- Yığın: Node.js, Her yığın
- Yapay zekâ kodunda: ölçülmedi. Dayanak: uzman görüşü.
- Ne zaman bakılır: İlk yayından önce, Her ay
- CWE: CWE-770
- Checklist ifadesi: Her liste sorgusunda sayfa boyutu sınırı ve sayfalama var.
- Son inceleme: 4 Ekim 2026, Komünite editörlüğü
- Adres: https://vibecheck.komunite.com.tr/madde/liste-sorgusunda-sayfalama-ve-ust-sinir-yok

## 60 saniyelik kontrol

Yalnız kendi uygulamanda ya da yazılı izin aldığın sistemde dene. Bu bir sızma testi değildir.

1. Liste sorgusundaki gerçek LIMIT veya sağlayıcı sayfa sınırını bul.
2. Kullanıcının çok büyük veya geçersiz sayfa boyutu isteyip isteyemediğini kontrol et.
3. Ardışık sayfalarda kimliklerin ilerlediğini ve kayıtların yinelenmediğini doğrula.
4. Kiracı filtresinin her sayfada uygulandığını kontrol et.
5. Boş sonuçta ve son sayfada devam imlecinin bittiğini doğrula.

## Ne oluyor

Liste ekranı veritabanındaki bütün kayıtları getiriyor. Geliştirme verisi küçük olduğu için yanıt hemen geliyor ve arayüz rahatça gösteriyor. Veri büyüdüğünde aynı istek daha büyük yanıt, daha çok ayrıştırma ve daha fazla bellek gerektiriyor. Ekran yalnız ilk birkaç satırı gösterse bile sunucu ve tarayıcı geri kalan veriyi taşımış oluyor.

Arayüzde sayfa düğmeleri bulunması da gerçek sayfalama anlamına gelmez. Bütün kayıtlar indirildikten sonra diziyi parçalara ayıran ekran, aktarım maliyetini azaltmaz. Benzer şekilde API'nin `limit` parametresi kabul etmesi yeterli değildir. Bu değerin doğrulanıp gerçek sorguya uygulanması gerekir. Tanımlı ama kullanılmayan sayfa ayarı yanlış bir güven duygusu yaratabilir.

Sadece ilk sayfaya sınır koymak da eksik kalabilir. Devam etmek için kararlı bir sıra ve ilerleme bilgisi gerekir. Yeni sayfada kiracı filtresi kaybolursa başka kullanıcıların verisi karışabilir. Son sayfanın bittiği anlaşılmazsa istemci aynı veriyi tekrar isteyebilir. Sayfalama, sonuç boyutunu sınırlamakla birlikte bütün listeye güvenli ve anlaşılır erişim sözleşmesi kurar.

## Gerçek olay

Bu maddede belirli bir ürünün liste yüzünden çöktüğü iddia edilmiyor. [PostgreSQL LIMIT ve OFFSET belgesi](https://www.postgresql.org/docs/current/queries-limit.html), sonuçları parçalarken öngörülebilir sıralamanın önemini açıklar. Büyük offset değerlerinde atlanan satırların yine hesaplanması gerektiğini belirtir. Bu nedenle seçilen sayfalama yöntemi liste büyüklüğü ve gezinme ihtiyacına göre değerlendirilmelidir.

[SQLite SELECT belgesi](https://www.sqlite.org/lang_select.html), sorgu sonucuna üst sınır koymayı tanımlar. Yerel örnek pozitif ve değişmez kimlikle ilerleyen sayfalar üretir. Test ilk, ara ve son sayfayı dolaşır, başka kiracının satırının karışmadığını kontrol eder. Bu deney gerçek ağ aktarımını veya üretim belleğini ölçmez. Sorguya sınır uygulandığını ve devam davranışının doğru olduğunu gösterir.

## Yapay zekâ bunu neden üretiyor

**Örnek veri ölçek sanılır.** Ajan listeyi birkaç yapay kayıtla kurar. Bütün kayıtları getirmek bu ortamda kısa ve anlaşılır bir çözüm olur. Veri büyüme koşulu istemde yer almazsa sınırsız sorgu olduğu gibi kalabilir. Ekranın ilk kez açılmasıyla uzun vadeli sonuç bütçesi farklı ihtiyaçlardır.

**Görsel sayfalama yeterli görünür.** Ajan tabloya sayfa düğmeleri ekler ve istemcide `slice` kullanır. Kullanıcı daha az satır gördüğü için sayfalama tamamlanmış sanılabilir. Ancak veritabanı ve ağ aynı büyük sonucu üretmiştir. Sunucunun döndürdüğü veri ile ekranda çizilen satır sayısını ayrı incelemek gerekir.

**Varsayılan sınır tam çözüm sanılır.** Bir SDK veya hizmet sonuç sayısını varsayılan olarak sınırlayabilir. Ajan bu davranışı yeterli kabul edip devam yolunu kurmayabilir. Ekran hızlı görünür ama sonraki kayıtlar erişilemez kalır. Varsayılanın güncel değeri ve kapsamı belgelenmeli, kullanıcıya bütün listeyi dolaştıran sözleşme ayrıca kurulmalıdır.

**İmleç yetki gibi yorumlanır.** Ajan devam kimliğini taşıyan sorguda ilk sayfadaki kullanıcı filtresini unutabilir. İmleç yalnız ilerleme bilgisidir, erişim izni vermez. Her sayfa aynı yetki kapsamını korumalıdır. Bunlar olası üretim açıklamalarıdır. Belirli bir modelin bu hataları ne sıklıkta yaptığına ilişkin ölçülmüş yaygınlık iddiası bulunmuyor.

## Etki

Yanıt boyutu büyüdükçe aktarım ve JSON ayrıştırma süresi artabilir. Sunucu ve tarayıcı aynı anda büyük diziler tutabilir. Yoğun isteklerde tek listenin maliyeti diğer yolları da etkileyebilir. Yavaş cihaz veya bağlantıda kullanıcı ekranın donduğunu düşünebilir. Bu etkilerin büyüklüğü satır boyutuna ve iş yüküne bağlıdır.

Eksik sayfalama sessiz doğruluk sorunu da doğurur. Hizmetin ilk sonuç tavanına takılan liste bütün veri sanılabilir. Kullanıcı aradığı kaydın bulunmadığını düşünür. Kararsız sıralama ise sayfalar arasında atlama veya tekrar oluşturabilir. Sınırlı ve doğru devam eden sonuç, yalnız hızlı ekran kadar önem taşır.

## Nasıl anlarsın

Gerçek sorgudaki sınırı bul. Parametre yalnız arayüzde mi kullanılıyor, sunucuda mı doğrulanıyor? Çok büyük, sıfır, negatif veya metin biçimindeki değerler ne yapıyor? Bazı SQL davranışlarında negatif sınır sınırsız sonuç anlamına gelebilir. Bu yüzden değer doğrulaması sorgu parametresi bağlamaktan ayrı gerekir.

Yerel testte araya başka kiracının satırlarını yerleştir ve bütün sayfaları dolaş. Kimlikler ilerlemeli, aynı satır tekrarlanmamalı ve yabancı satır görünmemelidir. Son sayfada devam imleci bitmelidir. Boş liste ve geçersiz imleç de ayrı kontrol edilir. Üretim için sorgu planını ve aktarılan baytları ayrıca ölç, küçük yerel denemeden kapasite sonucu çıkarma.

## Nasıl düzeltirsin

1. **Sunucu sınırı belirle.** Varsayılan ve en yüksek sayfa boyutunu ürün ihtiyacına göre seç. Örnekteki değerler açıklama içindir. İstemcinin bu tavanı aşmasına izin verme. Gerçek sorguya sınır uygula, bütün diziyi aldıktan sonra kesmekle yetinme.
2. **Kararlı ilerleme kur.** Örnek pozitif, artan ve değişmez kayıt kimliğini kullanır. Son dönen kimlikten büyük kayıtlar seçilir. Başka sıralama gerekiyorsa eşit değerleri ayıran ek anahtar gerekir. Örneğin yalnız tarih sırası aynı tarihli kayıtlar için yeterli olmayabilir.
3. **Devamı açık döndür.** İyi örnek istenen boyuttan bir fazla satır okuyarak devam olup olmadığını belirler. Kullanıcıya yalnız sayfa boyutu kadar satır verilir. Devam varsa son gösterilen kimlik döner, yoksa imleç boş olur. Kiracı filtresi her çağrıda uygulanır.
4. **Liste ile dışa aktarımı ayır.** Bütün veriye ihtiyaç duyan iş için akışlı veya arka planda, ayrı bütçeli bir yol kur. Sayfalar arasında değişen verinin donmuş görünümü gerekiyorsa snapshot sözleşmesi gerekir. Basit imleç bunu kendiliğinden sağlamaz. İndeks seçimini de sorgu sırasıyla birlikte değerlendir.

## Bir daha olmasın

Her büyüyen listeye sınır ve devam testleri ekle. Yeni filtre veya sıralama geldiğinde ara sayfalarda kapsamın ve ilerlemenin korunduğunu doğrula.

## Sınır

Sabit ve küçük sözlük tablosu aynı riski taşımayabilir. Örnek tam metin arama, toplam kayıt sayısı veya donmuş veri görüntüsü üretmez. Satır sınırı tek satırın çok büyük olmasını engellemez. [CWE-770](https://cwe.mitre.org/data/definitions/770.html) kaynak sınırı eksikliğini destekler, her sınırsız sorgunun gerçekleşmiş hizmet kesintisi olduğunu söylemez.

## Düzeltme kodları

### Node.js: Sınırlı ve sıralı imleç sayfası

Önce:

```js
// liste/sayfa.js, açıklama amaçlı. tenantId güvenilen sunucu bağlamıdır.
export function listPage(db, tenantId, { after = 0, limit = 20 } = {}) {
  if (!Number.isSafeInteger(after) || after < 0) throw new Error('Geçersiz imleç');
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error('Geçersiz sayfa boyutu');
  // Sayfa ayarları kabul ediliyor ama sorguya uygulanmıyor.
  const rows = db.prepare(`SELECT id, title FROM items
    WHERE tenant_id = ? ORDER BY id`).all(tenantId);
  return {
    items: rows.map(row => ({ id: row.id, title: row.title })),
    nextCursor: null,
  };
}
// Küçük geliştirme verisinde bütün liste ekrana sığabilir.
// Veri arttığında her istek bütün satırları uygulamaya getirir.
// Arayüzde gizlemek sunucudan gelen yanıtı küçültmez.
```

Sonra:

```js
// liste/sayfa.js, açıklama amaçlı. tenantId güvenilen sunucu bağlamıdır.
export function listPage(db, tenantId, { after = 0, limit = 20 } = {}) {
  if (!Number.isSafeInteger(after) || after < 0) throw new Error('Geçersiz imleç');
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error('Geçersiz sayfa boyutu');
  const rows = db.prepare(`SELECT id, title FROM items
    WHERE tenant_id = ? AND id > ? ORDER BY id LIMIT ?`).all(tenantId, after, limit + 1);
  const hasMore = rows.length > limit;
  const items = rows.slice(0, limit).map(row => ({ id: row.id, title: row.title }));
  return {
    items,
    nextCursor: hasMore ? items.at(-1).id : null,
  };
}
// Kimlikler pozitif, artan ve değişmezdir. tenant_id, id indeksi varsayılır.
// Sayfalar arasında değişen verinin donmuş görüntüsü garanti edilmez.
```

Düzeltmeyi kanıtlayan test:

```js
// liste/sayfa.test.mjs, açıklama amaçlı. Gerçek SQLite ve karışık kiracı verisi.
import test from 'node:test';
import assert from 'node:assert/strict';
import { DatabaseSync } from 'node:sqlite';
const { listPage } = await import(process.env.ORNEK_DOSYA);
test('sınır uygulanır, imleç ilerler ve başka kiracı karışmaz', () => {
  const db = new DatabaseSync(':memory:');
  try {
    db.exec(`CREATE TABLE items(id INTEGER PRIMARY KEY, tenant_id TEXT, title TEXT);
      CREATE INDEX items_tenant ON items(tenant_id, id);
      INSERT INTO items VALUES (1, 'a', 'Bir'), (2, 'b', 'Yabancı'),
        (3, 'a', 'Üç'), (4, 'a', 'Dört'), (5, 'a', 'Beş'), (6, 'a', 'Altı');`);
    const first = listPage(db, 'a', { limit: 2 });
    assert.deepEqual(first.items.map(row => row.id), [1, 3]);
    assert.equal(first.nextCursor, 3);
    const second = listPage(db, 'a', { after: first.nextCursor, limit: 2 });
    assert.deepEqual(second.items.map(row => row.id), [4, 5]);
    const last = listPage(db, 'a', { after: second.nextCursor, limit: 2 });
    assert.deepEqual(last.items.map(row => row.id), [6]);
    assert.equal(last.nextCursor, null);
    assert.deepEqual(listPage(db, 'a', { after: 100 }), { items: [], nextCursor: null });
    assert.deepEqual(listPage(db, 'missing'), { items: [], nextCursor: null });
    for (const limit of [0, -1, 101, 1.5, '2']) assert.throws(() => listPage(db, 'a', { limit }));
    for (const after of [-1, NaN, '1']) assert.throws(() => listPage(db, 'a', { after }));
  } finally { db.close(); }
});
```

## Ajan kuralı (AGENTS.md)

```md
## Liste sınırsız veri getiriyor (vibecheck VC-092)
- Büyüyen listelerde sunucunun uyguladığı sonlu sayfa sınırı kullan.
- Sayfa boyutunu ve imleç biçimini doğrula.
- Kararlı sıralama ve ilerleme anahtarı seç.
- Yetki ve kiracı filtresini her sayfada koru.
- Son sayfada devam olmadığını açıkça bildir.
- Dışa aktarımı etkileşimli listeyle aynı sınırsız yol üzerinden verme.
```

## Kaynaklar

1. [SQLite SELECT](https://www.sqlite.org/lang_select.html), SQLite
2. [PostgreSQL LIMIT and OFFSET](https://www.postgresql.org/docs/current/queries-limit.html), PostgreSQL
3. [CWE-770 Allocation of Resources Without Limits or Throttling](https://cwe.mitre.org/data/definitions/770.html), MITRE

---

vibecheck · Komünite editörlüğü. Metin CC BY 4.0, prompt ve kural parçaları MIT-0. Kaynak: https://vibecheck.komunite.com.tr/madde/liste-sorgusunda-sayfalama-ve-ust-sinir-yok
