The Codest
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Indústrias
    • Fintech e Banca
    • E-commerce
    • Adtech
    • Tecnologia da saúde
    • Fabrico
    • Logística
    • Automóvel
    • IOT
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
Seta para trás VOLTAR
2021-06-30
Desenvolvimento de software

Ruby GraphQL. E quanto ao desempenho?

The Codest

Tomasz Szkaradek

Arquiteto de desenvolvimento

O GraphQL, como qualquer tecnologia, tem os seus problemas, alguns deles resultam diretamente da arquitetura e outros são idênticos aos que vemos em qualquer outra aplicação. No entanto, as soluções são completamente diferentes.

Para apresentar o problema, vamos assumir a seguinte arquitetura de aplicação:

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

E aqui a consulta correspondente em GraphQL para descarregar os dados. Vamos buscar todas as ligações, juntamente com o cartaz e as suas ligações adicionadas ao sistema,

{
  allLinks {
    id
    url
    descrição
    createdAt
    postedBy {
      id
      name
      links {
        id
      }
    }
  }
}

Como se vê abaixo, podemos ver aqui o problema clássico de n + 1 com relações.

Carga de links (0.4ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  app/controllers/graphql_controller.rb:5:in `execute'
  Carga de utilizadores (0.3ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 40], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de links (0.3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de utilizadores (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 38], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de links (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 38]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de utilizadores (0.2ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 36], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de links (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 36]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de utilizadores (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 34], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de links (0.2ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 34]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de utilizadores (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 32], ["LIMIT", 1]]

Neste caso, funciona exatamente como este pedaço de código:
Link.all.map(&:utilizador).map(&:ligações).

Parece que conhecemos a solução para o problema: Link.includes(user: :links).map(&:user).map(&:links)mas será que funciona mesmo? Vamos lá ver!

Para verificar a correção, alterei o GraphQL consulta para utilizar apenas alguns campos e nenhuma relação.

{
  allLinks {
    id
    url
    descrição
    createdAt
  }
}

Infelizmente, o resultado mostra que, apesar da falta de ligações em relação ao utilizador e às suas ligações, continuamos a anexar estes dados à consulta da base de dados. Infelizmente, são redundantes e, com uma estrutura ainda mais complicada, revela-se simplesmente ineficaz.

Processamento por GraphqlController#execute como */*
  Parâmetros: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}
  Carga do link (0,3ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  app/controllers/graphql_controller.rb:5:in `execute'
  Carga de utilizadores (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'
  Carregamento de links (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'
Concluído 200 OK em 39ms (Visualizações: 0.7ms | ActiveRecord: 0.9ms | Alocações: 8730)

Em GraphQLNo entanto, estes problemas são resolvidos de forma diferente, simplesmente carregando os dados em lotes, assumindo que os dados são necessários quando são colocados na consulta. Trata-se de um carregamento preguiçoso. Uma das bibliotecas mais populares é a https://github.com/Shopify/graphql-batch/.

Infelizmente, a sua instalação não é tão simples como pode parecer. Os carregadores de dados estão disponíveis aqui: https://github.com/Shopify/graphql-batch/tree/master/examples, refiro-me ao Carregador de registos e a classeAssociaçãoLoader classe. Vamos instalar classicamente a classe gem 'graphql-batch' e depois adicioná-la ao nosso esquema, bem como aos carregadores:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
classe GraphqlTutorialSchema < GraphQL::Schema
  consulta Types::QueryType
  mutação Types::MutationType
  use GraphQL::Batch
  ...
end

E os nossos tipos:

# graphql-ruby/app/graphql/types/link_type.rb
módulo Types
  classe LinkType < BaseNode
    campo :created_at, DateTimeType, null: false
    campo :url, String, null: false
    campo :description, String, null: false
    campo :posted_by, UserType, null: false, método: :user
    campo :votos, [Types::VoteType], null: false

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

# graphql-ruby/app/graphql/types/user_type.rb
módulo Types
  classe UserType < BaseNode
    campo :created_at, DateTimeType, null: false
    campo :name, String, null: false
    campo :email, String, null: false
    campo :votos, [VoteType], null: false
    campo :links, [LinkType], null: false

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

Como resultado da utilização dos carregadores, agrupamos os dados e consultamos os dados em duas consultas sql simples:

Iniciado POST "/graphql" para ::1 em 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Processamento por GraphqlController#execute como */*
  Parâmetros: {"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}"}}
  Carga do link (0.4ms) SELECT "links".* FROM "links"
  app/controllers/graphql_controller.rb:5:in `execute'
  Carga de utilizadores (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'
  Carregamento de links (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'
Concluído 200 OK em 62ms (Visualizações: 1.3ms | ActiveRecord: 1.8ms | Atribuições: 39887)

Existem também outras soluções que resolvem este problema, tais como:

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

Complexidade das consultas

N + 1 consultas não são tudo, em GraphQL podemos transportar livremente os atributos seguintes. Por defeito, está definido para 1. Por vezes, isto pode ser demasiado para o servidor, especialmente numa situação em que podemos aninhar dados livremente. Como lidar com isso? Podemos limitar a complexidade da consulta, mas, para isso, também precisamos de especificar o seu custo nos atributos. Por defeito, é definido como 1. Definimos este custo utilizando o parâmetro complexidade: onde podemos introduzir dados: campo: links, [LinkType], null: false, complexidade: 101. Para que a limitação funcione efetivamente, é necessário introduzir o limite máximo no seu regime:

classe GraphqlTutorialSchema < GraphQL::Schema
  consulta Types::QueryType
  mutação Types::MutationType
  use GraphQL::Batch
  max_complexity 100
  ...
fim

Rastreio

GraphQL processa as consultas de forma diferente, e o rastreamento não é tão simples se comparado ao que podemos fazer localmente. Infelizmente, o mini-perfilador de rack ou um registo SQL normal não dirá nós tudo e não apontará qual parte da consulta é responsável por uma determinada fatia de tempo. No caso do GraphQL-Ruby, podemos usar soluções comerciais disponíveis aqui: https://graphql-ruby.org/queries/tracingou tentar preparar o nosso próprio rastreio. Abaixo, o snippet parece um rastreador local.

# 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
    resultado = block.call
    duração = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
    observar(chave_da_plataforma, chave, duração)
    resultado
  fim

  def chave_de_campo_da_plataforma(tipo, campo)
    "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 "chave_da_plataforma: #{chave_da_plataforma}, chave: #{chave}, duração: #{(duração * 1000).round(5)} ms".yellow
  fim
fim

A instalação também é extremamente simples, basta incluir as informações do traçador no esquema traçador (MyCustomTracer.new) configuração. Como no exemplo abaixo:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
classe GraphqlTutorialSchema < GraphQL::Schema
  consulta Types::QueryType
  mutação Types::MutationType
  use GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
fim

O resultado desse rastreio tem o seguinte aspeto:

Iniciou o POST "/graphql" para ::1 em 2021-06-17 22:02:44 +0200
   (0.1ms) SELECT sqlite_version(*)
Processamento por GraphqlController#execute como */*
  Parâmetros: {"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}"}}
chave_da_plataforma: graphql.lex, chave: lex, duração: 0.156 ms
chave_da_plataforma: graphql.parse, chave: parse, duração: 0.108 ms
chave_da_plataforma: graphql.validate, chave: validate, duração: 0.537 ms
chave_da_plataforma: graphql.analyze_query, chave: analyze_query, duração: 0.123 ms
platform_key: graphql.analyze_multiplex, key: analyze_multiplex, duration: 0.159 ms
  Carga de links (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
platform_key: graphql.execute_multiplex, key: execute_multiplex, duration: 31.11 ms
Concluído 200 OK em 48ms (Visualizações: 1.2ms | ActiveRecord: 2.0ms | Atribuições: 40128)

Resumo

GraphQL já não é uma tecnologia nova, mas as soluções para os seus problemas não estão totalmente normalizadas se não fizerem parte da biblioteca. A implementação desta tecnologia na projeto oferece muitas oportunidades de interação com o frontend e, pessoalmente, considero-o uma nova qualidade em relação ao que a API REST oferece.

Porque é que deve (provavelmente) utilizar Typescript

Como não matar um projeto com más práticas de codificação?

Estratégias de obtenção de dados no NextJS

Artigos relacionados

Ilustração de uma aplicação de cuidados de saúde para smartphone com um ícone de coração e um gráfico de saúde em ascensão, com o logótipo The Codest, representando soluções digitais de saúde e HealthTech.
Desenvolvimento de software

Softwares para o setor de saúde: Tipos, casos de uso

As ferramentas em que as organizações de cuidados de saúde confiam atualmente não se assemelham em nada às fichas de papel de há décadas atrás. O software de cuidados de saúde apoia agora os sistemas de saúde, os cuidados aos doentes e a prestação de cuidados de saúde modernos em...

OCODEST
Ilustração abstrata de um gráfico de barras em declínio com uma seta ascendente e uma moeda de ouro que simboliza a eficiência ou a poupança de custos. O logótipo The Codest aparece no canto superior esquerdo com o slogan "In Code We Trust" sobre um fundo cinzento claro
Desenvolvimento de software

Como dimensionar a sua equipa de desenvolvimento sem perder a qualidade do produto

Aumentar a sua equipa de desenvolvimento? Saiba como crescer sem sacrificar a qualidade do produto. Este guia cobre sinais de que é hora de escalar, estrutura da equipe, contratação, liderança e ferramentas - além de como o The Codest pode...

OCODEST
Desenvolvimento de software

Construir aplicações Web preparadas para o futuro: ideias da equipa de especialistas do The Codest

Descubra como o The Codest se destaca na criação de aplicações web escaláveis e interactivas com tecnologias de ponta, proporcionando experiências de utilizador perfeitas em todas as plataformas. Saiba como a nossa experiência impulsiona a transformação digital e o negócio...

OCODEST
Desenvolvimento de software

As 10 principais empresas de desenvolvimento de software sediadas na Letónia

Saiba mais sobre as principais empresas de desenvolvimento de software da Letónia e as suas soluções inovadoras no nosso último artigo. Descubra como estes líderes tecnológicos podem ajudar a elevar o seu negócio.

thecodest
Soluções para empresas e escalas

Fundamentos do desenvolvimento de software Java: Um Guia para Terceirizar com Sucesso

Explore este guia essencial sobre o desenvolvimento de software Java outsourcing com sucesso para aumentar a eficiência, aceder a conhecimentos especializados e impulsionar o sucesso do projeto com The Codest.

thecodest

Subscreva a nossa base de conhecimentos e mantenha-se atualizado sobre os conhecimentos do sector das TI.

    Sobre nós

    The Codest - Empresa internacional de desenvolvimento de software com centros tecnológicos na Polónia.

    Reino Unido - Sede

    • Office 303B, 182-184 High Street North E6 2JA
      Londres, Inglaterra

    Polónia - Pólos tecnológicos locais

    • Parque de escritórios Fabryczna, Aleja
      Pokoju 18, 31-564 Cracóvia
    • Embaixada do Cérebro, Konstruktorska
      11, 02-673 Varsóvia, Polónia

      The Codest

    • Início
    • Sobre nós
    • Serviços
    • Case Studies
    • Saber como
    • Carreiras
    • Dicionário

      Serviços

    • Aconselhamento
    • Desenvolvimento de software
    • Desenvolvimento backend
    • Desenvolvimento de front-end
    • Staff Augmentation
    • Programadores de back-end
    • Engenheiros de nuvem
    • Engenheiros de dados
    • Outros
    • Engenheiros de GQ

      Recursos

    • Factos e mitos sobre a cooperação com um parceiro externo de desenvolvimento de software
    • Dos EUA para a Europa: Porque é que as empresas americanas decidem mudar-se para a Europa?
    • Comparação dos centros de desenvolvimento da Tech Offshore: Tech Offshore Europa (Polónia), ASEAN (Filipinas), Eurásia (Turquia)
    • Quais são os principais desafios dos CTOs e dos CIOs?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Direitos de autor © 2026 por The Codest. Todos os direitos reservados.

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