Offset Pagination — Paginierung mit Versatz — eine Methode zum seitenweisen Laden von Daten über HTTP API. Der Client sendet die Parameter offset (Versatz vom Anfang) und limit (Seitengröße), und der Server gibt Datensätze ab der Offset-Position zurück. Laut dem REST API Tutorial wird dieser Ansatz aufgrund seiner einfachen Implementierung häufig in RESTful-Diensten verwendet. Bei großen Datenmengen verliert die Offset-Paginierung jedoch aufgrund des vollständigen Tabellenscans bis zur gewünschten Position an Leistung.
Wichtige Punkte
Offset Pagination ist eine Methode zur seitenweisen Aufteilung von Daten, bei der die Client-Anfrage zwei Parameter enthält: offset (wie viele Datensätze übersprungen werden sollen) und limit (wie viele Datensätze zurückgegeben werden sollen). Der Server führt eine SQL-Abfrage mit OFFSET und LIMIT aus, überspringt die angegebene Anzahl von Zeilen und gibt eine Ergebnismenge fester Größe zurück.
Die Methode entstand in relationalen Datenbanken als einfachste Möglichkeit, die Seitennavigation zu organisieren, und wurde mit der Entwicklung der REST-Architektur auf HTTP-APIs übertragen. Offset Pagination erfordert keine Zustandsspeicherung auf dem Server — jede Anfrage ist unabhängig und enthält alle für die Abfrage notwendigen Informationen.
Laut dem API-Design-Bericht von Postman (2025) wird die Offset-Paginierung in 72% der öffentlichen REST-APIs verwendet, was sie trotz bekannter Leistungseinschränkungen bei großen Datenmengen zum dominierenden Standard macht.
Eine typische REST-Anfrage mit Offset Pagination enthält die Abfrageparameter offset und limit. Die Antwort enthält die Datensatzliste der angeforderten Seite und Metadaten zum Aufbau der Navigationsoberfläche.
Der limit-Parameter begrenzt die Anzahl der zurückgegebenen Datensätze und schützt Server und Client vor übermäßiger Belastung. Typische limit-Werte liegen zwischen 10 und 50 Datensätzen pro Seite, abhängig von der Datenkomplexität.
data class PageRequest(
val offset: Int,
val limit: Int
)
data class PageResponse<T>(
val items: List<T>,
val total: Int,
val hasMore: Boolean
)
fun RetrofitApi.fetchPage(request: PageRequest): Call<PageResponse<Item>>
Offset Pagination wird in eine SQL-Abfrage mit OFFSET- und FETCH-NEXT-Konstrukten (oder LIMIT in MySQL/SQLite) übersetzt. Der Datenbankserver scannt die Tabelle, überspringt die Anzahl der Zeilen, die dem Offset entspricht, und gibt die nächsten limit Zeilen zurück. Je größer der Offset, desto länger dauert die Abfrage.
Das Leistungsproblem liegt darin, dass die Datenbank nicht direkt zur Offset-Position springen kann — sie muss alle vorherigen Zeilen lesen und verwerfen. Bei offset = 100000 und limit = 20 liest das DBMS 100.020 Zeilen und gibt nur 20 zurück.
SQL — die Sprache, in der der Server die Offset-Paginierung ausführt. PostgreSQL und MySQL verwenden LIMIT, während SQL Server und Oracle OFFSET...FETCH verwenden. Unterschiedliche DBMS optimieren diese Abfrage unterschiedlich, aber das grundlegende Scan-Problem bleibt dasselbe.
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
OFFSET 100000 ROWS
FETCH NEXT 20 ROWS ONLY;
Datenkonsistenz ist der Hauptnachteil von Offset Pagination bei der Arbeit mit dynamischen Datensätzen. Wenn zwischen zwei Benutzeranfragen ein neuer Datensatz am Anfang der Tabelle hinzugefügt wird, verschieben sich alle vorhandenen Datensätze. Der Benutzer sieht Duplikate oder Lücken.
Stellen Sie sich eine Tabelle mit 100 Datensätzen und limit = 20 vor. Auf Seite 1 sieht der Benutzer die Datensätze 1-20. Ein Administrator fügt 5 neue Datensätze hinzu. Auf Seite 2 sieht der Benutzer die Datensätze 26-45 anstelle der erwarteten 21-40 — die Datensätze 21-25 werden übersprungen, und die Datensätze 21-25 aus dem vorherigen Satz werden auf Seite 1 dupliziert.
Cursor-basierte Paginierung ist eine Alternative zur Offset Pagination, die einen Zeiger auf den letzten Datensatz der aktuellen Seite verwendet. Anstelle eines numerischen Versatzes sendet der Client die Kennung des zuletzt erhaltenen Datensatzes, und der Server gibt die nächsten N Datensätze danach zurück.
Der cursor-basierte Ansatz löst das Konsistenzproblem: Die Cursor-Position ändert sich bei Einfügungen oder Löschungen nicht, da der Cursor auf einen bestimmten Datensatz und nicht auf eine Position verweist. Er ist jedoch aufwändiger zu implementieren — er erfordert ein eindeutiges sortierbares Feld (normalerweise ID oder Timestamp).
| Parameter | Offset Pagination | Cursor-basierte Pagination |
|---|---|---|
| Einfachheit | Hoch — zwei numerische Parameter | Mittel — Cursor-Codierung erforderlich |
| Konsistenz | Niedrig — Duplikate beim Einfügen | Hoch — Cursor unbeeinflusst von Änderungen |
| Leistung | Nimmt mit großem Offset ab | Stabil bei jedem Volumen |
| Sprung zur Seite | Ja — kann zu jeder Seite navigieren | Nein — nur sequenzielle Navigation |
| Geeignet für | Tabellen <10K Datensätze, UI mit Seitenzahlen | Feeds, unendliches Scrollen, große Datensätze |
Die Wahl zwischen den Ansätzen hängt von den Oberflächen-Anforderungen des Benutzers ab. Wenn eine Navigation mit Seitenzahlen und direktem Sprung benötigt wird — ist Offset Pagination einfacher. Für unendliches Scrollen oder Nachrichten-Feeds sind Cursor vorzuziehen.
Keyset-Paginierung ist eine Variante des cursor-basierten Ansatzes, bei der die Filterung über einen eindeutigen Schlüssel mit WHERE anstelle von OFFSET erfolgt. Die SQL-Abfrage verwendet eine Bedingung wie WHERE id > lastId, die es der Datenbank ermöglicht, einen Index zu verwenden, ohne verworfene Zeilen zu scannen.
Laut PostgreSQL Wiki läuft die Keyset-Paginierung bei großen Versätzen 100-1000 Mal schneller als Offset-Abfragen, da die Index-Suche den vollständigen Tabellenscan ersetzt. Der Nachteil ist die Unmöglichkeit, ohne sequenziellen Durchlauf zu einer beliebigen Seite zu springen.
Offset Pagination ist optimal für kleine bis mittlere Datensätze (bis zu 10.000 Datensätze), bei denen der Benutzer eine Seitenzahl-Oberfläche benötigt. Typische Szenarien sind Admin-Panels, Bestelllisten und gefilterte Kataloge mit seitenbasierter Paginierung.
Für mobile Anwendungen ist die Offset-Paginierung beim Laden historischer Daten geeignet, bei denen neue Einfügungen selten oder unmöglich sind — zum Beispiel Benutzerbestellverlauf, abgeschlossene Aufgabenlisten oder Transaktionsarchive. In diesen Szenarien tritt das Konsistenzproblem nicht auf.
Nicht empfohlen für Social-Media-Feeds, Kommentarlisten, Chats und andere dynamische Datensätze mit häufigen Einfügungen. In diesen Fällen beeinträchtigen Lücken und doppelte Datensätze die Benutzererfahrung und erfordern zusätzliche Deduplizierungslogik auf dem Client.
Hybride Paginierung kombiniert Offset und Cursor: Die erste Anfrage verwendet Offset, um die Startseite anzuzeigen, während nachfolgende Anfragen Cursor für das unendliche Scrollen verwenden. Dieser Ansatz wird bei Instagram und Twitter verwendet, wo die erste Seite per Cursor geladen wird, der Offset jedoch zur Berechnung der Position beim Zurückkehren zu einer vorherigen Ansicht verwendet wird.
Die Implementierung eines hybriden Ansatzes erfordert das Speichern der virtuellen Position des Benutzers auf dem Client und die Koordinierung zweier Paginierungsmechanismen auf dem Server. Laut dem Instagram Engineering Blog verwendet ihr Team cursor-basierte Paginierung mit einem zusätzlichen startCursor-Feld, das den Offset für das anfängliche Laden ersetzt.
Mobile Anwendungen verwenden Offset Pagination zusammen mit Retrofit/OkHttp auf Android und URLSession/Combine auf iOS. Das typische Muster ist das Laden der nächsten Seite beim Scrollen zum Ende der Liste über RecyclerView.OnScrollListener oder UICollectionView-Prefetching.
Die Implementierung der Offset-Paginierung auf einem mobilen Client umfasst drei Komponenten: einen Paginierungsmanager (speichert aktuellen Offset und hasMore), einen Listenadapter (zeigt Elemente und Ladeindikator an) und ein Repository (führt Anfragen aus und behandelt Fehler). Android Jetpack bietet die Paging 3 Bibliothek, die sowohl Offset- als auch cursor-basierte Paginierung von Haus aus unterstützt.
Paging 3 ist eine Android Jetpack Bibliothek für seitenweises Laden von Daten. Sie kapselt die Paginierungslogik, einschließlich Offset-Verfolgung, Ladezustandsverwaltung und automatisches Vorladen beim Scrollen. PagingSource definiert Schlüssel für die nächsten und vorherigen Seiten.
class OffsetPagingSource(
private val api: ApiService,
private val limit: Int = 20
) : PagingSource<Int, Item>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, Item> {
val offset = params.key ?: 0
return try {
val response = api.getItems(offset, limit)
LoadResult.Page(
data = response.items,
prevKey = null,
nextKey = if (response.hasMore) offset + limit else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
PagingSource definiert die Schlüssel prevKey und nextKey für die Seitennavigation. Bei der Offset-Paginierung ist prevKey immer null (kann nicht zur vorherigen Seite navigieren ohne Verlaufsspeicherung), während nextKey bei jedem Laden um limit erhöht wird, bis der Server hasMore = false zurückgibt. Dies ist ein einfaches und vorhersagbares Modell für mobile Listen.
Erster Fehler — sich auf die Reihenfolge der Datensätze ohne Sortierung zu verlassen. Offset Pagination erfordert eine stabile ORDER BY-Sortierung nach einem eindeutigen Feld. Ohne diese kann das DBMS Datensätze in beliebiger Reihenfolge zurückgeben, was zu zufälligen Duplikaten und Lücken zwischen den Seiten führt.
Zweiter Fehler — die Verwendung von Offset zur Berechnung der Seitennummer in der Benutzeroberfläche. Die Formel page = offset / limit + 1 funktioniert nur, wenn zwischen den Ladevorgängen kein Datensatz gelöscht oder hinzugefügt wurde. Bei dynamischen Daten wird die Seitennummer ungenau und der Benutzer sieht falsche Informationen.
Dritter Fehler — das Ignorieren von Timeouts bei Abfragen mit großem Offset. Bei einem Offset über 100.000 kann die Abfrage dutzende Sekunden dauern, die Benutzeroberfläche blockieren und Serverressourcen verbrauchen. Es wird empfohlen, einen maximalen Offset-Wert auf API-Ebene festzulegen (z.B. 10.000) und für große Volumen cursor-basierte Paginierung zu verwenden.
Vierter Fehler — das Fehlen von total count in der Antwort. Ohne die Gesamtzahl der Datensätze kann der Client die Seitenanzahl nicht anzeigen und keine nummerierte Paginierung implementieren. Allerdings ist COUNT(*) bei großen Tabellen teuer — für Datensätze über 100.000 Datensätze verwenden Sie Näherungsschätzungen oder begrenzen Sie den maximalen Gesamtwert.
Häufig gestellte Fragen
Offset verwendet einen numerischen Versatz zum Überspringen von Datensätzen, während Cursor einen Zeiger auf den letzten Datensatz der vorherigen Seite verwendet. Offset ist einfacher zu implementieren, leidet aber unter Duplikaten beim Einfügen und Leistungseinbußen bei großen Versätzen. Cursor ist bei allen Datenänderungen stabil.
Die Offset-Paginierung ist bei Offsets über 10.000 Datensätzen aufgrund des vollständigen Tabellenscans ineffizient. Sie ist auch für dynamische Datensätze (Feeds, Chats) ungeeignet, bei denen zwischen Anfragen neue Datensätze erscheinen — der Benutzer sieht Lücken und doppelte Datensätze bei der Navigation.
Der optimale Limit hängt von der Datensatzgröße und Netzwerkgeschwindigkeit ab — 10 bis 50 Elemente pro Seite. Für Listen mit großen Bildern verwenden Sie limit = 10-15; für Textdaten 20-50. Erlauben Sie dem Client immer, seinen eigenen Limit mit einer maximalen Begrenzung auf dem Server (normalerweise 100) anzugeben.
Zur Behandlung von Duplikaten verwenden Sie clientseitige Deduplizierung nach eindeutiger ID, wenden Sie stabiles Sortieren nach einem eindeutigen Feld an oder wechseln Sie zur cursor-basierten Paginierung. Android Paging 3 unterstützt key zur automatischen Deduplizierung von Listenelementen.
Ja, GraphQL unterstützt Offset-Paginierung über die Argumente offset und limit in Abfragen, obwohl die Relay-Spezifikation den cursor-basierten Ansatz empfiehlt. Die Bibliotheken Apollo GraphQL und Relay bieten integrierte Unterstützung für Offset-Paginierung mit automatischer Seitenzustandsverwaltung.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch