window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = finestra if (w.LeadBooster) { console.warn('LeadBooster esiste già') } 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. E le prestazioni? - The Codest
The Codest
  • Chi siamo
  • Servizi
    • Sviluppo di software
      • Sviluppo Frontend
      • Sviluppo backend
    • Staff Augmentation
      • Sviluppatori Frontend
      • Sviluppatori backend
      • Ingegneri dei dati
      • Ingegneri del cloud
      • Ingegneri QA
      • Altro
    • Consulenza
      • Audit e consulenza
  • Industrie
    • Fintech e banche
    • E-commerce
    • Adtech
    • Tecnologia della salute
    • Produzione
    • Logistica
    • Automotive
    • IOT
  • Valore per
    • CEO
    • CTO
    • Responsabile della consegna
  • Il nostro team
  • Case Studies
  • Sapere come
    • Blog
    • Incontri
    • Webinar
    • Risorse
Carriera Contattate
  • Chi siamo
  • Servizi
    • Sviluppo di software
      • Sviluppo Frontend
      • Sviluppo backend
    • Staff Augmentation
      • Sviluppatori Frontend
      • Sviluppatori backend
      • Ingegneri dei dati
      • Ingegneri del cloud
      • Ingegneri QA
      • Altro
    • Consulenza
      • Audit e consulenza
  • Valore per
    • CEO
    • CTO
    • Responsabile della consegna
  • Il nostro team
  • Case Studies
  • Sapere come
    • Blog
    • Incontri
    • Webinar
    • Risorse
Carriera Contattate
Freccia indietro TORNA INDIETRO
2021-06-30
Sviluppo di software

GraphQL Ruby. E le prestazioni?

The Codest

Tomasz Szkaradek

Architetto dello sviluppo

GraphQL, come qualsiasi tecnologia, ha i suoi problemi, alcuni dei quali derivano direttamente dall'architettura e altri sono identici a quelli che vediamo in qualsiasi altra applicazione. Tuttavia, le soluzioni sono completamente diverse.

Per presentare il problema, ipotizziamo la seguente architettura applicativa:

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

Ed ecco la corrispondente query in GraphQL per scaricare i dati. Recuperiamo tutti i link, insieme al poster e ai suoi link aggiunti al sistema,

{
  tutti i link {
    id
    url
    descrizione
    creatoAt
    postedBy {
      id
      nome
      link {
        id
      }
    }
  }
}

Come mostrato di seguito, si può vedere il classico problema delle relazioni n + 1.

Carico dei link (0,4ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento utenti (0,3ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 40], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento dei link (0,3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento degli utenti (0,1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 38], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento dei link (0,1 ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 38]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento degli utenti (0,2ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 36], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento dei link (0,1 ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 36]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento degli utenti (0,1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 34], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento dei link (0,2ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 34]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Caricamento degli utenti (0,1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 32], ["LIMIT", 1]]

In questo caso, funziona esattamente come questo pezzo di codice:
Link.all.map(&:utente).map(&:link).

Sembra che conosciamo la soluzione al problema: Link.includes(user: :links).map(&:user).map(&:links)ma funzionerà davvero? Scopriamolo!

Per verificare la correzione, ho modificato il file GraphQL per utilizzare solo alcuni campi e nessuna relazione.

{
  tutti i link {
    id
    url
    descrizione
    createdAt
  }
}

Sfortunatamente, il risultato mostra che, nonostante la mancanza di collegamenti in relazione all'utente e ai suoi link, si allegano comunque questi dati all'interrogazione del database. Purtroppo sono ridondanti e, con una struttura ancora più complicata, risultano semplicemente inefficienti.

Elaborazione da parte di GraphqlController#execute come */*
  Parametri: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}
  Caricamento dei link (0,3 ms) SELECT "link".* FROM "link" 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", 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'
  Caricamento dei link (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'
Completato 200 OK in 39ms (Views: 0.7ms | ActiveRecord: 0.9ms | Allocations: 8730)

In GraphQLTali problemi vengono risolti in modo diverso, semplicemente caricando i dati in batch, assumendo che i dati siano necessari quando vengono inseriti nella query. Si tratta di un caricamento pigro. Una delle librerie più diffuse è https://github.com/Shopify/graphql-batch/.

Purtroppo, la sua installazione non è così semplice come potrebbe sembrare. I caricatori di dati sono disponibili qui: https://github.com/Shopify/graphql-batch/tree/master/examples, intendo il file Caricatore di record e la classeCaricatore di associazioni classe. Installiamo classicamente la classe gemma 'graphql-batch' e poi aggiungerla al nostro schema e ai caricatori:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
classe GraphqlTutorialSchema < GraphQL::Schema
  query Tipi::QueryType
  mutazione Types::MutationType
  use GraphQL::Batch
  ...
fine

E i nostri tipi:

# graphql-ruby/app/graphql/types/link_type.rb
modulo Tipi
  classe LinkType < BaseNode
    campo :created_at, DateTimeType, null: false
    campo :url, String, null: false
    campo :description, Stringa, null: false
    campo :posted_by, UserType, null: false, metodo: :user
    campo :votes, [Types::VoteType], null: false

    def utente
      Loaders::RecordLoader.for(User).load(object.user_id)
    fine
  fine
fine

# graphql-ruby/app/graphql/types/user_type.rb
modulo Tipi
  class UserType < BaseNode
    campo :created_at, DateTimeType, null: false
    campo :name, String, null: false
    campo :email, Stringa, null: false
    campo :votes, [VoteType], null: false
    campo :link, [LinkType], null: false

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

Grazie all'uso dei caricatori, i dati vengono raggruppati e interrogati con due semplici query sql:

Avviato POST "/graphql" per ::1 al 2021-06-16 22:40:17 +0200
   (0.1ms) SELEZIONA sqlite_version(*)
Elaborazione da parte di GraphqlController#execute come */*
  Parametri: {"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}"}}
  Caricamento dei link (0.4ms) 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", 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'
  Caricamento dei link (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'
Completato 200 OK in 62 ms (Viste: 1,3 ms | ActiveRecord: 1,8 ms | Allocazioni: 39887)

Esistono anche altre soluzioni che risolvono questo problema, come ad esempio:

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

Complessità delle interrogazioni

N + 1 interrogazioni non sono tutto, in GraphQL possiamo trasportare liberamente gli attributi successivi. Per impostazione predefinita, è impostato a 1. Questo può essere talvolta eccessivo per il server, soprattutto in una situazione in cui possiamo annidare liberamente i dati. Come comportarsi? Possiamo limitare la complessità della query, ma per farlo dobbiamo anche specificare il loro costo negli attributi. Per impostazione predefinita è impostato a 1. Il costo può essere impostato utilizzando l'opzione complessità: dove si possono inserire i dati: campo: link, [LinkType], null: false, complessità: 101. Se si vuole che la limitazione funzioni davvero, è necessario introdurre il limite massimo nel proprio schema:

classe GraphqlTutorialSchema < GraphQL::Schema
  query Tipi::QueryType
  mutazione Types::MutationType
  use GraphQL::Batch
  max_complessità 100
  ...
fine

Tracciamento

GraphQL elabora le query in modo diverso e il tracciamento non è così semplice se confrontato con quello che possiamo fare localmente. Sfortunatamente, il mini profiler di rack o un normale log di SQL non ci diranno tutto e non indicheranno quale parte della query è responsabile di un determinato lasso di tempo. Nel caso di GraphQL-Ruby, possiamo utilizzare le soluzioni commerciali disponibili qui: https://graphql-ruby.org/queries/tracingo provare a preparare un proprio tracciante. Di seguito, lo snippet appare come un tracciante locale.

# 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
    risultato = block.call
    durata = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - inizio
    observe(platform_key, key, duration)
    risultato
  fine

  def piattaforma_chiave_campo(tipo, campo)
    "graphql.#{type.graphql_name}.#{field.graphql_name}"
  fine

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

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

  def observe(platform_key, key, duration)
    return if key == 'authorized'

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

Anche l'installazione è estremamente semplice, è necessario includere le informazioni del tracciatore nello schema tracer (MyCustomTracer.new) configurazione. Come nell'esempio seguente:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
classe GraphqlTutorialSchema < GraphQL::Schema
  query Tipi::QueryType
  mutazione Types::MutationType
  use GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
fine

L'output di questo tipo di tracciamento è simile a questo:

Avviato POST "/graphql" per ::1 al 2021-06-17 22:02:44 +0200
   (0.1ms) SELEZIONA sqlite_version(*)
Elaborazione da parte di GraphqlController#execute come */*
  Parametri: {"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, chiave: parse, durata: 0,108 ms
chiave_piattaforma: graphql.validate, chiave: validate, durata: 0,537 ms
platform_key: graphql.analyze_query, chiave: analyze_query, durata: 0,123 ms
chiave_piattaforma: graphql.analyze_multiplex, chiave: analyze_multiplex, durata: 0,159 ms
  Carico dei link (0,4 ms) SELEZIONA "link".* DA "link"
  ↳ 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
chiave_piattaforma: graphql.execute_multiplex, chiave: execute_multiplex, durata: 31,11 ms
Completato 200 OK in 48 ms (Viste: 1,2 ms | ActiveRecord: 2,0 ms | Allocazioni: 40128)

Sintesi

GraphQL non è più una tecnologia nuova, ma le soluzioni ai suoi problemi non sono completamente standardizzate se non fanno parte della biblioteca. L'implementazione di questa tecnologia nella progetto offre molte opportunità di interazione con il frontend e personalmente la considero una nuova qualità rispetto a quanto offerto dalle API REST.

Perché si dovrebbe (probabilmente) usare Typescript

Come non uccidere un progetto con cattive pratiche di codifica?

Strategie di recupero dei dati in NextJS

Articoli correlati

Sviluppo di software

Costruire applicazioni web a prova di futuro: le intuizioni del team di esperti di The Codest

Scoprite come The Codest eccelle nella creazione di applicazioni web scalabili e interattive con tecnologie all'avanguardia, offrendo esperienze utente senza soluzione di continuità su tutte le piattaforme. Scoprite come la nostra esperienza favorisce la trasformazione digitale e il business...

IL CANCRO
Sviluppo di software

Le 10 principali aziende di sviluppo software con sede in Lettonia

Scoprite le migliori aziende di sviluppo software della Lettonia e le loro soluzioni innovative nel nostro ultimo articolo. Scoprite come questi leader tecnologici possono aiutarvi a migliorare la vostra attività.

thecodest
Soluzioni per aziende e scaleup

Essenziali di sviluppo software Java: Guida all'outsourcing di successo

Esplorate questa guida essenziale sullo sviluppo di software Java con successo outsourcing per migliorare l'efficienza, accedere alle competenze e guidare il successo del progetto con The Codest.

thecodest
Sviluppo di software

La guida definitiva all'outsourcing in Polonia

L'aumento di outsourcing in Polonia è guidato dai progressi economici, educativi e tecnologici, che favoriscono la crescita dell'IT e un clima favorevole alle imprese.

IlCodesto
Soluzioni per aziende e scaleup

Guida completa agli strumenti e alle tecniche di audit IT

Gli audit IT garantiscono sistemi sicuri, efficienti e conformi. Per saperne di più sulla loro importanza, leggete l'articolo completo.

The Codest
Jakub Jakubowicz CTO e cofondatore

Iscrivetevi alla nostra knowledge base e rimanete aggiornati sulle competenze del settore IT.

    Chi siamo

    The Codest - Società internazionale di sviluppo software con centri tecnologici in Polonia.

    Regno Unito - Sede centrale

    • Ufficio 303B, 182-184 High Street North E6 2JA
      Londra, Inghilterra

    Polonia - Poli tecnologici locali

    • Parco uffici Fabryczna, Aleja
      Pokoju 18, 31-564 Cracovia
    • Ambasciata del cervello, Konstruktorska
      11, 02-673 Varsavia, Polonia

      The Codest

    • Casa
    • Chi siamo
    • Servizi
    • Case Studies
    • Sapere come
    • Carriera
    • Dizionario

      Servizi

    • Consulenza
    • Sviluppo di software
    • Sviluppo backend
    • Sviluppo Frontend
    • Staff Augmentation
    • Sviluppatori backend
    • Ingegneri del cloud
    • Ingegneri dei dati
    • Altro
    • Ingegneri QA

      Risorse

    • Fatti e miti sulla collaborazione con un partner esterno per lo sviluppo di software
    • Dagli Stati Uniti all'Europa: Perché le startup americane decidono di trasferirsi in Europa
    • Confronto tra gli hub di sviluppo Tech Offshore: Tech Offshore Europa (Polonia), ASEAN (Filippine), Eurasia (Turchia)
    • Quali sono le principali sfide di CTO e CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Condizioni di utilizzo del sito web

    Copyright © 2025 di The Codest. Tutti i diritti riservati.

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