GraphQL hat, wie jede Technologie, seine Probleme, von denen einige direkt aus der Architektur resultieren und einige identisch mit denen sind, die wir in jeder anderen Anwendung sehen. Die Lösungen sind jedoch völlig unterschiedlich.
Um das Problem darzustellen, gehen wir von der folgenden Anwendungsarchitektur aus:
Und hier die entsprechende Abfrage in GraphQL um die Daten herunterzuladen. Wir holen alle Links ab, zusammen mit dem Plakat und seinen Links, die dem System hinzugefügt wurden,
{
allLinks {
id
url
Beschreibung
createdAt
postedBy {
id
Name
Links {
id
}
}
}
}
Wie unten dargestellt, sehen wir hier das klassische n + 1 Problem mit Beziehungen.
Link Load (0.4ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
↳ app/controllers/graphql_controller.rb:5:in `execute'
Benutzer laden (0.3ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ? [["id", 40], ["LIMIT", 1]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Link Load (0.3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ? [["user_id", 40]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Benutzer laden (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ? [["id", 38], ["LIMIT", 1]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Link Load (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ? [["user_id", 38]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Benutzer laden (0.2ms) SELECT "benutzer".* FROM "benutzer" WHERE "benutzer". "id" = ? LIMIT ? [["id", 36], ["LIMIT", 1]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Link Load (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ? [["user_id", 36]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Benutzer laden (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ? [["id", 34], ["LIMIT", 1]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Link Load (0.2ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ? [["user_id", 34]]
↳ app/controllers/graphql_controller.rb:5:in `execute'
Benutzer laden (0.1ms) SELECT "benutzer".* FROM "benutzer" WHERE "benutzer". "id" = ? LIMIT ? [["id", 32], ["LIMIT", 1]]
In diesem Fall funktioniert es genau wie dieses Stück von Code: Link.all.map(&:user).map(&:links).
Wir scheinen die Lösung des Problems zu kennen: Link.includes(user: :links).map(&:user).map(&:links)aber wird es wirklich funktionieren? Probieren wir es aus!
Um die Korrektur zu überprüfen, änderte ich die GraphQL Abfrage, um nur einige wenige Felder und keine Beziehung zu verwenden.
{
allLinks {
id
url
Beschreibung
createdAt
}
}
Leider zeigt das Ergebnis, dass wir trotz des Fehlens von Links in Bezug auf den Benutzer und seine Links diese Daten immer noch an die Datenbankabfrage anhängen. Leider sind sie redundant, und mit einer noch komplizierteren Struktur erweist sich dies als einfach ineffizient.
Unter GraphQLSolche Probleme werden anders gelöst, nämlich durch stapelweises Laden von Daten, wobei davon ausgegangen wird, dass die Daten benötigt werden, wenn sie in die Abfrage eingegeben werden. Es handelt sich um ein solches träges Laden. Eine der beliebtesten Bibliotheken ist https://github.com/Shopify/graphql-batch/.
Leider ist die Installation nicht so einfach, wie es scheint. Die Datenlader sind hier verfügbar: https://github.com/Shopify/graphql-batch/tree/master/examples, ich meine die RecordLoader Klasse und dieAssociationLoader Klasse. Installieren wir klassisch die gem 'graphql-batch' Bibliothek und fügen Sie sie dann zu unserem Schema hinzu, ebenso wie die Lader:
Es gibt auch andere Lösungen, die dieses Problem lösen, wie zum Beispiel:
Komplexität der Abfragen
N + 1 Abfragen sind nicht alles, in GraphQL können wir die nächsten Attribute frei übertragen. Standardmäßig ist er auf 1 gesetzt. Dies kann manchmal zu viel für den Server sein, insbesondere in einer Situation, in der wir Daten frei verschachteln können. Wie kann man damit umgehen? Wir können die Komplexität der Abfrage begrenzen, aber dazu müssen wir auch ihre Kosten in den Attributen angeben. Standardmäßig sind sie auf 1 eingestellt. Wir setzen diese Kosten mit der Option Komplexität: Attribut, in das wir Daten eingeben können: Feld: Links, [LinkType], null: false, Komplexität: 101. Wenn die Begrenzung tatsächlich funktionieren soll, müssen Sie noch die Höchstgrenze in Ihr System einführen:
GraphQL verarbeitet Abfragen unterschiedlich, und die Rückverfolgung ist nicht so einfach, wenn man sie mit dem vergleicht, was wir lokal tun können. Leider sagt uns der Rack-Mini-Profiler oder ein normales SQL-Protokoll nicht alles und zeigt auch nicht, welcher Teil der Abfrage für einen bestimmten Zeitabschnitt verantwortlich ist. Im Fall von GraphQL-Ruby können wir kommerzielle Lösungen verwenden, die hier verfügbar sind: https://graphql-ruby.org/queries/tracingoder versuchen, unsere eigene Ablaufverfolgung vorzubereiten. Im Folgenden sieht das Snippet wie ein lokaler Tracer aus.
Die Installation ist ebenfalls sehr einfach: Sie müssen die Tracer-Informationen in das Schema aufnehmen tracer (MyCustomTracer.new) Konfiguration. Wie im folgenden Beispiel:
Die Ausgabe einer solchen Verfolgung sieht wie folgt aus:
POST "/graphql" für ::1 gestartet am 2021-06-17 22:02:44 +0200
(0.1ms) SELECT sqlite_version(*)
Verarbeitung durch GraphqlController#execute as */*
Parameter: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n}"}}
platform_key: graphql.lex, key: lex, duration: 0.156 ms
platform_key: graphql.parse, Schlüssel: parse, Dauer: 0.108 ms
plattform_schluessel: graphql.validate, Schluessel: validate, Dauer: 0.537 ms
platform_key: graphql.analyze_query, Schlüssel: analyze_query, Dauer: 0.123 ms
platform_key: graphql.analyze_multiplex, Schlüssel: analyze_multiplex, Dauer: 0.159 ms
Link Load (0.4ms) SELECT "links".* FROM "links"
↳ app/graphql/graphql_tutorial_schema.rb:21:in `platform_trace'
platform_key: graphql.execute_query, key: execute_query, duration: 15.562 ms
↳ app/graphql/loaders/record_loader.rb:12:in `perform'
↳ app/graphql/loaders/association_loader.rb:46:in `preload_association'
platform_key: graphql.execute_query_lazy, key: execute_query_lazy, duration: 14.12 ms
plattform_schlüssel: graphql.execute_multiplex, schlüssel: execute_multiplex, dauer: 31.11 ms
Abgeschlossen 200 OK in 48ms (Views: 1.2ms | ActiveRecord: 2.0ms | Allocations: 40128)
Zusammenfassung
GraphQL ist keine neue Technologie mehr, aber die Lösungen für ihre Probleme sind nicht vollständig standardisiert, wenn sie nicht Teil der Bibliothek sind. Die Umsetzung dieser Technologie in der Projekt bietet viele Möglichkeiten, mit dem Frontend zu interagieren, und ich persönlich halte es für eine neue Qualität im Vergleich zu dem, was REST API bietet.