354 Stimmen

Bewährte API-Paginierungsverfahren

Ich würde gerne etwas Hilfe bei der Behandlung eines seltsamen Randfalls mit einer paginierten API, die ich gerade aufbaue.

Wie viele APIs paginiert auch diese große Ergebnisse. Wenn Sie /foos abfragen, erhalten Sie 100 Ergebnisse (d. h. foo #1-100) und einen Link zu /foos?page=2, der foo #101-200 liefern sollte.

Wenn jedoch foo #10 aus dem Datensatz gelöscht wird, bevor der API-Konsument die nächste Abfrage stellt, wird /foos?page=2 um 100 versetzt und foos #102-201 zurückgegeben.

Dies ist ein Problem für API-Konsumenten, die versuchen, alle foos zu ziehen - sie werden foo #101 nicht erhalten.

Wie ist das am besten zu handhaben? Wir würden es gerne so einfach wie möglich machen (d.h. keine Sitzungen für API-Anfragen). Beispiele aus anderen APIs wären uns sehr willkommen!

13voto

phauer Punkte 141

Option A: Paginierung des Schlüsselsatzes mit einem Zeitstempel

Um die von Ihnen erwähnten Nachteile der Offset-Paginierung zu vermeiden, können Sie die keyset-basierte Paginierung verwenden. Normalerweise haben die Entitäten einen Zeitstempel, der ihre Erstellungs- oder Änderungszeit angibt. Dieser Zeitstempel kann für die Paginierung verwendet werden: Übergeben Sie einfach den Zeitstempel des letzten Elements als Abfrageparameter für die nächste Anfrage. Der Server wiederum verwendet den Zeitstempel als Filterkriterium (z.B. WHERE modificationDate >= receivedTimestampParameter )

{
    "elements": [
        {"data": "data", "modificationDate": 1512757070}
        {"data": "data", "modificationDate": 1512757071}
        {"data": "data", "modificationDate": 1512757072}
    ],
    "pagination": {
        "lastModificationDate": 1512757072,
        "nextPage": "https://domain.de/api/elements?modifiedSince=1512757072"
    }
}

Auf diese Weise verpassen Sie kein einziges Element. Dieser Ansatz sollte für viele Anwendungsfälle ausreichend sein. Beachten Sie jedoch Folgendes:

  • Es kann zu Endlosschleifen kommen, wenn alle Elemente einer einzelnen Seite denselben Zeitstempel haben.
  • Es kann vorkommen, dass Sie viele Elemente mehrfach an den Kunden liefern, wenn sich Elemente mit demselben Zeitstempel auf zwei Seiten überschneiden.

Sie können diese Nachteile weniger wahrscheinlich machen, indem Sie die Seitengröße erhöhen und Zeitstempel mit Millisekundengenauigkeit verwenden.

Option B: Erweiterte Keyset-Paginierung mit einem Fortsetzungs-Token

Um die genannten Nachteile der normalen Paginierung von Keysets zu umgehen, können Sie dem Zeitstempel einen Offset hinzufügen und ein so genanntes "Continuation Token" oder "Cursor" verwenden. Der Offset ist die Position des Elements relativ zum ersten Element mit demselben Zeitstempel. Normalerweise hat das Token ein Format wie Timestamp_Offset . Sie wird in der Antwort an den Client weitergegeben und kann an den Server zurückgeschickt werden, um die nächste Seite abzurufen.

{
    "elements": [
        {"data": "data", "modificationDate": 1512757070}
        {"data": "data", "modificationDate": 1512757072}
        {"data": "data", "modificationDate": 1512757072}
    ],
    "pagination": {
        "continuationToken": "1512757072_2",
        "nextPage": "https://domain.de/api/elements?continuationToken=1512757072_2"
    }
}

Das Token "1512757072_2" verweist auf das letzte Element der Seite und besagt: "Der Client hat bereits das zweite Element mit dem Zeitstempel 1512757072 erhalten". Auf diese Weise weiß der Server, wo er fortfahren soll.

Bitte beachten Sie, dass Sie Fälle behandeln müssen, in denen die Elemente zwischen zwei Anfragen geändert wurden. Dies geschieht in der Regel durch Hinzufügen einer Prüfsumme zum Token. Diese Prüfsumme wird über die IDs aller Elemente mit diesem Zeitstempel berechnet. Das Ergebnis ist ein Token-Format wie dieses: Timestamp_Offset_Checksum .

Weitere Informationen zu diesem Ansatz finden Sie in dem Blogbeitrag " Web-API-Paginierung mit Fortsetzungs-Tokens ". Ein Nachteil dieses Ansatzes ist die schwierige Implementierung, da es viele Eckfälle gibt, die berücksichtigt werden müssen. Aus diesem Grund sind Bibliotheken wie Fortsetzungs-Token kann nützlich sein (wenn Sie Java oder eine JVM-Sprache verwenden). Haftungsausschluss: Ich bin der Autor des Beitrags und ein Co-Autor der Bibliothek.

10voto

Archimedes Trajano Punkte 28004

Die Paginierung ist im Allgemeinen eine "Benutzer"-Operation, und um eine Überlastung sowohl des Computers als auch des menschlichen Gehirns zu vermeiden, gibt man in der Regel eine Teilmenge an. Anstatt jedoch zu denken, dass wir nicht die ganze Liste bekommen, ist es vielleicht besser zu fragen Ist das wichtig?

Wenn eine genaue Live-Scroll-Ansicht benötigt wird, sind REST-APIs, die auf Anfrage/Antwort beruhen, für diesen Zweck nicht gut geeignet. In diesem Fall sollten Sie WebSockets oder HTML5 Server-Sent Events in Betracht ziehen, um Ihr Frontend über Änderungen zu informieren.

Wenn es nun eine brauchen um eine Momentaufnahme der Daten zu erhalten, würde ich einfach einen API-Aufruf bereitstellen, der alle Daten in einer Anfrage ohne Paginierung liefert. Allerdings bräuchten Sie etwas, das die Ausgabe streamt, ohne sie vorübergehend in den Speicher zu laden, wenn Sie einen großen Datensatz haben.

In meinem Fall bezeichne ich implizit einige API-Aufrufe, um die gesamten Informationen (hauptsächlich Referenztabellendaten) zu erhalten. Sie können diese APIs auch absichern, damit Ihr System nicht beschädigt wird.

9voto

Anonymous Punkte 2116

Ich möchte nur die Antwort von Kamilk ergänzen: https://www.stackoverflow.com/a/13905589

Das hängt stark davon ab, wie groß der Datensatz ist, den Sie bearbeiten. Kleine Datensätze arbeiten effektiv mit Offset-Seitenumbruch aber große Echtzeit-Datensätze erfordern Cursor-Paginierung.

Ich habe einen wunderbaren Artikel darüber gefunden, wie Slack entwickelt seine api's pagination als es Datensätze erhöht erklären die positiven und negativen in jeder Phase: https://slack.engineering/evolving-api-pagination-at-slack-1c1f644f8e12

4voto

mickeymoon Punkte 4570

Ich denke, dass Ihre API derzeit so reagiert, wie sie sollte. Die ersten 100 Datensätze auf der Seite in der allgemeinen Reihenfolge der Objekte, die Sie pflegen. Ihre Erklärung sagt, dass Sie eine Art von Bestellung ids verwenden, um die Reihenfolge Ihrer Objekte für die Paginierung zu definieren.

Wenn Sie nun wollen, dass Seite 2 immer bei 101 beginnt und bei 200 endet, müssen Sie die Anzahl der Einträge auf der Seite als variabel festlegen, da sie gelöscht werden können.

Sie sollten so etwas wie den folgenden Pseudocode verwenden:

page_max = 100
def get_page_results(page_no) :

    start = (page_no - 1) * page_max + 1
    end = page_no * page_max

    return fetch_results_by_id_between(start, end)

4voto

adnanmuttaleb Punkte 3031

Eine weitere Option für die Paginierung in RESTFul-APIs ist die Verwendung des eingeführten Link-Headers ici . Zum Beispiel Github es benutzen wie folgt:

Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next",
  <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

Die möglichen Werte für rel sind: erste, letzte, nächste, vorherige . Aber durch die Verwendung von Link Kopfzeile, ist es möglicherweise nicht möglich, die Gesamtzahl (Gesamtzahl der Elemente).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X