window.pipedriveLeadboosterConfig = { base: pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster on jo olemassa') } 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. Entä suorituskyky? - The Codest
Codest
  • Tietoa meistä
  • Palvelut
    • Ohjelmistokehitys
      • Frontend-kehitys
      • Backend-kehitys
    • Staff Augmentation
      • Frontend-kehittäjät
      • Backend-kehittäjät
      • Tietoinsinöörit
      • Pilvi-insinöörit
      • QA insinöörit
      • Muut
    • Se neuvoa-antava
      • Tilintarkastus & konsultointi
  • Toimialat
    • Fintech & pankkitoiminta
    • E-commerce
    • Adtech
    • Terveysteknologia
    • Valmistus
    • Logistiikka
    • Autoteollisuus
    • IOT
  • Arvo
    • TOIMITUSJOHTAJA
    • CTO
    • Toimituspäällikkö
  • Tiimimme
  • Tapaustutkimukset
  • Tiedä miten
    • Blogi
    • Tapaamiset
    • Webinaarit
    • Resurssit
Työurat Ota yhteyttä
  • Tietoa meistä
  • Palvelut
    • Ohjelmistokehitys
      • Frontend-kehitys
      • Backend-kehitys
    • Staff Augmentation
      • Frontend-kehittäjät
      • Backend-kehittäjät
      • Tietoinsinöörit
      • Pilvi-insinöörit
      • QA insinöörit
      • Muut
    • Se neuvoa-antava
      • Tilintarkastus & konsultointi
  • Arvo
    • TOIMITUSJOHTAJA
    • CTO
    • Toimituspäällikkö
  • Tiimimme
  • Tapaustutkimukset
  • Tiedä miten
    • Blogi
    • Tapaamiset
    • Webinaarit
    • Resurssit
Työurat Ota yhteyttä
Takaisin nuoli PALAA TAAKSE
2021-06-30
Ohjelmistokehitys

GraphQL Ruby. Entä suorituskyky?

Codest

Tomasz Szkaradek

Kehitysarkkitehti

GraphQL:llä, kuten millä tahansa teknologialla, on ongelmansa, joista osa johtuu suoraan arkkitehtuurista ja osa on samanlaisia kuin missä tahansa muussa sovelluksessa. Ratkaisut ovat kuitenkin täysin erilaisia.

Ongelman esittelemiseksi oletetaan seuraava sovellusarkkitehtuuri:

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

Ja tässä vastaava kysely osoitteessa GraphQL tietojen lataamiseksi. Haemme kaikki linkit sekä järjestelmään lisätyn julisteen ja sen linkit,

{
  allLinks {
    id
    url
    description
    createdAt
    postedBy {
      id
      name
      linkit {
        id
      }
    }
  }
}

Kuten alla on esitetty, voimme nähdä klassisen n + 1 -ongelman suhteiden kanssa.

Linkkien kuormitus (0.4ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `toteuta'
  Käyttäjän lataus (0.3ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 40], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute' (suorita)
  Linkkien lataus (0.3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in 'suorita'
  Käyttäjän lataus (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [[["id", 38], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute' (suorita)
  Linkkien lataus (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 38]]
  ↳ app/controllers/graphql_controller.rb:5:in 'suorita'
  Käyttäjän lataus (0.2ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [[["id", 36], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute' (suorita)
  Linkkien lataus (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 36]]
  ↳ app/controllers/graphql_controller.rb:5:in 'suorita'
  Käyttäjän lataus (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [[["id", 34], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute' (suorita)
  Linkkien lataus (0.2ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 34]]
  ↳ app/controllers/graphql_controller.rb:5:in 'suorita'
  Käyttäjän lataus (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [[["id", 32], ["LIMIT", 1]]

Tässä tapauksessa se toimii täsmälleen kuten tämä pala koodi:
Link.all.map(&:user).map(&:links).

Näyttää siltä, että tiedämme ratkaisun ongelmaan: Link.includes(user: :links).map(&:user).map(&:links), mutta toimiiko se todella? Tarkistetaan se!

Varmistaakseni korjauksen, muutin GraphQL kysely, jossa käytetään vain muutamaa kenttää eikä mitään suhdetta.

{
  allLinks {
    id
    url
    description
    createdAt
  }
}

Valitettavasti tulos osoittaa, että vaikka käyttäjään ja hänen linkkeihinsä liittyvät linkit puuttuvat, liitämme silti nämä tiedot tietokantakyselyyn. Valitettavasti ne ovat tarpeettomia, ja vielä monimutkaisemman rakenteen kanssa se osoittautuu yksinkertaisesti tehottomaksi.

Käsittely GraphqlController#execute as */*
  Parametrit: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}
  Linkkien lataus (0.3ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute' (suorita)
  Käyttäjän lataus (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'
  Linkkien lataus (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' (suorita)
Suoritettu 200 OK 39ms:ssa (Views: 0.7ms | ActiveRecord: 0.9ms | Allocations: 8730).

Osoitteessa GraphQLtällaiset ongelmat ratkaistaan eri tavalla, yksinkertaisesti lataamalla tiedot erissä olettaen, että tietoja tarvitaan silloin, kun ne asetetaan kyselyyn. Se on tällainen laiska lataus. Yksi suosituimmista kirjastoista on https://github.com/Shopify/graphql-batch/.

Valitettavasti sen asennus ei ole niin vaivatonta kuin miltä se saattaa vaikuttaa. Tiedonlataajat ovat saatavilla täältä: https://github.com/Shopify/graphql-batch/tree/master/examples, tarkoitan siis RecordLoader luokka jaAssociationLoader luokka. Asennetaan klassisesti gem 'graphql-batch' kirjastoa ja lisää se sitten skeemaamme, samoin kuin lataajat:

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

Ja meidän tyyppimme:

# graphql-ruby/app/graphql/types/link_type.rb
moduuli Types
  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 user
      Loaders::RecordLoader.for(User).load(object.user_id)
    end
  end
end

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

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

Lataajien käytön tuloksena keräämme tiedot eräajona ja haemme tietoja kahdella yksinkertaisella sql-kyselyllä:

Käynnistetty POST "/graphql" for ::1 at 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Käsittelee GraphqlController#execute as */*
  Parametrit: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n }n }n }n}"}}
  Linkkien lataus (0.4ms) SELECT "links".* FROM "links"
  ↳ app/controllers/graphql_controller.rb:5:in `execute' (suorita)
  Käyttäjien lataus (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 `suorita'
  Linkkien lataus (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'
Suoritettu 200 OK 62ms:ssa (Views: 1.3ms | ActiveRecord: 1.8ms | Allocations: 39887).

On myös muita ratkaisuja, jotka ratkaisevat tämän ongelman, kuten:

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

Kyselyjen monimutkaisuus

N + 1 kyselyt eivät ole kaikki, vuonna GraphQL voimme vapaasti siirtää seuraavat ominaisuudet. Oletusarvo on 1. Tämä voi joskus olla liikaa palvelimelle, erityisesti tilanteessa, jossa voimme vapaasti sijoittaa tietoja. Miten käsitellä sitä? Voimme rajoittaa kyselyn monimutkaisuutta, mutta tätä varten meidän on myös määritettävä niiden kustannukset attribuuteissa. Oletuksena se on asetettu arvoon 1. Asetamme tämän kustannuksen käyttämällä monimutkaisuus: attribuutti, johon voimme syöttää tietoja: field: links, [LinkType], null: false, complexity: 101. Jos rajoittamisen on tarkoitus todella toimia, sinun on silti otettava käyttöön enimmäisraja järjestelmässäsi:

class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  max_complexity 100
  ...
end

Jäljitys

GraphQL käsittelee kyselyitä eri tavalla, eikä jäljittäminen ole niin yksinkertaista, jos sitä verrataan siihen, mitä voimme tehdä paikallisesti. Valitettavasti rack mini profiler tai tavallinen SQL-loki ei kerro kaikkea eikä osoita, mikä osa kyselystä on vastuussa tietystä aikaviipaleesta. GraphQL-Rubyn tapauksessa voimme käyttää kaupallisia ratkaisuja, jotka ovat saatavilla täällä: https://graphql-ruby.org/queries/tracingtai yrittää valmistella omaa jäljitystä. Alla oleva pätkä näyttää paikalliselta jäljittäjältä.

# 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' => 'graphql.execute_query_lazy' (suorita_kysely_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(platform_key, key, duration)
    tulos
  end

  def platform_field_key(type, field)
    "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(platform_key, key, duration)
    return if key == 'authorized'

    puts "platform_key: #{platform_key}, key: #{key}, duration: #{(duration * 1000).round(5)} ms".yellow
  end
end

Asennus on myös erittäin yksinkertaista, sinun täytyy sisällyttää jäljittimen tiedot skeemaan. tracer (MyCustomTracer.new) kokoonpano. Kuten alla olevassa esimerkissä:

# 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

Tällaisen jäljityksen tulos näyttää tältä:

Käynnistetty POST "/graphql" for ::1 klo 2021-06-17 22:02:44 +0200
   (0.1ms) SELECT sqlite_version(*)
Käsittelee GraphqlController#execute as */*
  Parametrit: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n }n }n }n}"}}
platform_key: graphql.lex, key: lex, duration: 0.156 ms
platform_key: graphql.parse, key: parse, duration: 0.108 ms
platform_key: graphql.validate, key: validate, duration: 0.537 ms
platform_key: graphql.analyze_query, avain: analyze_query, kesto: 0.123 ms
platform_key: graphql.analyze_multiplex, avain: analyze_multiplex, kesto: 0.159 ms
  Linkkien kuormitus (0.4ms) SELECT "links".* FROM "links" (linkit)
  ↳ app/graphql/graphql_tutorial_schema.rb:21:in `platform_trace' (alustan jäljitys)
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, duration: 31.11 ms
Suoritettu 200 OK 48 ms:ssa (Views: 1.2ms | ActiveRecord: 2.0ms | Allocations: 40128).

Yhteenveto

GraphQL ei ole enää uusi tekniikka, mutta sen ongelmien ratkaisut eivät ole täysin standardoituja, jos ne eivät ole osa kirjastoa. Tämän tekniikan toteuttaminen projekti antaa paljon mahdollisuuksia vuorovaikutukseen etusivun kanssa, ja itse pidän sitä uutena ominaisuutena verrattuna siihen, mitä REST API tarjoaa.

Miksi sinun pitäisi (luultavasti) käyttää Typescriptiä?

Miten projektia ei saa tappaa huonoilla koodauskäytännöillä?

Tiedonhakustrategiat NextJS:ssä

Aiheeseen liittyvät artikkelit

Ohjelmistokehitys

Tulevaisuuden web-sovellusten rakentaminen: The Codest:n asiantuntijatiimin näkemyksiä

Tutustu siihen, miten The Codest loistaa skaalautuvien, interaktiivisten verkkosovellusten luomisessa huipputeknologian avulla ja tarjoaa saumattomia käyttäjäkokemuksia kaikilla alustoilla. Lue, miten asiantuntemuksemme edistää digitaalista muutosta ja liiketoimintaa...

THECODEST
Ohjelmistokehitys

Top 10 Latviassa toimivaa ohjelmistokehitysyritystä

Tutustu Latvian parhaisiin ohjelmistokehitysyrityksiin ja niiden innovatiivisiin ratkaisuihin uusimmassa artikkelissamme. Tutustu siihen, miten nämä teknologiajohtajat voivat auttaa nostamaan liiketoimintaasi.

thecodest
Yritys- ja skaalausratkaisut

Java-ohjelmistokehityksen perusteet: A Guide to Outsourcing Successfully

Tutustu tähän keskeiseen oppaaseen Java-ohjelmistokehityksen onnistuneesta ulkoistamisesta tehokkuuden parantamiseksi, asiantuntemuksen saamiseksi ja projektin onnistumiseksi The Codestin avulla.

thecodest
Ohjelmistokehitys

Perimmäinen opas ulkoistamiseen Puolassa

Ulkoistamisen lisääntyminen Puolassa johtuu taloudellisesta, koulutuksellisesta ja teknologisesta kehityksestä, joka edistää tietotekniikan kasvua ja yritysystävällistä ilmapiiriä.

TheCodest
Yritys- ja skaalausratkaisut

Täydellinen opas IT-tarkastustyökaluihin ja -tekniikoihin

Tietotekniikan tarkastuksilla varmistetaan turvalliset, tehokkaat ja vaatimustenmukaiset järjestelmät. Lue lisää niiden merkityksestä lukemalla koko artikkeli.

Codest
Jakub Jakubowicz teknologiajohtaja ja toinen perustaja

Tilaa tietopankkimme ja pysy ajan tasalla IT-alan asiantuntemuksesta.

    Tietoa meistä

    The Codest - Kansainvälinen ohjelmistokehitysyritys, jolla on teknologiakeskuksia Puolassa.

    Yhdistynyt kuningaskunta - pääkonttori

    • Toimisto 303B, 182-184 High Street North E6 2JA
      Lontoo, Englanti

    Puola - Paikalliset teknologiakeskukset

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Krakova
    • Brain Embassy, Konstruktorska
      11, 02-673 Varsova, Puola

      Codest

    • Etusivu
    • Tietoa meistä
    • Palvelut
    • Tapaustutkimukset
    • Tiedä miten
    • Työurat
    • Sanakirja

      Palvelut

    • Se neuvoa-antava
    • Ohjelmistokehitys
    • Backend-kehitys
    • Frontend-kehitys
    • Staff Augmentation
    • Backend-kehittäjät
    • Pilvi-insinöörit
    • Tietoinsinöörit
    • Muut
    • QA insinöörit

      Resurssit

    • Faktoja ja myyttejä yhteistyöstä ulkoisen ohjelmistokehityskumppanin kanssa
    • Yhdysvalloista Eurooppaan: Miksi amerikkalaiset startup-yritykset päättävät muuttaa Eurooppaan?
    • Tech Offshore -kehityskeskusten vertailu: Tech Offshore Eurooppa (Puola), ASEAN (Filippiinit), Euraasia (Turkki).
    • Mitkä ovat teknologiajohtajien ja tietohallintojohtajien tärkeimmät haasteet?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Verkkosivuston käyttöehdot

    Tekijänoikeus © 2025 by The Codest. Kaikki oikeudet pidätetään.

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