The Codest
  • Quiénes somos
  • Servicios
    • Desarrollo de software
      • Desarrollo Frontend
      • Desarrollo backend
    • Staff Augmentation
      • Desarrolladores frontales
      • Desarrolladores de backend
      • Ingenieros de datos
      • Ingenieros de la nube
      • Ingenieros de control de calidad
      • Otros
    • Asesoramiento
      • Auditoría y consultoría
  • Industrias
    • Fintech y Banca
    • E-commerce
    • Adtech
    • Tecnología sanitaria
    • Fabricación
    • Logística
    • Automoción
    • IOT
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • Nuestro equipo
  • Case Studies
  • Saber cómo
    • Blog
    • Meetups
    • Seminarios en línea
    • Recursos
Carreras profesionales Póngase en contacto
  • Quiénes somos
  • Servicios
    • Desarrollo de software
      • Desarrollo Frontend
      • Desarrollo backend
    • Staff Augmentation
      • Desarrolladores frontales
      • Desarrolladores de backend
      • Ingenieros de datos
      • Ingenieros de la nube
      • Ingenieros de control de calidad
      • Otros
    • Asesoramiento
      • Auditoría y consultoría
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • Nuestro equipo
  • Case Studies
  • Saber cómo
    • Blog
    • Meetups
    • Seminarios en línea
    • Recursos
Carreras profesionales Póngase en contacto
Flecha atrás VOLVER
2021-06-30
Desarrollo de software

GraphQL Ruby. ¿Y el rendimiento?

The Codest

Tomasz Szkaradek

Arquitecto de desarrollo

GraphQL, como cualquier tecnología, tiene sus problemas, algunos de ellos resultan directamente de la arquitectura y otros son idénticos a los que vemos en cualquier otra aplicación. Sin embargo, las soluciones son completamente diferentes.

Para presentar el problema, supongamos la siguiente arquitectura de aplicación:

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

Y aquí la consulta correspondiente en GraphQL para descargar los datos. Recuperamos todos los enlaces, junto con el cartel y sus enlaces añadidos al sistema,

{
  allLinks {
    id
    url
    descripción
    createdAt
    postedBy {
      id
      nombre
      enlaces {
        id
      }
    }
  }
}

Como se muestra a continuación, aquí podemos ver el clásico problema n + 1 con relaciones.

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

En este caso, funciona exactamente igual que esta pieza de código:
Link.all.map(&:usuario).map(&:enlaces).

Parece que conocemos la solución al problema: Link.includes(usuario: :enlaces).map(&:usuario).map(&:enlaces)pero, ¿funcionará realmente? ¡Vamos a comprobarlo!

Para verificar el arreglo, cambié el GraphQL consulta para utilizar sólo unos pocos campos y ninguna relación.

{
  allLinks {
    id
    url
    descripción
    createdAt
  }
}

Por desgracia, el resultado muestra que, a pesar de la falta de vínculos en relación con el usuario y sus enlaces, seguimos adjuntando estos datos a la consulta de la base de datos. Por desgracia, son redundantes y, con una estructura aún más complicada, resulta ser simplemente ineficaz.

Procesado por GraphqlController#execute as */*
  Parámetros: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}
  Carga de enlaces (0.3ms) SELECT "enlaces".* FROM "enlaces" ORDER BY creado_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de usuarios (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'
  Carga de enlaces (0.3ms) SELECT "enlaces".* FROM "enlaces" WHERE "enlaces". "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'
Completado 200 OK en 39ms (Vistas: 0.7ms | ActiveRecord: 0.9ms | Asignaciones: 8730)

En GraphQLSin embargo, este tipo de problemas se resuelven de otra manera, simplemente cargando los datos por lotes, suponiendo que los datos son necesarios cuando se introducen en la consulta. Se trata de una carga perezosa. Una de las bibliotecas más populares es https://github.com/Shopify/graphql-batch/.

Por desgracia, su instalación no es tan sencilla como parece. Los cargadores de datos están disponibles aquí: https://github.com/Shopify/graphql-batch/tree/master/examples, me refiero al Cargador de grabaciones y la claseCargador de asociaciones clase. Instalemos clásicamente la clase gem 'graphql-batch' y luego añadirlo a nuestro esquema, así como cargadores:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Esquema
  consulta Types::QueryType
  mutación Types::MutationType
  use GraphQL::Lote
  ...
end

Y nuestros tipos:

# graphql-ruby/app/graphql/types/link_type.rb
módulo Types
  clase LinkType < BaseNode
    campo :created_at, DateTimeType, null: false
    campo :url, String, null: false
    field :description, String, null: false
    field :posted_by, UserType, null: false, method: :usuario
    field :votes, [Types::VoteType], null: false

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

# graphql-ruby/app/graphql/types/user_type.rb
módulo Types
  clase TipoUsuario < NodoBase
    campo :created_at, DateTimeType, null: false
    campo :name, String, null: false
    field :email, String, null: false
    field :votes, [VoteType], null: false
    field :links, [LinkType], null: false

    def enlaces
      Loaders::AssociationLoader.for(Usuario, :enlaces).load(objeto)
    end
  end
end

Como resultado del uso de los cargadores, procesamos los datos por lotes y los consultamos en dos simples consultas sql:

Iniciado POST "/graphql" para ::1 en 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Procesado por GraphqlController#execute as */*
  Parámetros: {"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}"}}}}
  Carga de enlaces (0.4ms) SELECT "enlaces".* FROM "enlaces"
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Carga de usuarios (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'
  Carga de enlaces (0.5ms) SELECT "enlaces".* FROM "enlaces" WHERE "enlaces". "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'
Completado 200 OK en 62ms (Vistas: 1.3ms | ActiveRecord: 1.8ms | Asignaciones: 39887)

También hay otras soluciones que resuelven este problema, como:

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

Complejidad de las consultas

N + 1 consultas no lo son todo, en GraphQL podemos traspasar libremente los siguientes atributos. Por defecto, se establece en 1. Esto a veces puede ser demasiado para el servidor, especialmente en una situación en la que podemos anidar datos libremente. ¿Cómo solucionarlo? Podemos limitar la complejidad de la consulta, pero para ello, también tenemos que especificar su coste en los atributos. Por defecto se establece en 1. Fijamos este coste utilizando el atributo complejidad: donde podemos introducir los datos: campo: links, [LinkType], null: false, complejidad: 101. Para que la limitación funcione de verdad, hay que introducir el límite máximo en el régimen:

class GraphqlTutorialSchema < GraphQL::Schema
  consulta Types::QueryType
  mutación Types::MutationType
  use GraphQL::Lote
  max_complexity 100
  ...
end

Rastreando

GraphQL procesa las consultas de forma diferente, y el rastreo no es tan sencillo si lo comparamos con lo que podemos hacer localmente. Desgraciadamente, el mini perfilador de rack o un log SQL normal no nos lo dirán todo y no señalarán qué parte de la consulta es responsable de un tramo de tiempo determinado. En el caso de GraphQL-Ruby, podemos utilizar soluciones comerciales disponibles aquí: https://graphql-ruby.org/queries/tracingo intentar preparar nuestro propio rastreo. A continuación, el fragmento se parece a un trazador local.

# lib/my_custom_tracer.rb
class MiRastreadorPersonalizado  '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 rastreo_plataforma(clave_plataforma, clave, _datos, &bloque)
    start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
    resultado = bloque.llamada
    duración = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - inicio
    observar(clave_plataforma, clave, duración)
    resultado
  fin

  def plataforma_campo_clave(tipo, campo)
    "graphql.#{tipo.graphql_nombre}.#{campo.graphql_nombre}"
  end

  def plataforma_clave_autorizada(tipo)
    "graphql.authorized.#{tipo.graphql_nombre}"
  end

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

  def observar(clave_plataforma, clave, duración)
    return if key == 'authorized'

    puts "clave_plataforma: #{clave_plataforma}, clave: #{clave}, duración: #{(duración * 1000).round(5)} ms".yellow
  end
end

La instalación también es extremadamente sencilla, basta con incluir la información del trazador en el esquema tracer (MyCustomTracer.new) configuración. Como en el ejemplo siguiente:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Esquema
  consulta Types::QueryType
  mutación Types::MutationType
  use GraphQL::Lote
  tracer(MyCustomTracer.new)
  ...
end

La salida de dicho rastreo tiene el siguiente aspecto:

Iniciado POST "/graphql" para ::1 en 2021-06-17 22:02:44 +0200
   (0.1ms) SELECT sqlite_version(*)
Procesado por GraphqlController#execute como */*
  Parámetros: {"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}"}}}
clave_plataforma: graphql.lex, clave: lex, duración: 0.156 ms
platform_key: graphql.parse, clave: parse, duración: 0.108 ms
platform_key: graphql.validate, clave: validate, duración: 0,537 ms 0.537 ms
platform_key: graphql.analyze_query, clave: analyze_query, duración: 0,123 ms 0.123 ms
platform_key: graphql.analyze_multiplex, clave: analyze_multiplex, duración: 0,159 ms 0.159 ms
  Carga de enlaces (0,4 ms) SELECT "enlaces".* FROM "enlaces"
  ↳ app/graphql/graphql_tutorial_schema.rb:21:in `platform_trace'
platform_key: graphql.execute_query, clave: execute_query, duración: 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, clave: execute_multiplex, duración: 31.11 ms
Completado 200 OK en 48ms (Vistas: 1.2ms | ActiveRecord: 2.0ms | Asignaciones: 40128)

Resumen

GraphQL ya no es una tecnología nueva, pero las soluciones a sus problemas no están totalmente normalizadas si no forman parte de la biblioteca. La implantación de esta tecnología en la proyecto ofrece muchas oportunidades para interactuar con el frontend y personalmente lo considero una nueva cualidad en relación con lo que ofrece REST API.

Por qué debería (probablemente) utilizar Typescript

¿Cómo no matar un proyecto con malas prácticas de codificación?

Estrategias de obtención de datos en NextJS

Artículos relacionados

Desarrollo de software

Crear aplicaciones web preparadas para el futuro: ideas del equipo de expertos de The Codest

Descubra cómo The Codest destaca en la creación de aplicaciones web escalables e interactivas con tecnologías de vanguardia, ofreciendo experiencias de usuario fluidas en todas las plataformas. Descubra cómo nuestra experiencia impulsa la transformación...

EL MEJOR
Desarrollo de software

Las 10 mejores empresas de desarrollo de software de Letonia

Conozca las principales empresas de desarrollo de software de Letonia y sus innovadoras soluciones en nuestro último artículo. Descubra cómo estos líderes tecnológicos pueden ayudarle a mejorar su negocio.

thecodest
Soluciones para empresas y escalas

Fundamentos del desarrollo de software Java: Guía para externalizar con éxito

Explore esta guía esencial sobre el desarrollo de software Java outsourcing con éxito para mejorar la eficiencia, acceder a la experiencia e impulsar el éxito de los proyectos con The Codest.

thecodest
Desarrollo de software

La guía definitiva para subcontratar en Polonia

El auge de las outsourcing en Polonia está impulsado por los avances económicos, educativos y tecnológicos, que fomentan el crecimiento de las TI y un clima favorable a las empresas.

TheCodest
Soluciones para empresas y escalas

Guía completa de herramientas y técnicas de auditoría informática

Las auditorías informáticas garantizan sistemas seguros, eficientes y conformes. Obtenga más información sobre su importancia leyendo el artículo completo.

The Codest
Jakub Jakubowicz CTO y Cofundador

Suscríbase a nuestra base de conocimientos y manténgase al día de la experiencia del sector informático.

    Quiénes somos

    The Codest - Empresa internacional de desarrollo de software con centros tecnológicos en Polonia.

    Reino Unido - Sede central

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

    Polonia - Centros tecnológicos locales

    • Parque de oficinas Fabryczna, Aleja
      Pokoju 18, 31-564 Cracovia
    • Embajada del Cerebro, Konstruktorska
      11, 02-673 Varsovia, Polonia

      The Codest

    • Inicio
    • Quiénes somos
    • Servicios
    • Case Studies
    • Saber cómo
    • Carreras profesionales
    • Diccionario

      Servicios

    • Asesoramiento
    • Desarrollo de software
    • Desarrollo backend
    • Desarrollo Frontend
    • Staff Augmentation
    • Desarrolladores de backend
    • Ingenieros de la nube
    • Ingenieros de datos
    • Otros
    • Ingenieros de control de calidad

      Recursos

    • Hechos y mitos sobre la cooperación con un socio externo de desarrollo de software
    • De EE.UU. a Europa: ¿Por qué las startups estadounidenses deciden trasladarse a Europa?
    • Comparación de los polos de desarrollo de Tech Offshore: Tech Offshore Europa (Polonia), ASEAN (Filipinas), Eurasia (Turquía)
    • ¿Cuáles son los principales retos de los CTO y los CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Condiciones de uso del sitio web

    Copyright © 2025 por The Codest. Todos los derechos reservados.

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