Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 21 Клиентские заголовки GQL-Client-Name: ios-app GQL-Client-Version: 1.2.4 для поддержки нескольких клиентов проще понять, нужно ли ускорять конкретный запрос
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 22 Ищем узкое место class GraphqlSchema < GraphQL!::Schema tracer = Class.new do class !<< self def trace(key, data) started_at = Time.now yield.tap do t = (Time.now - started_at).round(5) Rails.logger.info "!#{key} (!#{"%.5f" % t})" end end end end tracer(tracer) end
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 24 Ищем медленное поле class GraphqlSchema < GraphQL!::Schema tracer = Class.new do class !<< self def trace(key, data) started_at = Time.now yield.tap do t = (Time.now - started_at).round(5) if key !== "execute_field" Rails.logger.info "!#{key} (!#{"%.5f" % t}): !#{data[:field].name}" else Rails.logger.info "!#{key} (!#{"%.5f" % t})" if t > 0.0001 end end end end end tracer(tracer) end
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 27 Директивный профайлинг class Pp < GraphQL!::Schema!::Directive class Profiler < Types!::BaseEnum value :mem value :stack value :ruby end locations(GraphQL!::Schema!::Directive!::FIELD) argument :type, Profiler, required: true class !<< self def resolve(_object, arguments, context) field_name = context.query.selected_operation.selections.first.name msg = send(arguments[:type], field_name) do yield end $stdout.puts "\e[34m[PP] !#{msg.join}\e[0m" context.namespace(:interpreter)[:runtime].write_in_response(["pp"], msg) end def stack(field) require "stackprof" report_path = Rails.root.join("tmp/!#{field}_stackprof.dump") StackProf.run(mode: :cpu, raw: true, out: report_path) do yield end end end end
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 35 💡 Боремся с N+1: запрашиваем все! class Resolvers!::FeedResolverPreload < Resolvers!::BaseResolver type [Types!::Tweet], null: false def resolve FeedBuilder.for(current_user).includes(:author) end end
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 36 💡 Боремся с N+1: lookahead class Resolvers!::FeedResolverLookahead < Resolvers!::BaseResolver type [Types!::Tweet], null: false extras [:lookahead] def resolve(lookahead:) FeedBuilder.for(current_user) .merge(relation_with_includes(lookahead)) end private def relation_with_includes(lookahead) # .selects?(:author) returns true when author field is requested return Tweet.all unless lookahead.selects?(:author) Tweet.includes(:author) end end
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 40 🚨 Клиентский батчинг клиент собирает несколько запросов в один сокращает нагрузку на сеть увеличивает время парсинга и подготовки ответа
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 41 HTTP кэширование Cache-Control/Expires — время жизни данных Last-Modified/If-Modified-Since — время модификации данных ETag — идентификатор ресурса
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 43 HTTP кэширование Rails.application.routes.draw do get "/graphql", to: "graphql#execute" post "/graphql", to: "graphql#execute" end class GraphqlController < ApplicationController def execute if operation_name !!= GET_BANNERS !|| stale?(etag: Date.current) render json: GraphqlSchema.execute(query) end end end 🌍 https:!//guides.rubyonrails.org/caching_with_rails.html#conditional-get-support
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 47 ⚠ GET + mutation = CSRF class GraphqlSchema < GraphQL!::Schema use GraphQL!::PersistedQueries, verify_http_method: true end GET /graphql?query=mutation+%7B+sendMoney+%7D Авторизованный пользователь может перейти по ссылке и выполнить нежелательную операцию!
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 50 💡 Cохраняем AST вместо текста запроса class GraphqlSchema < GraphQL!::Schema use GraphQL!::PersistedQueries, compiled_queries: true end 🌍 https:!//github.com/DmitryTsepelev/graphql-ruby-persisted_queries
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 54 graphql-cache class GraphqSchema < GraphQL!::Schema use GraphQL!::Cache end class BaseType < GraphQL!::Schema!::Object field_class GraphQL!::Cache!::Field end class PostType < BaseObject field :id, ID, null: false field :title, String, null: false, cache: true end 🚨 Не работает с Interpreter!
Ruby Meetup 13 @dmitrytsepelev @dmitrytsepelev 56 graphql-fragment_cache class GraphqSchema < GraphQL!::Schema use GraphQL!::FragmentCache end class BaseType < GraphQL!::Schema!::Object include GraphQL!::FragmentCache!::Object end class PostType < BaseObject field :id, ID, null: false field :title, String, null: false, cache_fragment: true end 🌍 https:!//github.com/DmitryTsepelev/graphql-ruby-fragment_cache