Der Codest
  • Über uns
  • Dienstleistungen
    • Software-Entwicklung
      • Frontend-Softwareentwicklung
      • Backend-Softwareentwicklung
    • Staff Augmentation
      • Frontend-Entwickler
      • Backend-Entwickler
      • Daten-Ingenieure
      • Cloud-Ingenieure
      • QS-Ingenieure
      • Andere
    • IT-Beratung
      • Prüfung und Beratung
  • Branchen
    • Fintech & Bankwesen
    • E-commerce
    • Adtech
    • Gesundheitstechnik
    • Herstellung
    • Logistik
    • Automobilindustrie
    • IOT
  • Wert für
    • CEO
    • CTO
    • Delivery Manager
  • Unser Team
  • Fallstudien
  • Gewusst wie
    • Blog
    • Begegnungen
    • Webinare
    • Ressourcen
Karriere Kontakt aufnehmen
  • Über uns
  • Dienstleistungen
    • Software-Entwicklung
      • Frontend-Softwareentwicklung
      • Backend-Softwareentwicklung
    • Staff Augmentation
      • Frontend-Entwickler
      • Backend-Entwickler
      • Daten-Ingenieure
      • Cloud-Ingenieure
      • QS-Ingenieure
      • Andere
    • IT-Beratung
      • Prüfung und Beratung
  • Wert für
    • CEO
    • CTO
    • Delivery Manager
  • Unser Team
  • Fallstudien
  • Gewusst wie
    • Blog
    • Begegnungen
    • Webinare
    • Ressourcen
Karriere Kontakt aufnehmen
Pfeil zurück ZURÜCK
2021-06-30
Software-Entwicklung

GraphQL Ruby. Wie sieht es mit der Leistung aus?

Der Codest

Tomasz Szkaradek

Architekt für Entwicklung

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:

https://drive.google.com/file/d/1N4sWPJSls0S8FFHbpHCUVHBNBpEuSsyz/view

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.

Verarbeitung durch GraphqlController#execute als */*
  Parameter: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}
  Link Load (0.3ms) 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" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 40], ["id", 38], ["id", 36], ["id", 34], ["id", 32], ["id", 30], ["id", 28], ["id", 26], ["id", 24], ["id", 22], ["id", 20], ["id", 18], ["id", 16], ["id", 14], ["id", 12], ["id", 10], ["id", 8], ["id", 6], ["id", 4], ["id", 2]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Link Load (0.3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["user_id", 2], ["user_id", 4], ["user_id", 6], ["user_id", 8], ["user_id", 10], ["user_id", 12], ["user_id", 14], ["user_id", 16], ["user_id", 18], ["user_id", 20], ["user_id", 22], ["user_id", 24], ["user_id", 26], ["user_id", 28], ["user_id", 30], ["user_id", 32], ["user_id", 34], ["user_id", 36], ["user_id", 38], ["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
Abgeschlossen 200 OK in 39ms (Views: 0.7ms | ActiveRecord: 0.9ms | Allocations: 8730)

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:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
Klasse GraphqlTutorialSchema < GraphQL::Schema
  Abfrage Types::QueryType
  Mutation Types::MutationType
  benutze GraphQL::Batch
  ...
end

Und unsere Typen:

# graphql-ruby/app/graphql/types/link_type.rb
Modul Typen
  Klasse LinkType < BaseNode
    feld :created_at, DateTimeType, null: false
    feld :url, String, null: false
    feld :beschreibung, String, null: false
    feld :posted_by, UserType, null: false, Methode: :user
    feld :votes, [Typen::VoteType], null: false

    def benutzer
      Loaders::RecordLoader.for(User).load(object.user_id)
    end
  end
end

# graphql-ruby/app/graphql/types/user_type.rb
Modul Typen
  Klasse UserType < BaseNode
    feld :created_at, DateTimeType, null: false
    feld :name, String, null: false
    feld :email, String, null: false
    feld :votes, [VoteType], null: false
    feld :links, [LinkType], null: false

    def links
      Loaders::AssociationLoader.for(Benutzer, :links).load(object)
    end
  end
end

Durch die Verwendung der Lader werden die Daten in Stapeln gespeichert und in zwei einfachen SQL-Abfragen abgefragt:

POST "/graphql" für ::1 gestartet am 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Verarbeitung durch GraphqlController#execute als */*
  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}"}}
  Link Load (0.4ms) SELECT "links".* FROM "links"
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Benutzer laden (0.9ms) SELECT "users".* FROM "users" WHERE "users". "id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 2], ["id", 4], ["id", 6], ["id", 8], ["id", 10], ["id", 12], ["id", 14], ["id", 16], ["id", 18], ["id", 20], ["id", 22], ["id", 24], ["id", 26], ["id", 28], ["id", 30], ["id", 32], ["id", 34], ["id", 36], ["id", 38], ["id", 40]]
  ↳ app/graphql/loaders/record_loader.rb:12:in `perform'
  Link Load (0.5ms) SELECT "links".* FROM "links" WHERE "links". "user_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["user_id", 2], ["user_id", 4], ["user_id", 6], ["user_id", 8], ["user_id", 10], ["user_id", 12], ["user_id", 14], ["user_id", 16], ["user_id", 18], ["user_id", 20], ["user_id", 22], ["user_id", 24], ["user_id", 26], ["user_id", 28], ["user_id", 30], ["user_id", 32], ["user_id", 34], ["user_id", 36], ["user_id", 38], ["user_id", 40]]
  ↳ app/graphql/loaders/association_loader.rb:46:in `preload_association'
Abgeschlossen 200 OK in 62ms (Views: 1.3ms | ActiveRecord: 1.8ms | Allocations: 39887)

Es gibt auch andere Lösungen, die dieses Problem lösen, wie zum Beispiel:

https://github.com/exAspArk/batch-loader#basic-example

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:

Klasse GraphqlTutorialSchema < GraphQL::Schema
  Abfrage Types::QueryType
  Mutation Types::MutationType
  benutze GraphQL::Batch
  max_complexity 100
  ...
end

Rückverfolgung

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.

# lib/my_custom_tracer.rb
Klasse MyCustomTracer  'graphql.lex',
    'parse' => 'graphql.parse',
    'validate' => 'graphql.validate',
    'analyze_query' => 'graphql.analyze_query',
    'analyze_multiplex' => 'graphql.analyze_multiplex',
    'execute_multiplex' => 'graphql.execute_multiplex',
    'execute_query' => 'graphql.execute_query',
    'execute_query_lazy' => 'graphql.execute_query_lazy'
  }

  def platform_trace(platform_key, key, _data, &block)
    start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
    result = block.call
    Dauer = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
    beobachten(plattform_schlüssel, schlüssel, dauer)
    Ergebnis
  end

  def plattform_feld_schlüssel(typ, feld)
    "graphql.#{typ.graphql_name}.#{feld.graphql_name}"
  end

  def plattform_autorisierter_schlüssel(typ)
    "graphql.autorisiert.#{Typ.graphql_name}"
  end

  def platform_resolve_type_key(type)
    "graphql.resolve_type.#{type.graphql_name}"
  end

  def observe(plattform_schlüssel, schlüssel, dauer)
    return if Schlüssel == 'autorisiert'

    puts "plattform_schlüssel: #{plattform_schlüssel}, schlüssel: #{schlüssel}, dauer: #{(Dauer * 1000).round(5)} ms".yellow
  end
end

Die Installation ist ebenfalls sehr einfach: Sie müssen die Tracer-Informationen in das Schema aufnehmen tracer (MyCustomTracer.new) Konfiguration. Wie im folgenden Beispiel:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
Klasse GraphqlTutorialSchema < GraphQL::Schema
  Abfrage Types::QueryType
  Mutation Types::MutationType
  benutze GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
end

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.

Warum Sie (wahrscheinlich) Typescript verwenden sollten

Wie kann man ein Projekt nicht durch schlechte Programmierpraktiken zerstören?

Strategien zum Abrufen von Daten in NextJS

Ähnliche Artikel

Software-Entwicklung

Zukunftssichere Web-Apps bauen: Einblicke vom The Codest-Expertenteam

Entdecken Sie, wie sich The Codest bei der Erstellung skalierbarer, interaktiver Webanwendungen mit Spitzentechnologien auszeichnet, die nahtlose Benutzererfahrungen auf allen Plattformen bieten. Erfahren Sie, wie unsere Expertise die digitale Transformation und...

DAS SCHÖNSTE
Software-Entwicklung

Top 10 Softwareentwicklungsunternehmen in Lettland

Erfahren Sie in unserem neuesten Artikel mehr über die besten Softwareentwicklungsunternehmen Lettlands und ihre innovativen Lösungen. Entdecken Sie, wie diese Technologieführer Ihr Unternehmen voranbringen können.

thecodest
Enterprise & Scaleups Lösungen

Grundlagen der Java-Softwareentwicklung: Ein Leitfaden für erfolgreiches Outsourcing

Entdecken Sie diesen wichtigen Leitfaden zum erfolgreichen Outsourcing der Java-Softwareentwicklung, um die Effizienz zu steigern, auf Fachwissen zuzugreifen und den Projekterfolg mit The Codest voranzutreiben.

thecodest
Software-Entwicklung

Der ultimative Leitfaden für Outsourcing in Polen

Der Anstieg des Outsourcings in Polen wird durch wirtschaftliche, bildungspolitische und technologische Fortschritte angetrieben, die das IT-Wachstum und ein unternehmensfreundliches Klima fördern.

TheCodest
Enterprise & Scaleups Lösungen

Der vollständige Leitfaden für IT-Audit-Tools und -Techniken

IT-Audits gewährleisten sichere, effiziente und gesetzeskonforme Systeme. Erfahren Sie mehr über ihre Bedeutung, indem Sie den vollständigen Artikel lesen.

Der Codest
Jakub Jakubowicz CTO & Mitbegründer

Abonnieren Sie unsere Wissensdatenbank und bleiben Sie auf dem Laufenden über das Fachwissen aus dem IT-Sektor.

    Über uns

    The Codest - Internationales Software-Unternehmen mit technischen Zentren in Polen.

    Vereinigtes Königreich - Hauptsitz

    • Büro 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokale Tech-Hubs

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polen

      Der Codest

    • Startseite
    • Über uns
    • Dienstleistungen
    • Fallstudien
    • Gewusst wie
    • Karriere
    • Wörterbuch

      Dienstleistungen

    • IT-Beratung
    • Software-Entwicklung
    • Backend-Softwareentwicklung
    • Frontend-Softwareentwicklung
    • Staff Augmentation
    • Backend-Entwickler
    • Cloud-Ingenieure
    • Daten-Ingenieure
    • Andere
    • QS-Ingenieure

      Ressourcen

    • Fakten und Mythen über die Zusammenarbeit mit einem externen Softwareentwicklungspartner
    • Aus den USA nach Europa: Warum entscheiden sich amerikanische Start-ups für eine Verlagerung nach Europa?
    • Tech Offshore Development Hubs im Vergleich: Tech Offshore Europa (Polen), ASEAN (Philippinen), Eurasien (Türkei)
    • Was sind die größten Herausforderungen für CTOs und CIOs?
    • Der Codest
    • Der Codest
    • Der Codest
    • Privacy policy
    • Website terms of use

    Urheberrecht © 2025 von The Codest. Alle Rechte vorbehalten.

    de_DEGerman
    en_USEnglish sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek de_DEGerman