window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', versjon: 2, } ;(function () { var w = vindu if (w.LeadBooster) { console.warn('LeadBooster finnes allerede') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() GraphQL Ruby. Hva med ytelse? - Codest
The Codest
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Industrier
    • Fintech og bankvirksomhet
    • E-commerce
    • Adtech
    • Helseteknologi
    • Produksjon
    • Logistikk
    • Bilindustrien
    • IOT
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
Pil tilbake GÅ TILBAKE
2021-06-30
Programvareutvikling

GraphQL Ruby. Hva med ytelse?

The Codest

Tomasz Szkaradek

Utviklingsarkitekt

GraphQL har, som all annen teknologi, sine problemer. Noen av dem er et direkte resultat av arkitekturen, og andre er identiske med det vi ser i alle andre applikasjoner. Løsningene er imidlertid helt forskjellige.

For å presentere problemet, la oss anta følgende applikasjonsarkitektur:

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

Og her er den tilsvarende spørringen i GraphQL for å laste ned dataene. Vi henter alle lenker, sammen med plakaten og lenkene som er lagt til i systemet,

{
  allLinks {
    id
    url
    beskrivelse
    createdAt
    postedBy {
      id
      navn
      lenker {
        id
      }
    }
  }
}

Som vist nedenfor, kan vi se det klassiske n + 1-problemet med relasjoner her.

Link Load (0.4ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Brukerbelastning (0,3 ms) 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]] [[["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (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]] [[["user_id", 38]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."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]] [[["user_id", 36]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (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]] [[["user_id", 34]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 32], ["LIMIT", 1]]]

I dette tilfellet fungerer det akkurat som denne delen av kode:
Link.all.map(&:user).map(&:links).

Vi ser ut til å vite løsningen på problemet: Link.includes(user: :links).map(&:user).map(&:links), men vil det virkelig fungere? La oss sjekke det ut!

For å bekrefte løsningen endret jeg GraphQL spørring for å bare bruke noen få felt og ingen relasjoner.

{
  allLinks {
    id
    url
    beskrivelse
    createdAt
  }
}

Dessverre viser resultatet at til tross for mangelen på lenker i forhold til brukeren og deres lenker, legger vi fortsatt disse dataene til databaseforespørselen. Dessverre er de overflødige, og med en enda mer komplisert struktur viser det seg rett og slett å være ineffektivt.

Behandles av GraphqlController#execute som */*
  Parametere: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}}
  Lasting av lenker (0,3 ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 40], ["id", 38], ["id", 36], ["id", 36], ["id", 34], ["id", 32], ["id", 30], ["id", 28], ["id", 28], ["id", 26], ["id", 26], ["id", 24], ["id", 22], ["id", ["id", 22], ["id", 20], ["id", 18], ["id", 16], ["id", 16], ["id", 14], ["id", 12], ["id", 10], ["id", 8], ["id", 8], ["id", 6], ["id", 6], ["id", 4], ["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", 14], ["user_id", 16], ["user_id", 16], ["user_id", 18], ["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", 34], ["user_id", 36], ["user_id", 36], ["user_id", 38], ["user_id", 40]] ["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
Fullført 200 OK på 39 ms (Visninger: 0,7 ms | ActiveRecord: 0,9 ms | Allokeringer: 8730)

I GraphQLløses slike problemer på en annen måte, ganske enkelt ved å laste inn data i batcher, forutsatt at dataene er nødvendige når de legges inn i spørringen. Det er en slik lat lasting. Et av de mest populære bibliotekene er https://github.com/Shopify/graphql-batch/.

Dessverre er ikke installasjonen så problemfri som det kan virke. Datalasterne er tilgjengelige her: https://github.com/Shopify/graphql-batch/tree/master/examples, jeg mener RecordLoader klassen ogAssociationLoader klasse. La oss på klassisk vis installere gem 'graphql-batch' biblioteket og deretter legge det til i skjemaet vårt, samt lastere:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  ...
end

Og våre typer:

# graphql-ruby/app/graphql/types/link_type.rb
modul Typer
  class LinkType < BaseNode
    field :created_at, DateTimeType, null: false
    field :url, String, null: false
    field :description, String, null: false
    field :posted_by, UserType, null: false, method: :user
    field :votes, [Types::VoteType], null: false

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

# graphql-ruby/app/graphql/types/user_type.rb
modul Types
  class UserType < BaseNode
    field :created_at, DateTimeType, null: false
    field :name, String, null: false
    field :email, String, null: false
    field :votes, [VoteType], null: false
    field :links, [LinkType], null: false

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

Ved hjelp av innlasterne kan vi samle inn data i grupper og søke etter data i to enkle sql-spørringer:

Startet POST "/graphql" for ::1 kl 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Behandlet av GraphqlController#execute som */*
  Parametere: {"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 }n}"}}}
  Lasting av lenker (0,4 ms) SELECT "links".* FROM "links"
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 2], ["id", 4], ["id", 6], ["id", 6], ["id", 8], ["id", 10], ["id", 12], ["id", 12], ["id", 14], ["id", 14], ["id", 16], ["id", 16], ["id", 18], ["id", 18], ["id", 18], ["id", 20], ["id", 22], ["id", 24], ["id", 26], ["id", 26], ["id", 28], ["id", 30], ["id", 30], ["id", 32], ["id", 34], ["id", 34], ["id", 36], ["id", 36], ["id", 36], ["id", 38], ["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", 14], ["user_id", 16], ["user_id", 16], ["user_id", 18], ["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", 34], ["user_id", 36], ["user_id", 36], ["user_id", 38], ["user_id", 38], ["user_id", 40]]
  ↳ app/graphql/loaders/association_loader.rb:46:in `preload_association'
Fullført 200 OK på 62 ms (Visninger: 1,3 ms | ActiveRecord: 1,8 ms | Allokeringer: 39887)

Det finnes også andre løsninger som løser dette problemet, for eksempel:

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

Kompleksiteten i spørringene

N + 1-spørringer er ikke alt, i GraphQL kan vi fritt overføre de neste attributtene. Som standard er den satt til 1. Dette kan noen ganger være for mye for serveren, spesielt i en situasjon der vi fritt kan hekke data. Hvordan håndtere det? Vi kan begrense kompleksiteten i spørringen, men for å gjøre dette må vi også spesifisere kostnadene i attributtene. Som standard er den satt til 1. Vi setter denne kostnaden ved hjelp av kompleksitet: attributtet, der vi kan legge inn data: field: links, [LinkType], null: false, complexity: 101. Hvis begrensningen faktisk skal fungere, må du likevel innføre en maksimumsgrense i ordningen din:

class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  bruk GraphQL::Batch
  max_kompleksitet 100
  ...
end

Sporing

GraphQL behandler spørringer ulikt, og sporing er ikke så enkelt sammenlignet med hva vi kan gjøre lokalt. Dessverre vil ikke rack mini profiler eller en vanlig SQL-logg fortelle oss alt, og den vil ikke vise hvilken del av spørringen som er ansvarlig for et gitt tidsavsnitt. Når det gjelder GraphQL-Ruby, kan vi bruke kommersielle løsninger som er tilgjengelige her: https://graphql-ruby.org/queries/tracingeller prøve å lage vår egen sporing. Nedenfor 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(platform_key, key, _data, &block)
    start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
    result = block.call
    duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
    observe(plattformnøkkel, nøkkel, varighet)
    resultat
  end

  def plattform_feltnøkkel(type, felt)
    "graphql.#{type.graphql_name}.#{field.graphql_name}"
  end

  def platform_authorized_key(type)
    "graphql.authorized.#{type.graphql_name}"
  end

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

  def observe(plattform_nøkkel, nøkkel, varighet)
    return if key == 'authorized'

    puts "plattformnøkkel: #{plattformnøkkel}, nøkkel: #{nøkkel}, varighet: #{(varighet * 1000).round(5)} ms".yellow
  end
end

Installasjonen er også ekstremt enkel, du trenger bare å inkludere sporingsinformasjonen i skjemaet tracer (MyCustomTracer.new) konfigurasjon. Som i eksempelet nedenfor:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
end

Resultatet fra en slik sporing ser slik ut:

Startet POST "/graphql" for ::1 kl 2021-06-17 22:02:44 +0200
   (0.1ms) SELECT sqlite_version(*)
Behandlet av GraphqlController#execute som */*
  Parametere: {"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 }n}"}}}
plattformnøkkel: graphql.lex, nøkkel: lex, varighet: 0.156 ms
platform_key: graphql.parse, key: parse, varighet: 0.108 ms
platform_key: graphql.validate, key: validate, varighet: 0,537 ms
platform_key: graphql.analyze_query, key: analyze_query, varighet: 0.123 ms
platform_key: graphql.analyze_multiplex, key: analyze_multiplex, duration: 0,159 ms
  Lasting av lenker (0,4 ms) 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
platform_key: graphql.execute_multiplex, key: execute_multiplex, varighet: 31.11 ms
Fullført 200 OK på 48 ms (Visninger: 1,2 ms | ActiveRecord: 2,0 ms | Allokeringer: 40128)

Sammendrag

GraphQL er ikke en ny teknologi lenger, men løsningene på problemene er ikke fullt ut standardiserte hvis de ikke er en del av biblioteket. Implementeringen av denne teknologien i prosjekt gir mange muligheter til å samhandle med frontend, og jeg anser det personlig som en ny kvalitet i forhold til hva REST API tilbyr.

Derfor bør du (sannsynligvis) bruke Typescript

Hvordan unngår man å drepe et prosjekt med dårlig kodingspraksis?

Strategier for datahenting i NextJS

Relaterte artikler

Programvareutvikling

Bygg fremtidssikre webapper: Innsikt fra The Codests ekspertteam

Oppdag hvordan The Codest utmerker seg når det gjelder å skape skalerbare, interaktive webapplikasjoner med banebrytende teknologi som gir sømløse brukeropplevelser på tvers av alle plattformer. Finn ut hvordan ekspertisen vår driver digital transformasjon og...

THECODEST
Programvareutvikling

Topp 10 Latvia-baserte programvareutviklingsselskaper

I vår nyeste artikkel kan du lese mer om Latvias beste programvareutviklingsselskaper og deres innovative løsninger. Oppdag hvordan disse teknologilederne kan bidra til å løfte virksomheten din.

thecodest
Løsninger for bedrifter og oppskalering

Grunnleggende om Java-programvareutvikling: En guide til vellykket outsourcing

Utforsk denne viktige veiledningen om vellykket outsourcing av Java-programvareutvikling for å øke effektiviteten, få tilgang til ekspertise og drive frem prosjektsuksess med The Codest.

thecodest
Programvareutvikling

Den ultimate guiden til outsourcing i Polen

Den kraftige økningen i outsourcing i Polen er drevet av økonomiske, utdanningsmessige og teknologiske fremskritt, noe som fremmer IT-vekst og et forretningsvennlig klima.

TheCodest
Løsninger for bedrifter og oppskalering

Den komplette guiden til verktøy og teknikker for IT-revisjon

IT-revisjoner sørger for sikre, effektive og kompatible systemer. Les hele artikkelen for å lære mer om viktigheten av dem.

The Codest
Jakub Jakubowicz CTO og medgrunnlegger

Abonner på vår kunnskapsbase og hold deg oppdatert på ekspertisen fra IT-sektoren.

    Om oss

    The Codest - Internasjonalt programvareutviklingsselskap med teknologisentre i Polen.

    Storbritannia - Hovedkvarter

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

    Polen - Lokale teknologisentre

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

      The Codest

    • Hjem
    • Om oss
    • Tjenester
    • Casestudier
    • Vet hvordan
    • Karriere
    • Ordbok

      Tjenester

    • Det rådgivende
    • Programvareutvikling
    • Backend-utvikling
    • Frontend-utvikling
    • Staff Augmentation
    • Backend-utviklere
    • Ingeniører i skyen
    • Dataingeniører
    • Annet
    • QA-ingeniører

      Ressurser

    • Fakta og myter om samarbeid med en ekstern programvareutviklingspartner
    • Fra USA til Europa: Hvorfor velger amerikanske oppstartsbedrifter å flytte til Europa?
    • Sammenligning av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinene), Eurasia (Tyrkia)
    • Hva er de største utfordringene for CTO-er og CIO-er?
    • The Codest
    • The Codest
    • The Codest
    • Retningslinjer for personver
    • Vilkår for bruk av nettstedet

    Opphavsrett © 2025 av The Codest. Alle rettigheter forbeholdt.

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