window.pipedriveLeadboosterConfig = { bas: 'leadbooster-chat.pipedrive.com', företagId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(funktion () { var w = fönster if (w.LeadBooster) { console.warn('LeadBooster finns redan') } annars { w.LeadBooster = { q: [], on: funktion (n, h) { this.q.push({ t: "o", n: n, h: h }) }, trigger: funktion (n) { this.q.push({ t: 't', n: n }) }, } } })() GraphQL Ruby. Hur är det med prestanda? - The Codest
Codest
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Industrier
    • Fintech & bankverksamhet
    • E-commerce
    • Adtech
    • Hälsoteknik
    • Tillverkning
    • Logistik
    • Fordon
    • IOT
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
Pil tillbaka GÅ TILLBAKA
2021-06-30
Utveckling av programvara

GraphQL Ruby. Hur är det med prestanda?

Codest

Tomasz Szkaradek

Utvecklingsarkitekt

GraphQL har som all teknik sina problem, vissa av dem är en direkt följd av arkitekturen och andra är identiska med vad vi ser i alla andra applikationer. Lösningarna är dock helt olika.

För att presentera problemet antar vi följande applikationsarkitektur:

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

Och här är motsvarande fråga i GraphQL för att ladda ner data. Vi hämtar alla länkar, tillsammans med affischen och dess länkar som lagts till i systemet,

{
  allaLänkar {
    id
    url
    beskrivning
    skapadAt
    postedBy {
      id
      namn
      länkar {
        id
      }
    }
  }
}

Som visas nedan kan vi se det klassiska n + 1-problemet med relationer här.

Laddning av länkar (0,4 ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Användarbelastning (0,3 ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 40], ["LIMIT", 1]] [["id", 40], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Laddning av länkar (0,3 ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ?  [["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Användarbelastning (0,1 ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 38], ["LIMIT", 1]] [["id", 38], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Laddning av länkar (0,1 ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ?  [["user_id", 38]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Användarbelastning (0,2 ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 36], ["LIMIT", 1]] [["id", 36], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Laddning av länkar (0,1 ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ?  [["user_id", 36]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Användarbelastning (0,1 ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 34], ["LIMIT", 1]] [["id", 34], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Laddning av länkar (0,2 ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ?  [["user_id", 34]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Användarbelastning (0,1 ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 32], ["LIMIT", 1]] [["id", 32], ["LIMIT", 1]]

I det här fallet fungerar det precis som den här biten av kod:
Länk.alla.karta(&:användare).karta(&:länkar).

Vi verkar veta lösningen på problemet: Link.includes(användare: :länkar).map(&:användare).map(&:länkar)men kommer det verkligen att fungera? Låt oss kolla upp det!

För att verifiera lösningen ändrade jag GraphQL fråga för att bara använda några få fält och ingen relation.

{
  allaLänkar {
    id
    url
    beskrivning
    skapadAt
  }
}

Tyvärr visar resultatet att vi, trots avsaknaden av länkar i förhållande till användaren och deras länkar, fortfarande bifogar dessa uppgifter till databasfrågan. Tyvärr är de överflödiga och med en ännu mer komplicerad struktur visar det sig helt enkelt vara ineffektivt.

Bearbetning av GraphqlController#execute som */*
  Parametrar: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}","graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}}
  Laddning av länkar (0,3 ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Användarbelastning (0,3 ms) 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'
  Laddning av länk (0,3 ms) 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'
Avslutad 200 OK på 39 ms (Views: 0,7 ms | ActiveRecord: 0,9 ms | Allokeringar: 8730)

I GraphQLSådana problem löses på ett annat sätt, helt enkelt genom att ladda data i omgångar och anta att datan behövs när den läggs in i frågan. Det är en sådan lat laddning. Ett av de mest populära biblioteken är https://github.com/Shopify/graphql-batch/.

Tyvärr är installationen inte så problemfri som det kan verka. Dataladdarna finns tillgängliga här: https://github.com/Shopify/graphql-batch/tree/master/examples, jag menar RecordLoader klassen ochAssociationLoader klass. Låt oss på klassiskt sätt installera gem 'graphql-batch' bibliotek och sedan lägga till det i vårt schema, samt laddare:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  query Typer::QueryType
  mutation Typer::MutationType
  använd GraphQL::Batch
  ...
slut

Och våra typer:

# graphql-ruby/app/graphql/types/link_type.rb
modul Typer
  klass LinkType < Basnod
    fält :created_at, DateTimeType, null: false
    fält :url, Sträng, null: false
    field :description, Sträng, null: false
    field :posted_by, UserType, null: false, method: :user
    field :votes, [Types::VoteType], null: false

    def användare
      Loaders::RecordLoader.for(User).load(objekt.user_id)
    slut
  slut
slut

# graphql-ruby/app/graphql/types/user_type.rb
modul Typer
  klass UserType < Basnod
    fält :created_at, DateTimeType, null: false
    field :name, Sträng, null: false
    field :email, Sträng, null: false
    fält :votes, [VoteType], null: false
    fält :links, [LinkType], null: false

    def länkar
      Loaders::AssociationLoader.for(Användare, :länkar).load(objekt)
    slut
  slut
slut

Som ett resultat av att vi använder laddarna samlar vi data i batch och vi frågar efter data i två enkla sql-frågor:

Startade POST "/graphql" för ::1 kl 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Bearbetning av GraphqlController#execute som */*
  Parametrar: {"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 }n}"}}
  Laddning av länkar (0,4 ms) SELECT "links".* FROM "links"
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Användarbelastning (0,9 ms) 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'
  Laddning av länkar (0,5 ms) 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'
Avslutad 200 OK på 62 ms (Vyer: 1,3 ms | ActiveRecord: 1,8 ms | Allokeringar: 39887)

Det finns också andra lösningar som löser detta problem, till exempel:

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

Komplexitet i förfrågningar

N + 1 frågor är inte allt, i GraphQL kan vi fritt föra över nästa attribut. Som standard är den inställd på 1. Detta kan ibland vara för mycket för servern, särskilt i en situation där vi fritt kan häcka data. Hur hanterar man det? Vi kan begränsa frågans komplexitet, men för att göra detta måste vi också ange deras kostnad i attributen. Som standard är den inställd på 1. Vi ställer in denna kostnad med hjälp av komplexitet: attribut, där vi kan mata in data: fält: länkar, [LinkType], null: false, komplexitet: 101. Om begränsningen verkligen ska fungera måste du fortfarande införa en maxgräns i ditt system:

class GraphqlTutorialSchema < GraphQL::Schema
  query Typer::QueryType
  mutation Typer::MutationType
  använder GraphQL::Batch
  max_komplexitet 100
  ...
slut

Spårning

GraphQL bearbetar frågor på olika sätt, och spårning är inte så enkelt om man jämför med vad vi kan göra lokalt. Tyvärr kommer rack mini profiler eller en vanlig SQL-logg inte att berätta allt för oss och kommer inte att peka på vilken del av frågan som är ansvarig för en viss tidsskiva. När det gäller GraphQL-Ruby kan vi använda kommersiella lösningar som finns tillgängliga här: https://graphql-ruby.org/queries/tracingeller försöka förbereda vår egen spårning. Nedan ser utdraget ut som en lokal tracer.

# lib/my_custom_tracer.rb
class 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(plattform_nyckel, nyckel, _data, &block)
    start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
    resultat = block.anrop
    duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
    observe(plattform_nyckel, nyckel, varaktighet)
    resultat
  slut

  def plattform_fält_nyckel(typ, fält)
    "graphql.#{typ.graphql_namn}.#{fält.graphql_namn}"
  slut

  def plattform_auktoriserad_nyckel(typ)
    "graphql.authorized.#{typ.graphql_name}"
  slut

  def plattform_resolve_typ_nyckel(typ)
    "graphql.resolve_type.#{typ.graphql_name}"
  slut

  def observe(plattform_nyckel, nyckel, varaktighet)
    return om nyckel == 'auktoriserad'

    puts "plattforms_nyckel: #{plattforms_nyckel}, nyckel: #{nyckel}, varaktighet: #{(duration * 1000).round(5)} ms".yellow
  slut
slut

Installationen är också extremt enkel, du måste inkludera spårningsinformationen i schemat tracer (MyCustomTracer.new) konfiguration. Som i exemplet nedan:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  query Typer::QueryType
  mutation Typer::MutationType
  använd GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
slut

Utdata från en sådan spårning ser ut så här:

Startade POST "/graphql" för ::1 kl 2021-06-17 22:02:44 +0200
   (0,1 ms) SELECT sqlite_version(*)
Bearbetning av GraphqlController#execute som */*
  Parametrar: {"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 }n}"}}
plattform_nyckel: graphql.lex, nyckel: lex, varaktighet: 0,156 ms
platform_key: graphql.parse, nyckel: parse, varaktighet: 0,108 ms
platform_key: graphql.validate, nyckel: validate, varaktighet: 0,537 ms
plattforms_nyckel: graphql.analyze_query, nyckel: analyze_query, varaktighet: 0,123 ms
platform_key: graphql.analyze_multiplex, nyckel: analyze_multiplex, varaktighet: 0,159 ms
  Laddning av länkar (0,4 ms) SELECT "links".* FROM "links"
  ↳ app/graphql/graphql_tutorial_schema.rb:21:in `platform_trace'
platform_key: graphql.execute_query, nyckel: execute_query, varaktighet: 15,562 ms
  ↳ app/graphql/laddare/record_loader.rb:12:in `perform'
  ↳ app/graphql/laddare/association_loader.rb:46:in `preload_association'
platform_key: graphql.execute_query_lazy, nyckel: execute_query_lazy, varaktighet: 14,12 ms
plattforms_nyckel: graphql.execute_multiplex, nyckel: execute_multiplex, varaktighet: 31,11 ms
Avslutad 200 OK på 48 ms (Views: 1,2 ms | ActiveRecord: 2,0 ms | Allokeringar: 40128)

Sammanfattning

GraphQL är inte längre en ny teknik, men lösningarna på dess problem är inte helt standardiserade om de inte ingår i biblioteket. Implementeringen av denna teknik i projekt ger många möjligheter att interagera med frontend och jag anser personligen att det är en ny kvalitet i förhållande till vad REST API erbjuder.

Varför du (förmodligen) bör använda Typescript

Hur undviker man att döda ett projekt med dåliga kodningsrutiner?

Strategier för datahämtning i NextJS

Relaterade artiklar

Utveckling av programvara

Bygg framtidssäkrade webbappar: Insikter från The Codest:s expertteam

Upptäck hur The Codest utmärker sig genom att skapa skalbara, interaktiva webbapplikationer med banbrytande teknik som ger sömlösa användarupplevelser på alla plattformar. Läs om hur vår expertis driver digital omvandling och affärsutveckling...

DEKODEST
Utveckling av programvara

Topp 10 Lettlandsbaserade mjukvaruutvecklingsföretag

Läs mer om Lettlands främsta mjukvaruutvecklingsföretag och deras innovativa lösningar i vår senaste artikel. Upptäck hur dessa teknikledare kan hjälpa till att lyfta ditt företag.

thecodest
Lösningar för företag och uppskalningsföretag

Java Software Development Essentials: En guide till framgångsrik outsourcing

Utforska denna viktiga guide om framgångsrik outsourcing av Java-programvaruutveckling för att förbättra effektiviteten, få tillgång till expertis och driva projektframgång med The Codest.

thecodest
Utveckling av programvara

Den ultimata guiden till outsourcing i Polen

Den kraftiga ökningen av outsourcing i Polen drivs av ekonomiska, utbildningsmässiga och tekniska framsteg, vilket främjar IT-tillväxt och ett företagsvänligt klimat.

TheCodest
Lösningar för företag och uppskalningsföretag

Den kompletta guiden till verktyg och tekniker för IT-revision

IT-revisioner säkerställer säkra, effektiva och kompatibla system. Läs mer om hur viktiga de är genom att läsa hela artikeln.

Codest
Jakub Jakubowicz CTO och medgrundare

Prenumerera på vår kunskapsbas och håll dig uppdaterad om expertisen från IT-sektorn.

    Om oss

    The Codest - Internationellt mjukvaruutvecklingsföretag med teknikhubbar i Polen.

    Förenade kungariket - Huvudkontor

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

    Polen - Lokala tekniknav

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

      Codest

    • Hem
    • Om oss
    • Tjänster
    • Fallstudier
    • Vet hur
    • Karriär
    • Ordbok

      Tjänster

    • Det rådgivande
    • Utveckling av programvara
    • Backend-utveckling
    • Frontend-utveckling
    • Staff Augmentation
    • Backend-utvecklare
    • Ingenjörer inom molntjänster
    • Dataingenjörer
    • Övriga
    • QA-ingenjörer

      Resurser

    • Fakta och myter om att samarbeta med en extern partner för mjukvaruutveckling
    • Från USA till Europa: Varför väljer amerikanska startup-företag att flytta till Europa?
    • Jämförelse av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinerna), Eurasien (Turkiet)
    • Vilka är de största utmaningarna för CTO:er och CIO:er?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Användarvillkor för webbplatsen

    Copyright © 2025 av The Codest. Alla rättigheter reserverade.

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