TL;DR
Für große Datensätze verwenden Sie Cursor-basierte oder Keyset-Paginierung anstelle der Offset-basierten Paginierung. Offset-Paginierung (?page=1&limit=20) funktioniert bei Millionen von Datensätzen schlecht und führt zu Dateninkonsistenzen. Die moderne PetstoreAPI implementiert Cursor-basierte Paginierung mit undurchsichtigen Token und HATEOAS-Links für effiziente, konsistente Ergebnisse.
Einleitung
Ihre API gibt eine Liste von Haustieren zurück. Sie haben 10 Millionen Haustiere in der Datenbank. Ein Client fragt GET /pets?page=500000&limit=20 an. Ihre Datenbank führt OFFSET 10000000 LIMIT 20 aus. Die Abfrage dauert 30 Sekunden. Ihre API läuft in einen Timeout.
Dies ist das Problem der Offset-Paginierung. Sie funktioniert gut für kleine Datensätze, bricht aber bei Skalierung zusammen. Die Datenbank muss Millionen von Zeilen scannen, um den Offset zu erreichen, obwohl Sie nur 20 Ergebnisse zurückgeben.
Der alte Swagger Petstore behandelt Paginierung überhaupt nicht. Die moderne PetstoreAPI implementiert Cursor-basierte Paginierung, die auf Millionen von Datensätzen mit konsistenter Leistung skaliert.
App herunterladen
In diesem Leitfaden erfahren Sie, warum die Offset-Paginierung fehlschlägt, wie die Cursor-basierte Paginierung funktioniert und wie die moderne PetstoreAPI eine effiziente Paginierung implementiert.
Warum die Offset-Paginierung bei Skalierung fehlschlägt
Offset-Paginierung ist der gängigste Ansatz, hat aber ernsthafte Probleme.
Wie Offset-Paginierung funktioniert
GET /pets?page=1&limit=20 → OFFSET 0 LIMIT 20
GET /pets?page=2&limit=20 → OFFSET 20 LIMIT 20
GET /pets?page=3&limit=20 → OFFSET 40 LIMIT 20
Die Datenbank überspringt offset Zeilen und gibt limit Zeilen zurück.
Problem 1: Leistung verschlechtert sich mit der Seitenzahl
Seite 1:
SELECT * FROM pets OFFSET 0 LIMIT 20;
-- Schnell: scannt 20 Zeilen
Seite 1000:
SELECT * FROM pets OFFSET 20000 LIMIT 20;
-- Langsam: scannt 20.020 Zeilen, gibt 20 zurück
Seite 500.000:
SELECT * FROM pets OFFSET 10000000 LIMIT 20;
-- Sehr langsam: scannt 10.000.020 Zeilen, gibt 20 zurück
Die Datenbank muss alle Zeilen bis zum Offset scannen, obwohl sie diese verwirft. Die Leistung verschlechtert sich linear mit der Seitenzahl.
Problem 2: Inkonsistente Ergebnisse
Während ein Client durch die Ergebnisse blättert, ändern sich die Daten:
Anfrage 1:
GET /pets?page=1&limit=2
Gibt zurück: [Haustier A, Haustier B]
Jemand fügt Haustier Z hinzu (wird alphabetisch zuerst sortiert)
Anfrage 2:
GET /pets?page=2&limit=2
Gibt zurück: [Haustier B, Haustier C] ← Haustier B erscheint zweimal!
Haustier B erschien auf beiden Seiten, weil ein neues Haustier eingefügt wurde. Umgekehrt können Haustiere übersprungen werden, wenn Löschungen vorgenommen werden.
Problem 3: Tiefe Paginierung ist teuer
Benutzer gehen selten über Seite 10 hinaus. Aber wenn Ihre API ?page=1000000 zulässt, müssen Sie damit umgehen. Tiefe Paginierungsabfragen sind teuer und können für Denial-of-Service-Angriffe verwendet werden.
Wann Offset-Paginierung akzeptabel ist
Offset-Paginierung funktioniert gut für:
- Kleine Datensätze (< 10.000 Einträge)
- Interne APIs mit kontrollierter Nutzung
- Admin-Oberflächen, bei denen Benutzer nicht tief paginieren
- Daten, die sich selten ändern
Für öffentliche APIs oder große Datensätze verwenden Sie Cursor-basierte Paginierung.
Cursor-basierte Paginierung erklärt
Cursor-basierte Paginierung verwendet einen undurchsichtigen Token, um die Position im Ergebnisdatensatz zu markieren.
Funktionsweise
Anfrage 1:
GET /pets?limit=20
Antwort 1:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9",
"hasMore": true
}
}
Anfrage 2:
GET /pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20
Der Cursor ist ein undurchsichtiger Token (üblicherweise base64-kodiert), der die Position kodiert. Der Client analysiert ihn nicht – er gibt ihn einfach zurück.
Vorteile
1. Konsistente Leistung
Die Datenbank verwendet einen Index, um die Cursor-Position direkt zu finden:
SELECT * FROM pets
WHERE id > '019b4132-70aa-764f-b315-e2803d882a24'
ORDER BY id
LIMIT 20;
Diese Abfrage ist unabhängig von der Position im Datensatz schnell. Sie verwendet eine Indexsuche, keinen Scan.
2. Konsistente Ergebnisse
Cursor sind stabil. Wenn sich Daten zwischen Anfragen ändern, erhalten Sie immer noch konsistente Ergebnisse. Neue Datensätze verursachen keine Duplikate oder Übersprünge.
3. Keine Angriffe durch tiefe Paginierung
Clients können nicht zu beliebigen Positionen springen. Sie müssen sequenziell paginieren, was Missbrauch einschränkt.
Cursor-Format
Cursor sind typischerweise base64-kodiertes JSON:
// Dekodierter Cursor
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"createdAt": "2026-03-13T10:30:00Z"
}
Der Cursor enthält genügend Informationen, um die Paginierung fortzusetzen. Für die moderne PetstoreAPI umfasst dies die Ressourcen-ID und das Sortierfeld.
Keyset-Paginierung für sortierte Daten
Keyset-Paginierung ist eine Variante der Cursor-basierten Paginierung für sortierte Daten.
Funktionsweise
Anstelle eines undurchsichtigen Cursors verwenden Sie den letzten Wert der vorherigen Seite:
Anfrage 1:
GET /pets?limit=20&sortBy=createdAt
Antwort 1:
{
"data": [
{"id": "...", "createdAt": "2026-03-13T10:00:00Z"},
...
{"id": "...", "createdAt": "2026-03-13T10:30:00Z"}
]
}
Anfrage 2:
GET /pets?limit=20&sortBy=createdAt&after=2026-03-13T10:30:00Z
Der Parameter after verwendet den letzten createdAt-Wert der vorherigen Seite.
SQL-Abfrage
SELECT * FROM pets
WHERE created_at > '2026-03-13T10:30:00Z'
ORDER BY created_at
LIMIT 20;
Dies ist effizient, da ein Index auf created_at verwendet wird.
Wann Keyset-Paginierung zu verwenden ist
- Daten sind natürlich sortiert (nach Zeitstempel, ID usw.)
- Clients müssen den Paginierungsschlüssel verstehen
- Sie möchten eine transparente Paginierung (keine undurchsichtigen Cursor)
Die moderne PetstoreAPI verwendet standardmäßig Cursor-basierte Paginierung, unterstützt aber Keyset-Paginierung für Zeitreihendaten.
Wie die moderne PetstoreAPI Paginierung implementiert
Die moderne PetstoreAPI verwendet Cursor-basierte Paginierung mit HATEOAS-Links.
Anfrageformat
GET /pets?limit=20
GET /pets?cursor={token}&limit=20
Parameter:
limit- Anzahl der Ergebnisse pro Seite (Standard: 20, Max: 100)cursor- Undurchsichtiger Paginierungs-Token aus der vorherigen Antwort
Antwortformat
{
"data": [
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"species": "CAT"
}
],
"pagination": {
"limit": 20,
"hasMore": true,
"nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9"
},
"links": {
"self": "https://petstoreapi.com/pets?limit=20",
"next": "https://petstoreapi.com/pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20"
}
}
Hauptmerkmale
1. Undurchsichtige Cursor
Cursor sind base64-kodiert. Clients analysieren sie nicht.
2. HATEOAS-Links
Das links-Objekt stellt gebrauchsfertige URLs bereit. Clients müssen keine Paginierungs-URLs konstruieren.
3. hasMore-Flag
Zeigt an, ob weitere Ergebnisse vorhanden sind. Clients wissen, wann sie mit der Paginierung aufhören müssen.
4. Limit-Validierung
Das maximale Limit beträgt 100. Verhindert, dass Clients riesige Seiten anfordern.
Die vollständigen Details finden Sie in der Paginierungsdokumentation der modernen PetstoreAPI.
Paginierungs-Antwortformat
Die moderne PetstoreAPI verpackt paginierte Antworten in einer konsistenten Struktur.
Sammlungs-Wrapper
{
"data": [...],
"pagination": {...},
"links": {...}
}
Warum Sammlungen verpacken?
- Erweiterbarkeit - Kann Metadaten hinzufügen, ohne Clients zu beeinträchtigen
- Konsistenz - Alle paginierten Endpunkte verwenden dasselbe Format
- HATEOAS - Links führen Clients durch die Paginierung
Paginierungs-Metadaten
"pagination": {
"limit": 20,
"hasMore": true,
"nextCursor": "...",
"totalCount": 1000 // Optional, teuer zu berechnen
}
totalCount ist optional, da die Berechnung bei großen Datensätzen teuer ist. Die meisten Clients benötigen es nicht.
Paginierung mit Apidog testen
Apidog hilft Ihnen, das Paginierungsverhalten umfassend zu testen.
Testszenarien
1. Erste Seite
GET /pets?limit=20
Erwartung: 20 Ergebnisse, hasMore=true, nextCursor vorhanden
2. Folgeseiten
GET /pets?cursor={token}&limit=20
Erwartung: 20 Ergebnisse, hasMore=true/false, nextCursor vorhanden/nicht vorhanden
3. Letzte Seite
GET /pets?cursor={lastToken}&limit=20
Erwartung: < 20 Ergebnisse, hasMore=false, kein nextCursor
4. Leere Ergebnisse
GET /pets?status=NONEXISTENT&limit=20
Erwartung: 0 Ergebnisse, hasMore=false, kein nextCursor
5. Limit-Validierung
GET /pets?limit=1000
Erwartung: 400 Bad Request (überschreitet maximales Limit)
Apidog-Testkonfiguration
// Test: Paginierungsstruktur
pm.test("Response has pagination", () => {
pm.expect(pm.response.json()).to.have.property('pagination');
pm.expect(pm.response.json().pagination).to.have.property('hasMore');
});
// Test: HATEOAS-Links
pm.test("Response has links", () => {
const links = pm.response.json().links;
pm.expect(links).to.have.property('self');
if (pm.response.json().pagination.hasMore) {
pm.expect(links).to.have.property('next');
}
});
Die richtige Paginierungsstrategie wählen
Verschiedene Strategien passen zu unterschiedlichen Anwendungsfällen.
Offset-Paginierung
Verwenden Sie, wenn:
- Datensatz klein ist (< 10.000 Einträge)
- Benutzer wahlfreien Zugriff benötigen (zu Seite 50 springen)
- Daten sich selten ändern
- Interne API mit kontrollierter Nutzung
Verwenden Sie nicht, wenn:
- Datensatz groß ist (> 100.000 Einträge)
- Leistung wichtig ist
- Daten sich häufig ändern
Cursor-basierte Paginierung
Verwenden Sie, wenn:
- Datensatz groß ist
- Leistung wichtig ist
- Daten sich häufig ändern
- Sequenzieller Zugriff ausreicht
Verwenden Sie nicht, wenn:
- Benutzer wahlfreien Zugriff benötigen
- Cursor-Komplexität ein Problem ist
Keyset-Paginierung
Verwenden Sie, wenn:
- Daten natürlich sortiert sind
- Transparente Paginierung bevorzugt wird
- Leistung wichtig ist
Verwenden Sie nicht, wenn:
- Sortierreihenfolge komplex ist
- Mehrere Sortierfelder benötigt werden
Empfehlung der modernen PetstoreAPI: Verwenden Sie Cursor-basierte Paginierung für öffentliche APIs und große Datensätze.
Fazit
Paginierung ist entscheidend für APIs, die große Datensätze zurückgeben. Offset-Paginierung ist einfach, skaliert aber nicht. Cursor-basierte Paginierung bietet konsistente Leistung und zuverlässige Ergebnisse für Millionen von Datensätzen.
Die moderne PetstoreAPI implementiert Cursor-basierte Paginierung mit undurchsichtigen Token, HATEOAS-Links und geeigneten Metadaten. Dieses Design skaliert effizient und bietet eine großartige Entwicklererfahrung.
Testen Sie Ihre Paginierungsimplementierung mit Apidog, um sicherzustellen, dass sie Grenzfälle behandelt, Limits validiert und konsistente Ergebnisse zurückgibt.
Wichtige Erkenntnisse:
- Vermeiden Sie Offset-Paginierung für große Datensätze
- Verwenden Sie Cursor-basierte Paginierung für Skalierbarkeit
- Verpacken Sie Sammlungen mit Metadaten und Links
- Testen Sie die Paginierung gründlich mit Apidog
- Folgen Sie den Paginierungsmustern der modernen PetstoreAPI
App herunterladen
FAQ
Warum nicht einfach alle Ergebnisse ohne Paginierung zurückgeben?
Das Zurückgeben von Millionen von Datensätzen in einer einzigen Antwort führt zu Speicherproblemen, langsamem Netzwerktransfer und einer schlechten Benutzererfahrung. Paginierung ist für große Datensätze unerlässlich.
Können Clients mit Cursor-Paginierung zu einer bestimmten Seite springen?
Nein, Cursor-Paginierung erfordert sequenziellen Zugriff. Wenn wahlfreier Zugriff erforderlich ist, sollten Sie die Offset-Paginierung für kleine Datensätze in Betracht ziehen oder stattdessen eine Suche/Filterung implementieren.
Wie gehe ich mit Paginierung und Filterung um?
Fügen Sie Filterparameter in Paginierungsanfragen ein: GET /pets?status=AVAILABLE&cursor={token}&limit=20. Der Cursor kodiert sowohl die Position als auch den Filterstatus.
Sollte ich die Gesamtzahl in Paginierungsantworten aufnehmen?
Nur wenn Clients es benötigen und Ihr Datensatz klein ist. Die Berechnung der Gesamtzahl ist bei großen Datensätzen teuer (erfordert eine separate COUNT-Abfrage).
Wie implementiere ich Cursor-Paginierung in SQL?
Verwenden Sie eine WHERE-Klausel mit dem Cursor-Wert: SELECT * FROM pets WHERE id > ? ORDER BY id LIMIT 20. Stellen Sie sicher, dass Sie einen Index auf der Sortierspalte haben.
Was, wenn meine Cursor-Token ungültig werden?
Geben Sie 400 Bad Request mit einer Fehlermeldung zurück. Cursor können ungültig werden, wenn Daten gelöscht werden oder der Paginierungsstatus abläuft.
Wie lange sollten Cursor gültig bleiben?
Cursor der modernen PetstoreAPI sind unbegrenzt gültig, solange die referenzierte Ressource existiert. Einige APIs lassen Cursor nach 24 Stunden ablaufen.
Kann ich Cursor-Paginierung mit mehreren Sortierfeldern verwenden?
Ja, aber der Cursor muss alle Sortierfelder kodieren. Dies macht Cursor komplexer. Erwägen Sie stattdessen die Verwendung eines einzelnen zusammengesetzten Sortierschlüssels.
