REST, GraphQL und gRPC Performanz Vergleich

Von: Stefan Kreutzer
Datum: 11. Februar 2021
Aktualisiert: 25. Februar 2022

Bei verteilten Systemen, die über ein Netzwerk kommunizieren, ist die Performanz ein wichtiges Kriterium für die Auswahl eines Protokolls.

Es liegt die Vermutung nahe, dass Google RPC aufgrund des binären Datenformates eine wesentlich bessere Performanz bietet als REST oder GraphQL. Dieser Teil des Artikels hinterfragt diese pauschale Aussage. Wie groß ist der Unterschied tatsächlich und wirkt sich dieser in der Praxis aus? Gibt es Anwendungsfälle, bei der eine der drei Alternativen Vorteile bietet?

Dies ist der 5. Teil eines Artikels zum Vergleich von GraphQL, REST und gRPC. Die übrigen Teilen dieses Artikels behandeln die folgenden Themen:

1. Einflußfaktoren auf die Performanz

Unter Performanz verstehen wir die Verzögerungszeit, die durch die Übertragung entsteht und die Bandbreite d.h. die Menge an Information, die in einer gewissen Zeit übertragen werden kann. Einfluss auf die Dauer eines entfernten Aufrufes haben:

  • Die Serialisierung eines Objektes in das Transportformat
  • Die Übertragung selbst (Verzögerungszeit und Bandbreite)
  • Die Deserialisierung vom Transportformat zurück in ein Objekt
  • Die Verarbeitung der Anfrage durch den Server

Die folgende Abbildung zeigt wo die Verzögerungen bei einem Aufruf anfallen.

Timing remote Call

Abbildung 1: Zeiten bei einem entfernten Aufruf

Die Größe der zu übertragenden Daten beeinflusst die Dauer aller beteiligten Phasen.

2. Serialisierung und Deserialisierung

Einen nicht zu vernachlässigenden Anteil an der Aufrufzeit hat die Serialisierung und Deserialisierung. Beide Vorgänge finden zweimal statt, einmal bei der Übertragung der Anfrage und einmal bei der Übertragung der Antwort.

Im Gegensatz zu gRPC und GraphQL kann für REST jedes beliebige Dateiformat verwendet werden. Es wäre denkbar für REST ebenfalls ein effizientes Binärformat zu verwenden. In der Praxis wird meist das JSON-Format verwendet, aus diesem Grund haben wir JSON auch für die Tests in diesem Artikel verwendet.

Um herauszufinden, wie groß der Beitrag der Serialisierung zur Übertragungszeit ist, haben wir die Zeiten für die Serialisierung und Deserialisierung von 100.000 Artikeln in JSON und in Protobuf verglichen.

JSON(REST) Protobuf(gRPC)
Serialisieren 241 ms 31 ms
Deserialisieren 357 ms 75 ms

Tabelle 1: Serialisierung und Deserialisierung von 100.000 Artikel Objekten

Die Zeiten für das GraphQL-Format haben wir nicht ermittelt, da sich die Serialisierung nicht ohne weiteres getrennt ausführen lässt. Die Zeiten sollten aber vergleichbar zu den Zeiten von REST sein, da beide ähnliche Textformate benutzen.

Wird die Betrachtung auf die Serialisierung und Deserialisierung beschränkt, dann hat das Binärformat von gRPC mit einem Faktor von ungefähr 7 einen klaren Vorteil.

gRPC verwendet das binäre protobuf-Format dessen Nachrichten ca. ein Drittel kleiner als die textbasierten Formate sind. Die geringere Nachrichtengröße und der eingesparte Aufwand für die Serialisierung macht gRPC attraktiv für Clients und Server mit eingeschränkten Ressourcen beispielsweise für einen Einsatz bei mobilen Anwendungen oder beim Internet of Things.

3. Remote Aufrufe

Die mit Protobuf erzeugten Nachrichten lassen sich nicht nur schneller serialisieren, sie sind darüberhinaus ca. 20 bis 30 Prozent kleiner. Kann gRPC diesen Vorteil nutzen und seinen Vorsprung weiter ausbauen, wenn die Zeiten für die gesamte Übermittlung betrachtet werden?

Für einen Performancevergleich haben wir einen Server aufgesetzt, der alle 3 Schnittstellen bereitstellt und für verschiedene Fälle die Wartezeiten im Client protokolliert. Die Ergebnisse sind nicht wissenschaftlich und nicht repräsentativ. Dafür haben wir das Szenario möglichst praxisnah aufgesetzt.

4. Realistische Bedingungen in der Praxis

Um die Übertragungszeit zu ermitteln wurde eine Funktion zum Abrufen eines Artikels 1.000 Mal hintereinander aufgerufen. Bei dieser Testreihe fand keine Verarbeitung und Speicherung auf dem Server statt.

REST GraphQL gRPC
543 ms 1.590 ms 185 ms

Tabelle 2: Übertragungszeit von 1.000 Aufrufen nacheinander ohne DB Zugriff

GraphQL und REST schneiden in diesem Test scheinbar nicht besonders gut ab. Das liegt auch daran, dass die Zeit für die Verarbeitung auf dem Server außer Acht gelassen wurde. Die reine Übertragung ohne eine Verarbeitung entspricht kaum realistischen Bedingungen in der Praxis. Beim nächsten Test wurden die Artikel einzeln pro Aufruf über JPA in eine Postgres Datenbank geschrieben.

REST GraphQL gRPC
2811 ms 3309 ms 2395 ms

Tabelle 3: Übertragungszeit von 1.000 Aufrufen nacheinander mit Abspeichern in einer Datenbank

Das Feld ist enger zusammengerückt und die Ergebnisse liegen nicht mehr weit auseinander. Bei realen Dankbanken und Abfragen dürften sich die Ergebnisse noch weiter annähern. Je nach Anwendungsfall spielen die Unterschiede zwischen einer Übertragung mit REST, gRPC oder sogar Web Services keine entscheidende Rolle.

5. Fett oder geschwätzig?

Nicht immer können alle benötigten Informationen mit einem Aufruf über eine Schnittstelle abgefragt werden. Es gibt einige typische Fragestellungen, bei denen mehrere Aufrufe hintereinander notwendig sind. Beispielsweise soll eine Liste von Produkten mit Preisen in einer mobilen App wie folgt angezeigt werden:

Waren

Angenommen die App kommuniziert über eine REST Schnittstelle mit einem Backend, welches die Daten bereitstellt. Der folgende Aufruf liefert eine Liste von Produktnamen und Schlüsseln zurück. Die Preise der Produkte sind in dieser Liste jedoch nicht enthalten.

GET /produkte/ { "produkte": [ { "id": 5, "name": "Lutscher", }, { "id": 6, "name": "Bonbon", }, { "id": 8, "name": "Schokolade", } ] }

Für jedes einzelne Produkt ist ein weiterer Aufruf notwendig, um an den Preis eines Produktes zu kommen:

GET /produkte/5 { "id": 5, "name": "Lutscher", "preis": 1.99 }

Der Ablauf ist in der Abbildung unten dargestellt. Der erste Aufruf mit den gelben Pfeilen wird an die Container-Ressource gerichtet, die die Liste mit Produkten zurückliefert. Für jeden Eintrag wird dann ein zusätzlicher Aufruf ausgeführt, der in seiner Antwort den Preis zum jeweiligen Produkt enthält.

n + 1 Problem

Abbildung : n+1 Problem bei der Abfrage einer REST Schnittstelle

Jeder Aufruf benötigt mindestens die Zeit für die Latenz des Netzwerkes. Die Latenz ist die Zeit, die für einen minimalen Aufruf benötigt wird. Die Zeit für die Latenz ist im Diagramm oben durch die schrägen Linien dargestellt. Die Abfrage von 100 Produkten wird mindestens 101-mal die Latenzzeit benötigen. Die Konversation zwischen Client und Server kann zurecht als „geschwätzig“ bezeichnet werden.

Für die Abfrage von 100 Produkten mit Preisen sind insgesamt 101 Aufrufe notwendig:

Anzahl der Aufrufe = 1 * Produktliste + 100 * Details mit Preis = 101

Warum nicht gleich in der Liste die Preise mitliefern wie im folgenden Diagramm dargestellt? Dann würde ein Aufruf genügen und die Latenzzeit würde sich nicht so stark bemerkbar machen?

Fette Antwort

Abbildung : Dicke Antwort mit Preisen

Mit dieser größeren Liste wäre das Performanzproblem gelöst. Um auch andere Abfragen zu optimieren müsste die Schnittstelle immer breiter angelegt werden. Die Folge wäre eine fette Schnittstelle.

Der Konflikt zwischen „geschwätzig“ und fett tritt bei REST, gRPC und bei weiteren Schnittstellentechnologien auf.

5. À la carte mit GraphQL

GraphQL ist wie der Name bereits andeutet eine Abfragesprache. Der Client kann, wie bei SQL eine Abfrage formulieren, die vom Server interpretiert und ausgeführt wird. Der Client kann genau die Daten anfordern, die er benötigt. Wie bei SQL ist es bei GraphQL ebenfalls möglich, über Beziehungen zu navigieren und mit einer Abfrage Daten von mehreren verschiedenen Geschäftsobjekten abzufragen.

Mit der folgenden Abfrage kann ein Client eine Liste mit Produkten anfordern, bei der jeder Eintrag den Namen und den Preis eines Produktes enthält.

POST https://www.predic8.de/fruit-shop-graphql

{
    products {
        name
        price
    }
}
Listing 1: GraphQL Abfrage, die Produktnamen und Preise zurückliefert

Das Ergebnis der Abfrage enhält, wie im Listing unten zu sehen ist, sowohl die Namen der Produkte als auch die Preise. Weitere Informationen werden nicht übermittelt, da auch keine angefragt wurden.

{
    "products": [
        {
            "name": "Lutscher",
            "price": 1.99
        },
        {
            "name": "Bonbon",
            "price ": 0.97
        },
        {
            "name": "Schokolade",
            "price ": 2.98
        }
    ]
}
Listing 2: Liste der Produkte mit name und preis

Ein Client kann mit GraphQL mit einer einzigen Abfrage genau die Daten abfragen, die er benötigt. Nicht mehr und nicht weniger. Selbst Daten, für die der Server über Beziehungen und mehrere Tabellen navigieren muss, können mit einer Abfrage angefordert werden. Die Latenzzeit fällt selbst bei komplexen Fragestellungen nur einmal an. Ein weiterer Effekt besteht darin, dass der Client bestimmen kann, welche Felder übertragen werden. Eine Beschränkung auf die benötigten Felder verkleinert die Größe der Nachrichten und reduziert die Übertragungszeit zusätzlich.

6. Performanz bei n +1 Abfragen

Um den Effekt den GraphQL durch das Einsparen von Anfragen erzielt zu messen haben wir eine Datenbank mit 100 Kunden und 1.000 gleichmäßig auf die Kunden verteilten Artikeln aufgesetzt.

Wie erwartet waren die Auswirkungen auf die Laufzeit erheblich. Im Setup wurde der Client und der Server auf demselben Rechner ausgeführt. Erfolgt die Verbindung über ein mobiles Netzwerk anstatt über ein lokales LAN, so fällt Der Unterschied noch deutlicher aus.

REST GraphQL gRPC
Zeit für die Abfrage (100 Kunden mit Artikel) 1.434 ms 90 ms 706 ms
Anzahl nötiger Abfragen 1100 1 1100

Tabelle: Zeiten für die Abfrage über eine Relation mit 100 Kunden und den zugehörigen Artikeln

Das Vermeiden von zusätzlichen Aufrufen und die Beschränkung der Antwort auf die benötigten Felder machen GraphQL besonders für mobile Anwendungen interessant.

Das Auflösen von Referenzen erhöht die Komplexität für Server-Entwickler. Abhängig von der Implementierung und der GraphQL-Engine sind hohe Serverlasten möglich. GraphQL Schnittstellen sind daher für Denial of Service Attacken anfällig.

7. Fazit

gRPC und GraphQL haben gegenüber REST Vorteile bei der Performanz. Ob der Unterschied in der Praxis tatsächlich eine Rolle spielt ist die andere Frage. Für viele Anwendungsfälle ist die Performanz von REST mit JSON absolut ausreichend oder der Zeitverlust bei der Kommunikation ist gegenüber der Datenbank oder dem Backend zu vernachlässigen.

Mobile Anwendungen, die über ein Netzwerk mit großer Verzögerungszeit kommunizieren, können von GraphQL durch die Reduzierung der Roundtrip-Zeiten profitieren. Eine Kommunikation über Kontinente hinweg oder komplexe Interaktionen zwischen Client und Server sprechen ebenfalls für den Einsatz von GraphQL.

Falls kurze Antwortzeiten die oberste Priorität haben, hat Googles RPC gegenüber REST einen Vorteil. Es empfiehlt sich aber immer alle Aspekte in die Betrachtung miteinzubeziehen und die Auswahl nicht auf ein Kriterium zu beschränken. Im Zweifel bieten sich Lasttests an, die die jeweiligen Anwendungsfälle nachbilden.

Quellcode

Hier findest du den Code zu den Beispielen im Video.