03. Okt. 2025·7 Min. Lesezeit

Cursor- vs. Offset-Paginierung für schnelle Admin-API-Endpunkte

Lerne Cursor- vs. Offset-Paginierung mit einem einheitlichen API-Vertrag für Sortierung, Filter und Totals, der Admin-Oberflächen im Web und auf Mobilgeräten schnell hält.

Cursor- vs. Offset-Paginierung für schnelle Admin-API-Endpunkte

Warum Paginierung Admin-Oberflächen langsam wirken lassen kann

Admin-Oberflächen beginnen oft als einfache Tabelle: die ersten 25 Zeilen laden, eine Suchbox dazu, fertig. Bei ein paar Hundert Datensätzen wirkt das instantan. Dann wächst der Datensatz, und dieselbe Ansicht beginnt zu ruckeln.

Das eigentliche Problem ist selten die UI. Sondern was die API tun muss, bevor sie Seite 12 mit angewendeten Sortierungen und Filtern zurückgeben kann. Mit wachsender Tabelle verbringt das Backend mehr Zeit damit, Treffer zu finden, sie zu zählen und frühere Ergebnisse zu überspringen. Wenn jeder Klick eine schwerere Abfrage auslöst, wirkt die Oberfläche wie am Nachdenken statt wie am Reagieren.

Man bemerkt es typischerweise an denselben Stellen: Seitenwechsel werden mit der Zeit langsamer, Sortieren wird träge, die Suche fühlt sich auf verschiedenen Seiten inkonsistent an, und bei Infinite Scroll lädt es in Schüben (schnell, dann plötzlich langsam). In stark genutzten Systemen können bei Änderungen zwischen Anfragen sogar Duplikate oder fehlende Zeilen auftreten.

Web- und Mobile-UIs treiben die Paginierung in unterschiedliche Richtungen. Eine Web-Admin-Tabelle lädt zum Springen zu einer bestimmten Seite und zum Sortieren nach vielen Spalten ein. Mobile Ansichten nutzen meist eine unendliche Liste, die das nächste Chunk lädt, und Nutzer erwarten, dass jeder Pull gleich schnell ist. Wenn deine API nur um Seitennummern herum gebaut ist, leidet Mobile oft. Wenn sie nur auf next/after ausgelegt ist, kann die Web-Tabelle eingeschränkt wirken.

Das Ziel ist nicht nur, 25 Elemente zurückzugeben. Sondern schnelles, vorhersehbares Paging, das mit wachsendem Datensatz stabil bleibt, und Regeln, die für Tabellen und Infinite Lists gleichermaßen funktionieren.

Grundlagen der Paginierung, von denen deine UI abhängt

Paginierung teilt eine lange Liste in kleinere Teile, damit die Ansicht schnell laden und rendern kann. Anstatt die API nach allen Datensätzen zu fragen, fordert die UI das nächste Segment der Ergebnisse an.

Die wichtigste Steuerung ist die Seitengröße (oft limit genannt). Kleinere Seiten wirken meist schneller, weil der Server weniger Arbeit hat und die App weniger Zeilen zeichnen muss. Zu kleine Seiten können sich jedoch sprunghaft anfühlen, weil Nutzer häufiger klicken oder scrollen müssen. Für viele Admin-Tabellen ist 25 bis 100 Elementen ein praktischer Bereich, wobei Mobile meist das untere Ende bevorzugt.

Eine stabile Sortierreihenfolge ist wichtiger, als die meisten Teams erwarten. Wenn sich die Reihenfolge zwischen Anfragen ändern kann, sehen Nutzer Duplikate oder fehlende Zeilen beim Durchblättern. Stabile Sortierung bedeutet normalerweise Sortierung nach einem primären Feld (z. B. created_at) plus einem Tie-Breaker (z. B. id). Das ist wichtig, egal ob du Offset- oder Cursor-Paginierung verwendest.

Aus Sicht des Clients sollte eine paginierte Antwort die Elemente, einen Hinweis auf die nächste Seite (Seitenzahl oder Cursor-Token) und nur die Counts enthalten, die die UI wirklich braucht. Manche Screens brauchen eine exakte Gesamtzahl für „1–50 von 12.340“. Andere benötigen nur has_more.

Offset-Paginierung: wie sie funktioniert und wo sie weh tut

Offset-Paginierung ist der klassische Page-N-Ansatz. Der Client fordert eine feste Anzahl Zeilen an und sagt der API, wie viele Zeilen zuerst übersprungen werden sollen. Du siehst das als limit und offset oder als page und pageSize, die der Server in ein Offset umrechnet.

Eine typische Anfrage sieht so aus:

  • GET /tickets?limit=50&offset=950
  • „Gib mir 50 Tickets, überspringe die ersten 950.“

Das passt zu typischen Admin-Bedürfnissen: zu Seite 20 springen, ältere Datensätze durchsuchen oder eine große Liste stückweise exportieren. Es ist auch intern leicht zu erklären: „Schau dir Seite 3 an und du siehst es.“

Das Problem zeigt sich bei tiefen Seiten. Viele Datenbanken müssen trotzdem die übersprungenen Zeilen passieren, bevor sie deine Seite zurückgeben — besonders, wenn die Sortierung nicht durch einen strengen Index unterstützt wird. Seite 1 ist vielleicht schnell, aber Seite 200 kann deutlich langsamer werden, und genau das lässt Admin-Oberflächen beim Scrollen oder Springen träge wirken.

Ein weiteres Problem ist Konsistenz, wenn sich Daten ändern. Stell dir vor, ein Support-Manager öffnet Seite 5 der Tickets, sortiert nach Neueste zuerst. Während er schaut, kommen neue Tickets dazu oder ältere werden gelöscht. Einfügungen können Elemente nach vorne verschieben (Duplikate über Seiten hinweg). Löschungen können Elemente zurückschieben (Datensätze verschwinden aus dem Browsing-Pfad des Nutzers).

Offset-Paginierung kann für kleine Tabellen, stabile Datensätze oder einmalige Exporte völlig ausreichend sein. Bei großen, aktiven Tabellen tauchen die Randfälle schnell auf.

Cursor-Paginierung: wie sie funktioniert und warum sie stabil bleibt

Cursor-Paginierung benutzt einen Cursor als Lesezeichen. Statt „gib mir Seite 7“ sagt der Client „mach weiter nach diesem exakten Element“. Der Cursor kodiert gewöhnlich die Sortierwerte des letzten Elements (z. B. created_at und id), sodass der Server an der richtigen Stelle weitermachen kann.

Die Anfrage besteht üblicherweise nur aus:

  • limit: wie viele Elemente zurückgegeben werden sollen
  • cursor: ein opaques Token aus der vorherigen Antwort (oft after genannt)

Die Antwort liefert Elemente plus einen neuen Cursor, der ans Ende dieses Abschnitts zeigt. Der praktische Unterschied ist, dass Cursor die Datenbank nicht auffordern, Zeilen zu zählen und zu überspringen. Stattdessen sagen sie: starte an einer bekannten Position.

Deshalb bleibt Cursor-Paginierung für vorwärtsgerichtete Listen schnell. Mit einem guten Index kann die Datenbank zu „Elementen nach X“ springen und dann die nächsten limit Zeilen lesen. Bei Offsets muss der Server oft immer mehr Zeilen scannen (oder zumindest überspringen), je größer das Offset wird.

Für das UI-Verhalten macht Cursor-Paginierung „Weiter“ natürlich: du nimmst den zurückgegebenen Cursor und sendest ihn in der nächsten Anfrage wieder. „Zurück“ ist optional und komplizierter. Manche APIs unterstützen einen before-Cursor, andere lesen umgekehrt und drehen die Ergebnisse um.

Wann Cursor, Offset oder ein Hybrid wählen

Starte schnell ein Admin-Panel
Erstelle interne Tools wie Tickets, Bestellungen und Nutzer mit wiederverwendbarem Listenverhalten.
Admin bauen

Die Entscheidung beginnt damit, wie Nutzer die Liste tatsächlich verwenden.

Cursor-Paginierung passt am besten, wenn Nutzer überwiegend vorwärts navigieren und Geschwindigkeit zählt: Aktivitätslogs, Chats, Bestellungen, Tickets, Audit-Trails und die meisten mobilen Infinite-Scrolls. Sie verhält sich auch besser, wenn während der Durchsicht neue Zeilen eingefügt oder gelöscht werden.

Offset-Paginierung macht Sinn, wenn Nutzer häufig herumspringen: klassische Admin-Tabellen mit Seitenzahlen, „Gehe zu Seite“-Funktion und schnellem Hin-und-Her. Es ist einfach zu erklären, kann auf großen Datensätzen aber langsamer werden und weniger stabil, wenn sich die Daten unter dir ändern.

Ein praktischer Entscheidungsweg:

  • Wähle Cursor, wenn die Hauptaktion „nächste, nächste, nächste“ ist.
  • Wähle Offset, wenn „springe zu Seite N“ eine echte Anforderung ist.
  • Betrachte Totals als optional. Exakte Totals können auf riesigen Tabellen teuer sein.

Hybride sind üblich. Ein Ansatz ist Cursor-basiertes next/prev für Geschwindigkeit plus ein optionaler Seiten-Sprung-Modus für kleine, gefilterte Teilmengen, bei denen Offsets schnell bleiben. Ein anderer ist Cursor-Abfrage mit Seitennummern basierend auf einer gecachten Momentaufnahme, sodass die Tabelle vertraut wirkt, ohne jede Anfrage in schwere Arbeit zu verwandeln.

Ein konsistenter API-Vertrag, der auf Web und Mobile funktioniert

Admin-UIs fühlen sich schneller an, wenn jeder Listendpunkt gleich reagiert. Die UI kann sich ändern (Web-Tabelle mit Seitennummern, Mobile Infinite Scroll), aber der API-Vertrag sollte stabil bleiben, sodass du die Paginierungsregeln nicht für jeden Screen neu lernen musst.

Ein praktischer Vertrag hat drei Teile: Zeilen, Paging-Status und optionale Totals. Behalte identische Feldnamen über Endpunkte hinweg (tickets, users, orders), selbst wenn der zugrundeliegende Paging-Modus variiert.

Hier ist eine Antwortstruktur, die sowohl für Web als auch Mobile gut funktioniert:

{
  "data": [ { "id": "...", "createdAt": "..." } ],
  "page": {
    "mode": "cursor",
    "limit": 50,
    "nextCursor": "...",
    "prevCursor": null,
    "hasNext": true,
    "hasPrev": false
  },
  "totals": {
    "count": 12345,
    "filteredCount": 120
  }
}

Ein paar Details machen das leicht wiederverwendbar:

  • page.mode sagt dem Client, was der Server macht, ohne Feldnamen zu ändern.
  • limit ist immer die angeforderte Seitengröße.
  • nextCursor und prevCursor sind vorhanden, auch wenn einer null ist.
  • totals ist optional. Wenn es teuer ist, gib es nur zurück, wenn der Client es anfordert.

Eine Web-Tabelle kann weiterhin „Seite 3“ anzeigen, indem sie ihren eigenen Seitenindex führt und die API wiederholt aufruft. Eine Mobile-Liste kann Seitenzahlen ignorieren und einfach das nächste Chunk anfordern.

Wenn du sowohl Web als auch Mobile Admin-UIs in AppMaster baust, zahlt sich ein stabiler Vertrag schnell aus. Dasselbe Listenverhalten lässt sich über Bildschirme hinweg wiederverwenden, ohne für jeden Endpunkt individuelle Paginierungslogik zu schreiben.

Sortierregeln, die Paginierung stabil halten

Lass Admin-Oberflächen sofort wirken
Erstelle ein Backend für Admin-Tabellen, das reaktionsschnell bleibt, während dein Datensatz wächst.
Projekt starten

Sortierung ist der Punkt, an dem Paginierung meist bricht. Wenn sich die Reihenfolge zwischen Anfragen ändern kann, sehen Nutzer Duplikate, Lücken oder „fehlende“ Zeilen.

Mach Sortierung zu einem Vertrag, nicht zu einer Empfehlung. Veröffentliche die erlaubten Sortierfelder und -richtungen und lehne alles andere ab. Das macht deine API vorhersehbar und verhindert, dass Clients langsame Sortierungen anfordern, die in der Entwicklung harmlos wirken.

Eine stabile Sortierung braucht einen eindeutigen Tie-Breaker. Wenn du nach created_at sortierst und zwei Datensätze denselben Zeitstempel haben, füge id (oder eine andere eindeutige Spalte) als letztes Sortierkriterium hinzu. Ohne ihn kann die Datenbank gleiche Werte in beliebiger Reihenfolge zurückgeben.

Praktische Regeln, die standhalten:

  • Erlaube Sortierung nur auf indizierten, klar definierten Feldern (z. B. created_at, updated_at, status, priority).
  • Füge immer einen eindeutigen Tie-Breaker als letztes Schlüssel hinzu (z. B. id ASC).
  • Definiere eine Standardsortierung (z. B. created_at DESC, id DESC) und halte sie über Clients hinweg konsistent.
  • Dokumentiere, wie mit Null-Werten umgegangen wird (z. B. „nulls last“ für Datums- und Zahl-Felder).

Sortierung treibt auch die Cursor-Erzeugung. Ein Cursor sollte die Sortierwerte des letzten Elements in Reihenfolge kodieren, einschließlich des Tie-Breakers, damit die nächste Seite mit der Anfrage "after" dieses Tupels fortgesetzt werden kann. Wenn sich die Sortierung ändert, werden alte Cursors ungültig. Behandle Sortier-Parameter als Teil des Cursor-Vertrags.

Filter und Totals, ohne den Vertrag zu brechen

Filter sollten sich von der Paginierung separiert anfühlen. Die UI sagt: „Zeig mir eine andere Menge von Zeilen“, und fragt dann: „Durchblättere diese Menge.“ Wenn du Filterfelder ins Pagination-Token mischst oder Filter optional und unvalidiert behandelst, bekommst du schwer zu debuggendes Verhalten: leere Seiten, Duplikate oder ein Cursor, der plötzlich in einem anderen Datensatz landet.

Eine einfache Regel: Filter leben in normalen Query-Parametern (oder im Request-Body für POST), und der Cursor ist opaques und nur für genau diese Filter- und Sort-Kombination gültig. Wenn der Nutzer einen Filter ändert (Status, Datumsbereich, Bearbeiter), sollte der Client den alten Cursor verwerfen und von vorn starten.

Sei strikt bei erlaubten Filtern. Das schützt vor Performance-Problemen und macht das Verhalten vorhersehbar:

  • Lehne unbekannte Filterfelder ab (ignoriere sie nicht stillschweigend).
  • Validere Typen und Bereiche (Datumsangaben, Enums, IDs).
  • Begrenze weitreichende Filter (z. B. max. 50 IDs in einer IN-Liste).
  • Wende dieselben Filter auf Daten und Totals an (keine mismatched Zahlen).

Totals sind ein häufiger Flaschenhals. Exakte Counts können auf großen Tabellen teuer sein, besonders mit mehreren Filtern. Du hast in der Regel drei Optionen: exakt, geschätzt oder kein Total. Exakt ist großartig für kleine Datensätze oder wenn Nutzer wirklich „zeige 1–25 von 12.431“ brauchen. Geschätzt reicht oft für Admin-Screens. Kein Total ist in Ordnung, wenn du nur „Mehr laden“ brauchst.

Um jede Anfrage nicht zu verlangsamen, mach Totals optional: berechne sie nur, wenn der Client sie anfordert (z. B. mit einem Flag wie includeTotal=true), cache sie kurz pro Filter-Set oder gib Totals nur auf der ersten Seite zurück.

Schritt für Schritt: Endpunkt entwerfen und implementieren

Eine API für Web und Mobile
Nutze einen einheitlichen Pagination-Vertrag für Web- und native Mobile-Oberflächen.
App erstellen

Beginne mit Defaults. Ein Listendpunkt braucht eine stabile Sortierreihenfolge plus einen Tie-Breaker für Zeilen mit gleichem Wert. Zum Beispiel: createdAt DESC, id DESC. Der Tie-Breaker (id) verhindert Duplikate und Lücken, wenn neue Datensätze hinzukommen.

Definiere eine Anfrageform und halte sie langweilig. Typische Parameter sind limit, cursor (oder offset), sort und filters. Wenn du beide Modi unterstützt, mache sie gegenseitig exklusiv: entweder sendet der Client cursor oder offset, aber nicht beides.

Behalte einen konsistenten Antwortvertrag, damit Web- und Mobile-UIs dieselbe Listenlogik teilen können:

  • items: die Seite mit Datensätzen
  • nextCursor: der Cursor, um die nächste Seite zu holen (oder null)
  • hasMore: boolean, damit die UI entscheiden kann, ob „Mehr laden“ angezeigt wird
  • total: Anzahl der passenden Datensätze (null, es sei denn angefordert, wenn Zählungen teuer sind)

Bei der Implementierung weichen die Ansätze auseinander.

Offset-Abfragen sind üblicherweise ORDER BY ... LIMIT ... OFFSET ..., was auf großen Tabellen langsam werden kann.

Cursor-Abfragen verwenden Seek-Bedingungen basierend auf dem letzten Element: „gib mir Elemente, wo (createdAt, id) kleiner ist als das letzte (createdAt, id)“. Das hält die Performance stabiler, weil die Datenbank Indizes nutzen kann.

Bevor du auslieferst, baue Schutzmaßnahmen ein:

  • Begrenze limit (z. B. max. 100) und setze einen Default.
  • Validere sort gegen eine Allowlist.
  • Validere Filter nach Typ und lehne unbekannte Keys ab.
  • Mache cursor opaques (kodiert die letzten Sortierwerte) und lehne fehlerhafte Cursors ab.
  • Entscheide, wie total angefordert wird.

Teste mit Daten, die sich unter dir ändern. Erzeuge und lösche Datensätze zwischen Anfragen, aktualisiere Felder, die Sortierung beeinflussen, und überprüfe, dass du keine Duplikate oder fehlenden Zeilen siehst.

Beispiel: Ticket-Liste, die auf Web und Mobile schnell bleibt

Ändere Anforderungen sicher
Regeneriere sauberen Quellcode, wenn sich Anforderungen ändern — ohne technischen Ballast.
Code generieren

Ein Support-Team öffnet eine Admin-Ansicht, um die neuesten Tickets zu prüfen. Sie brauchen, dass die Liste instant wirkt, auch wenn neue Tickets hereinkommen und Agenten ältere aktualisieren.

Im Web ist die UI eine Tabelle. Die Standardsortierung ist updated_at (neueste zuerst), und das Team filtert oft nach Open oder Pending. Derselbe Endpunkt kann beide Situationen mit stabiler Sortierung und einem Cursor-Token unterstützen.

GET /tickets?status=open&sort=-updated_at&limit=50&cursor=eyJ1cGRhdGVkX2F0IjoiMjAyNi0wMS0yNVQxMTo0NTo0MloiLCJpZCI6IjE2OTMifQ==

Die Antwort bleibt vorhersagbar für die UI:

{
  "items": [{"id": 1693, "subject": "Login issue", "status": "open", "updated_at": "2026-01-25T11:45:42Z"}],
  "page": {"next_cursor": "...", "has_more": true},
  "meta": {"total": 128}
}

Auf Mobile treibt derselbe Endpunkt das Infinite Scroll. Die App lädt 20 Tickets auf einmal und sendet dann next_cursor, um das nächste Batch zu holen. Keine Seitennummern-Logik und weniger Überraschungen, wenn Datensätze sich ändern.

Der Schlüssel ist, dass der Cursor die zuletzt gesehene Position kodiert (z. B. updated_at plus id als Tie-Breaker). Wenn ein Ticket während des Scrollens aktualisiert wird, kann es beim nächsten Refresh nach oben wandern, aber es verursacht keine Duplikate oder Lücken im bereits durchgescrollten Feed.

Totals sind nützlich, aber teuer auf großen Datensätzen. Eine einfache Regel: gib meta.total nur zurück, wenn der Nutzer einen Filter anwendet (z. B. status=open) oder es explizit anfragt.

Häufige Fehler, die Duplikate, Lücken und Verzögerungen verursachen

Die meisten Pagination-Bugs liegen nicht in der Datenbank. Sie entstehen durch kleine API-Entscheidungen, die in Tests harmlos wirken, aber auseinanderfallen, wenn sich Daten zwischen Anfragen ändern.

Die häufigste Ursache für Duplikate (oder fehlende Zeilen) ist Sortierung auf einem Feld, das nicht eindeutig ist. Wenn du nach created_at sortierst und zwei Items denselben Zeitstempel haben, kann sich die Reihenfolge zwischen Anfragen drehen. Die Lösung ist simpel: füge immer einen stabilen Tie-Breaker hinzu, meist den Primärschlüssel, und behandle die Sortierung als Paar wie (created_at desc, id desc).

Ein weiteres häufiges Problem ist, dass Clients beliebige Seitengrößen anfordern dürfen. Eine große Anfrage kann CPU, Speicher und Antwortzeiten in die Höhe treiben und damit jede Admin-Seite verlangsamen. Wähle einen sinnvollen Default und ein hartes Limit und gib einen Fehler zurück, wenn der Client mehr anfragt.

Totals können ebenfalls schaden. Das Zählen aller passenden Zeilen bei jeder Anfrage kann der langsamste Teil des Endpunkts werden, besonders mit Filtern. Wenn die UI Totals braucht, hole sie nur auf Anfrage (oder gib eine Schätzung) und vermeide, dass das Listenscrollen auf eine vollständige Zählung wartet.

Fehler, die am häufigsten Duplikate, Lücken und Verzögerungen erzeugen:

  • Sortierung ohne eindeutigen Tie-Breaker (instabile Reihenfolge)
  • Unbegrenzte Seitengrößen (Serverüberlastung)
  • Totals bei jeder Anfrage zurückgeben (langsame Abfragen)
  • Offset- und Cursor-Regeln im selben Endpunkt mischen (verwirrendes Client-Verhalten)
  • Derselbe Cursor wird wiederverwendet, wenn Filter oder Sortierung sich ändern (falsche Ergebnisse)

Setze die Paginierung zurück, wann immer Filter oder Sortierung geändert werden. Behandle eine neue Filterung wie eine neue Suche: leere den Cursor/Offset und starte bei der ersten Seite.

Schnell-Checkliste vor dem Launch

Mache deinen API-Vertrag konsistent
Standardisiere einmal dein Antwortformat und nutze es für alle Listendendenpunkte wieder.
Loslegen

Führe das mit API und UI parallel einmal durch. Die meisten Probleme passieren im Vertrag zwischen Listenansicht und Server.

  • Standardsortierung ist stabil und enthält einen eindeutigen Tie-Breaker (z. B. created_at DESC, id DESC).
  • Sortierfelder und -richtungen sind auf eine White-List gesetzt.
  • Max. Seitengröße ist durchgesetzt, mit einem sinnvollen Default.
  • Cursor-Tokens sind opaques, und ungültige Cursors schlagen vorhersehbar fehl.
  • Jede Filter- oder Sortänderung setzt die Paginierung zurück.
  • Totals-Verhalten ist explizit: exakt, geschätzt oder weggelassen.
  • Derselbe Vertrag unterstützt sowohl Tabelle als auch Infinite Scroll ohne Spezialfälle.

Nächste Schritte: standardisiere deine Listen und halte sie konsistent

Wähle eine Admin-Liste, die täglich genutzt wird, und mache sie zum Goldstandard. Eine stark frequentierte Tabelle wie Tickets, Orders oder Users ist ein guter Startpunkt. Wenn dieser Endpunkt schnell und vorhersehbar ist, übernimm denselben Vertrag für die restlichen Admin-Ansichten.

Schreibe den Vertrag auf, auch wenn er kurz ist. Sei explizit darüber, was die API akzeptiert und was sie zurückgibt, damit das UI-Team nichts raten muss und nicht versehentlich für jeden Endpunkt unterschiedliche Regeln erfindet.

Ein einfacher Standard für jeden Listendpunkt:

  • Erlaubte Sortierungen: genaue Feldnamen, Richtung und ein klarer Default (plus Tie-Breaker wie id).
  • Erlaubte Filter: welche Felder gefiltert werden können, Formate der Werte und was bei ungültigen Filtern passiert.
  • Totals-Verhalten: wann du eine Zählung zurückgibst, wann du "unbekannt" zurückgibst und wann du sie weglässt.
  • Antwortform: konsistente Keys (items, Paging-Info, angewendete Sort/Filter, Totals).
  • Fehlerregeln: konsistente Statuscodes und lesbare Validierungsnachrichten.

Wenn du diese Admin-Oberflächen mit AppMaster (appmaster.io) baust, hilft es, den Paginierungsvertrag früh zu standardisieren. Du kannst dasselbe Listenverhalten über Web- und native Mobile-Apps wiederverwenden und musst weniger Zeit damit verbringen, Pagination-Edge-Cases später zu jagen.

FAQ

Was ist der wirkliche Unterschied zwischen Offset- und Cursor-Paginierung?

Offset-Paginierung verwendet limit plus offset (oder page/pageSize), um Zeilen zu überspringen — tiefe Seiten werden oft langsamer, weil die Datenbank mehr Datensätze überspringen muss. Cursor-Paginierung nutzt ein after-Token basierend auf den Sortierwerten des letzten Elements, sodass die Suche an einer bekannten Position fortgesetzt werden kann und auch bei Vorwärtsbewegung schnell bleibt.

Warum fühlt sich meine Admin-Tabelle langsamer an, je mehr Seiten ich durchblättere?

Seite 1 ist normalerweise günstig, aber Seite 200 zwingt die Datenbank dazu, eine große Anzahl von Zeilen zu überspringen, bevor sie etwas zurückgibt. Wenn du zusätzlich sortierst und filterst, wächst die Arbeit — deshalb fühlt sich jeder Klick wie eine schwere Abfrage an statt eines schnellen Fetches.

Wie verhindere ich Duplikate oder fehlende Zeilen beim Paginieren?

Nutze immer eine stabile Sortierung mit einem eindeutigen Tie-Breaker, z. B. created_at DESC, id DESC oder updated_at DESC, id DESC. Ohne Tie-Breaker können sich Datensätze mit gleichen Zeitstempeln zwischen Anfragen vertauschen, was Duplikate oder "fehlende" Zeilen verursacht.

Wann sollte ich Cursor-Paginierung bevorzugen?

Bevorzuge Cursor-Paginierung für Listen, bei denen Nutzer hauptsächlich vorwärts navigieren und Geschwindigkeit wichtig ist — Aktivitätslogs, Tickets, Bestellungen und mobiles Infinite Scroll sind typische Beispiele. Cursor bleibt konsistent, wenn neue Zeilen eingefügt oder gelöscht werden, weil der Cursor die nächste Seite an einer exakten zuletzt gesehenen Position verankert.

Wann macht Offset-Paginierung trotzdem noch Sinn?

Offset-Paginierung ist sinnvoll, wenn "Springe zu Seite N" eine echte Anforderung ist und Nutzer oft hin und her navigieren. Sie eignet sich auch für kleine Tabellen oder stabile Datensätze, bei denen tiefe Seitenverlangsamung und Verschiebungen keine große Rolle spielen.

Was sollte eine konsistente Paginierungs-API-Antwort enthalten?

Behalte ein einheitliches Antwortformat über Endpunkte hinweg und liefere Elemente, Seitenstatus und optionale Totals. Ein praktisches Standardformat enthält items, ein page-Objekt (mit limit, nextCursor/prevCursor oder offset) und eine leichte Kennzahl wie hasNext, sodass sowohl Web-Tabellen als auch Mobile-Listen dieselbe Client-Logik nutzen können.

Warum können Totals Paginierung verlangsamen, und was ist eine sichere Grundeinstellung?

Weil ein exaktes COUNT(*) über große, gefilterte Datensätze die langsamste Komponente einer Anfrage werden kann und jede Seitenänderung ausbremst. Ein sicherer Standard ist, Totals optional zu machen, sie nur bei Anfrage zu liefern oder stattdessen has_more zu verwenden, wenn die UI nur "Mehr laden" braucht.

Was sollte mit dem Cursor passieren, wenn Filter oder Sortierung geändert werden?

Behandle Filter als Teil des Datensatzes und den Cursor als nur für genau diese Filter- und Sort-Kombination gültig. Wenn ein Nutzer Filter oder Sortierung ändert, setze die Paginierung zurück und starte ab der ersten Seite; die Wiederverwendung eines alten Cursors nach Änderungen führt häufig zu leeren Seiten oder verwirrenden Ergebnissen.

Wie mache ich Sortierung schnell und vorhersehbar für die Paginierung?

Whitelist erlaubte Sortierfelder und -richtungen und lehne alles andere ab, damit Clients nicht versehentlich langsame oder instabile Sortierungen anfordern. Bevorzuge sortieren auf indizierten Feldern und hänge immer einen eindeutigen Tie-Breaker wie id an, damit die Reihenfolge zwischen Anfragen determiniert bleibt.

Welche Schutzmaßnahmen sollte ich hinzufügen, bevor ich einen Paginierungsendpunkt ausliefere?

Setze ein maximales limit durch, validiere Filter- und Sort-Parameter und mache Cursor-Tokens undurchsichtig und strikt validiert. Wenn du Admin-Oberflächen in AppMaster erstellst, hilft Konsistenz dieser Regeln über alle Listenendpunkte hinweg, damit du dasselbe Tabellen- und Infinite-Scroll-Verhalten ohne individuelle Anpassungen wiederverwenden kannst.

Einfach zu starten
Erschaffe etwas Erstaunliches

Experimentieren Sie mit AppMaster mit kostenlosem Plan.
Wenn Sie fertig sind, können Sie das richtige Abonnement auswählen.

Starten