Close Menu
xodus.dexodus.de
    xodus.dexodus.de
    • Blockchain
    • Hardware
    • Internet of Things
    • Künstliche Intelligenz
    • Open Source
    • Robotik
    • Sicherheit
    • Software
    xodus.dexodus.de
    Home»Software»API-Pagination richtig umsetzen – Cursor statt Offset
    Software

    API-Pagination richtig umsetzen – Cursor statt Offset

    xodusxodus22. Januar 2026
    Facebook Twitter Pinterest LinkedIn Email Reddit Telegram WhatsApp
    API-Pagination richtig umsetzen – Cursor statt Offset
    API-Pagination richtig umsetzen – Cursor statt Offset

    Ein typisches API-Problem aus der Praxis: Eine Liste wird mit offset und limit paginiert, während im Hintergrund neue Datensätze entstehen oder bestehende aktualisiert werden. Nutzer:innen sehen doppelte Einträge, überspringen Elemente oder erhalten bei identischem Request unterschiedliche Ergebnisse. Das ist kein Edge-Case, sondern die normale Folge aus parallelen Writes, nicht-deterministischer Sortierung und fehlender Stabilität über mehrere Seiten.

    Sauber gelöst wird das meist mit Cursor-Pagination: Statt „Seite 5“ zu adressieren, wird „ab dem letzten gesehenen Element“ abgefragt. Das verschiebt die Verantwortung von einer abstrakten Seitenzahl hin zu einer konkreten Position in einer stabil sortierten Menge. Entscheidend ist dabei nicht nur das API-Design, sondern vor allem die Datenbank-Query, die Sortierregeln und die Indexierung.

    Warum Offset/Limit in produktiven APIs brüchig wird

    Writes zwischen den Seiten: Duplikate und Lücken sind logisch

    Offset-Pagination arbeitet mit einem relativen Index: OFFSET 40 LIMIT 20 bedeutet „überspringe die ersten 40 Treffer der aktuellen Ergebnismenge“. Wenn sich die Ergebnismenge zwischen Request 1 und Request 2 verändert (Insert/Delete/Update, das die Sortierung beeinflusst), verschiebt sich die Reihenfolge. Ein Element kann auf eine frühere Position rutschen und dadurch auf der nächsten Seite erneut erscheinen. Umgekehrt können Elemente nach hinten rutschen und dadurch vollständig übersprungen werden.

    Das Problem tritt auch auf, wenn keine Datensätze gelöscht werden: Schon ein Insert, das in der Sortierung „vor“ der aktuellen Position landet, verschiebt alles dahinter um 1. Offset ist dann kein stabiler Marker, sondern eine Momentaufnahme.

    Fehlende deterministische Sortierung: „ORDER BY created_at“ reicht oft nicht

    Ein häufiger Fehler ist eine Sortierung über ein Feld, das nicht eindeutig ist (z. B. Sekundenauflösung in created_at). Ohne eindeutigen Tie-Breaker ist die Reihenfolge innerhalb gleicher Werte nicht garantiert. Dann kann die Datenbank für zwei Requests unterschiedliche Reihenfolgen liefern, obwohl die Daten gleich sind. Pagination „zittert“: einzelne Zeilen wandern zwischen Seiten.

    Stabil wird es erst mit einer totalen Ordnung: neben dem Sortierfeld ein eindeutiges Feld (typisch id) als sekundärer Sortierschlüssel. Beispiel: ORDER BY created_at DESC, id DESC.

    Performance: große Offsets sind teuer

    Große Offsets führen dazu, dass die Datenbank viele Zeilen „durchlaufen“ muss, bevor sie die eigentlichen Treffer liefert. Selbst mit Index kann das bedeuten: scannen, verwerfen, dann erst liefern. Cursor-basierte Abfragen können dagegen direkt „ab Position X“ starten, sofern ein passender Index existiert.

    Cursor-Pagination: Prinzip, Begriffe und typische API-Form

    Keyset statt Offset: „größer/kleiner als letzter Schlüssel“

    Cursor-Pagination wird auch Keyset-Pagination genannt. Die Idee: Der Client erhält nach einer Seite einen Cursor (Token), der die Position beschreibt. Der nächste Request liefert Einträge „nach“ diesem Cursor, z. B. alle Datensätze mit (created_at, id) kleiner als der letzte Datensatz der vorherigen Seite (bei absteigender Sortierung).

    Eine typische API-Form:

    • GET /items?limit=50 → liefert 50 Einträge + next_cursor
    • GET /items?limit=50&cursor=... → nächste Seite

    Wichtig ist: Der Cursor ist kein Seitenzähler, sondern ein Positionsmarker. Damit bleibt die Folge stabil, selbst wenn neue Elemente „vorne“ dazukommen.

    Cursor-Token: opaque vs. lesbar

    In der Praxis hat sich ein „opaque“ Token bewährt: Der Client behandelt den Cursor als Blackbox. Intern kann er die relevanten Sortierwerte enthalten (z. B. Zeitstempel + ID) und zusätzlich eine Versionskennung. Lesbare Cursor (z. B. als Query-Parameter created_at=...&id=...) funktionieren auch, werden aber leichter falsch zusammengesetzt oder per Copy/Paste verfälscht. Opaque Tokens erlauben außerdem eine kontrollierte Weiterentwicklung.

    Stabile Sortierung und Datenbank-Queries ohne Überraschungen

    Sortierregeln: Eindeutig, konsistent, dokumentiert

    Cursor-Pagination funktioniert nur so gut wie die Sortierdefinition. Eine robuste Regel ist: primär nach einem monotonic-like Feld (oft created_at), sekundär nach einer eindeutigen ID. Dabei muss die API klar festlegen, ob auf- oder absteigend sortiert wird und ob Filter die Sortierreihenfolge beeinflussen.

    In der API-Doku sollte außerdem stehen, dass der Cursor an genau diese Sortierung gebunden ist. Wird die Sortierung serverseitig geändert, müssen alte Cursor kontrolliert behandelt werden (z. B. als ungültig markieren oder per Version migrieren).

    SQL-Beispiel: „Seek“ mit zusammengesetztem Schlüssel

    Angenommen, es wird absteigend nach created_at und id sortiert. Dann lautet das Muster:

    WHERE (created_at, id) < (:cursor_created_at, :cursor_id)
    ORDER BY created_at DESC, id DESC
    LIMIT :limit

    Viele Datenbanken unterstützen Tupelvergleiche; wenn nicht, lässt sich das logisch äquivalent formulieren:

    WHERE created_at < :t
    OR (created_at = :t AND id < :id)

    Damit wird genau „alles nach dem letzten Element“ geliefert. Keine Offsets, keine Seitenzahlen, und die Abfrage kann indexfreundlich bleiben.

    Indexierung: ohne passenden Index wird auch Cursor langsam

    Für die oben genannte Abfrage braucht es typischerweise einen zusammengesetzten Index, der die Sortierung abbildet. Beispiel: Index auf (created_at DESC, id DESC) oder in aufsteigender Reihenfolge, abhängig von Datenbank und Optimizer. Die Kernidee: Filter + Sortierung müssen möglichst „aus dem Index“ bedient werden, sonst entstehen Sort-Operationen oder breite Scans.

    Wenn zusätzliche Filter existieren (z. B. status oder tenant_id), ist oft ein weiterer zusammengesetzter Index nötig, der mit dem häufigsten Zugriffsmuster harmoniert. Hier lohnt ein Blick auf reale Query-Pläne im Staging – nicht auf Vermutungen.

    Cursor-Design: Token-Versionierung, Validierung, Sicherheit

    Welche Daten gehören in den Cursor?

    Minimal müssen alle Werte enthalten sein, die die Position in der Sortierung bestimmen: z. B. created_at und id. Optional können Filterparameter aufgenommen werden, wenn sichergestellt werden soll, dass Cursor nicht mit anderen Filtern wiederverwendet werden. Das ist vor allem bei Multi-Tenant-APIs sinnvoll: Cursor sollten nicht zwischen Mandanten „umherschweifen“ können.

    Praktisch ist eine kleine Struktur wie:

    • Version (z. B. v=1)
    • Sort-Key 1 (z. B. Zeitstempel)
    • Sort-Key 2 (z. B. ID)
    • Optional: Hash/Signatur über relevante Parameter

    Cursor validieren und Fehlerszenarien sauber behandeln

    Ein Cursor ist Eingabe – und kann kaputt sein. Gute APIs reagieren mit klaren 4xx-Fehlern (z. B. „invalid_cursor“), nicht mit 500. Außerdem sollte die Implementierung robust gegen:

    • falsches Format (kein gültiges Base64/JSON)
    • nicht parsebare Datumswerte
    • Cursor-Version unbekannt
    • Cursor passt nicht zu aktuellen Query-Parametern

    In Systemen mit strikter API-Validierung passt thematisch der Ansatz aus Schema-Validation für APIs: Eingaben früh ablehnen, bevor Query-Building oder Datenbankzugriff starten.

    Manipulationsschutz: Cursor nicht als Trust Boundary behandeln

    Opaque Cursor werden oft einfach base64-kodiert. Das ist keine Sicherheit. Wenn Cursor sensible Informationen enthalten (z. B. interne IDs, Tenant-IDs) oder wenn Missbrauch teuer werden kann (z. B. Cursor zeigt absichtlich weit zurück, um Last zu erzeugen), braucht es Integritätsschutz, etwa durch eine serverseitige Signatur (HMAC) über die Cursor-Payload. Alternativ kann der Cursor rein serverseitig gespeichert werden (z. B. kurzlebig), was aber Betriebsaufwand und State erzeugt.

    Praxisdetails: Filter, bidirektionales Blättern und Konsistenz

    Filter kombinieren: Cursor muss zum Filter passen

    Ein Klassiker: Erst Seite 1 ohne Filter laden, dann Seite 2 mit Filter (oder umgekehrt) – und der Cursor wird trotzdem wiederverwendet. Die Antwort ist dann unvorhersehbar. Sauber ist: Cursor entweder an den vollständigen Query-Kontext binden (Filterwerte in Cursor integrieren und signieren) oder die API verlangt, dass der Cursor nur mit identischen Parametern genutzt werden darf und verweigert sonst die Anfrage.

    Vorwärts und rückwärts: zwei Cursor statt „page=-1“

    Für UIs mit „Zurück“-Navigation kann die API neben next_cursor auch prev_cursor liefern. Rückwärts-Pagination ist nicht trivial, weil die Sortierung invertiert und die Ergebnismenge danach wieder umgedreht werden muss. Implementierungen scheitern oft an Details wie: „vorherige Seite“ soll genau die vorher gesehenen Elemente liefern, nicht nur „irgendwelche davor“.

    Konsistenzanspruch klären: Snapshot vs. „best effort“

    Cursor-Pagination liefert stabile Reihenfolge relativ zur Sortierung, aber keinen vollständigen Snapshot über alle Seiten. Wenn während des Scrollens Datensätze ändern, kann das zu „verschobenen“ Positionen führen, je nachdem ob Updates sortierrelevant sind. Für echte Snapshots braucht es meist zusätzliche Mechanismen (z. B. eine feste Sicht über eine Revision/Export-ID oder eine Transaktions-Snapshot-Strategie), was die Komplexität deutlich erhöht.

    Umsetzungsschritte, die in Teams funktionieren

    Konkrete Reihenfolge für eine saubere Einführung

    • Sortierung festlegen und total ordnen (primär + Tie-Breaker), inklusive Dokumentation im API-Vertrag.
    • Query als Keyset-Abfrage implementieren und mit realistischen Filtern testen.
    • Passende Indizes anlegen und Query-Pläne prüfen, bevor Traffic umgestellt wird.
    • Cursor-Format versionieren und Validierung zentral implementieren (z. B. im Request-Layer).
    • Fehlercodes und Edge-Cases definieren (invalid_cursor, cursor_mismatch, limit_out_of_range).
    • Client-Verhalten prüfen: Caching, Retry-Logik und parallele Requests (z. B. schnelles Scrollen).

    Typische Stolpersteine aus realen Backends

    Limit zu groß, Payload zu schwer: Pagination ist kein Export

    Pagination wird häufig zweckentfremdet, um „alle Daten“ zu ziehen. Das führt zu übergroßen Limits, langen Response-Zeiten und unnötiger Last. Hier hilft eine klare Obergrenze für limit und ein separater Export-Mechanismus (asynchron, mit Job-Tracking). Für die Entkopplung solcher Hintergrundprozesse passt konzeptionell Async Jobs im Backend.

    Timeouts und Retries: Pagination verstärkt langsame Endpunkte

    Wenn jede Seite langsam ist, multipliziert sich das Problem: UIs feuern mehrere Requests, Clients retryen bei Timeouts, und der Endpunkt wird zum Hotspot. Damit Pagination nicht zum Verstärker wird, lohnt sauberes Timeout-Handling und das Prüfen von Worst-Case-Latenzen. Dazu passt die Einordnung aus Request-Timeouts im Backend.

    Tests: Korrektheit braucht Daten, die sich währenddessen ändern

    Unit-Tests auf Query-Builder-Ebene sind wichtig, aber nicht ausreichend. Für Pagination sollten Integrationstests bewusst parallele Inserts/Updates simulieren: Seite 1 laden, dazwischen Inserts, Seite 2 laden. Erwartung: keine Duplikate, keine Lücken bezogen auf die Sortierlogik; Cursor-Invalidierung sauber; deterministische Reihenfolge bei gleichen Timestamps dank Tie-Breaker. In Multi-Tenant-Systemen zusätzlich testen, dass Cursor nicht tenant-übergreifend akzeptiert werden.

    Wann Offset trotzdem okay ist

    Statische Daten und kleine Datenmengen

    Offset/Limit kann ausreichend sein, wenn die Daten praktisch unverändert sind (z. B. historisierte Tabellen), die Offsets klein bleiben und es keine hohen Konsistenzanforderungen gibt. Auch für Admin-Tools mit niedriger Nutzung kann das vertretbar sein. Wichtig ist dann trotzdem: deterministische Sortierung und eine klare Grenze, ab wann Offset zu teuer wird.

    „Springe zu Seite 20“ als Produktanforderung

    Cursor-Pagination ist schlecht darin, direkt „Seite N“ anzuspringen, weil es keine Seite gibt, nur Positionen. Wenn „Seite 20“ zwingend ist (z. B. bei bestimmten UX-Anforderungen), kann eine hybride Lösung nötig sein: etwa serverseitige Such-/Filterfunktionen, die das „Anspringen“ über einen anderen Schlüssel ermöglichen, oder ein sekundärer Index, der gezielt Positionen berechnet. Das sollte als Produktentscheidung betrachtet werden, nicht als reine Technikfrage.

    Mehr Einordnung zu verwandten Backend-Themen findet sich unter Software & Entwicklung.

    Previous ArticleSafety-Mutings in Robotikzellen – Materialfluss sicher lösen
    Next Article Open-Source-CI/CD aufbauen – Jenkins, Woodpecker, Tekton
    Avatar-Foto
    xodus
    • Website

    Xodus steht für fundierte Beiträge zu Künstlicher Intelligenz, Blockchain-Technologien, Hardware-Innovationen, IT-Sicherheit und Robotik.

    AUCH INTERESSANT

    Database-Backups testen – Restore-Drills ohne böse Überraschung

    8. März 2026

    Frontend-Performance messen – Web Vitals praxisnah nutzen

    3. März 2026

    Datenbank-Transaktionen im Backend – Isolation richtig wählen

    21. Februar 2026
    KOSTENLOS ABONNIEREN

    Newsletter

    DANKE! Du bist eingetragen.

    Newsletter-Anmeldung. Abmeldung jederzeit möglich. Datenschutzerklärung.

    AKTUELLE THEMEN

    Sicherer Umgang mit QR-Codes – Quishing erkennen

    15. März 2026

    PC-Netzteil richtig anschließen – Kabel, Stecker, Sicherheit

    14. März 2026

    Pendle Finance – Yield-Trading mit Principal und Yield Token

    13. März 2026

    IoT im Factory-Reset – Daten sicher löschen und neu koppeln

    11. März 2026

    PC friert ein ohne Bluescreen – Ursachen sicher eingrenzen

    9. März 2026
    • Impressum
    • Datenschutzerklärung
    © 2026 xodus.de. Alle Rechte vorbehalten.

    Type above and press Enter to search. Press Esc to cancel.

    Diese Website benutzt Cookies. Wenn du die Website weiter nutzt, gehen wir von deinem Einverständnis aus.