API Rails Grape: производительность при получении большего количества данных

я столкнулся с проблемой производительности, используя виноградный API. у меня есть следующие модели:

class Profile
   has_many :transitive_user_profiles
end

class TransitiveUserProfile < ApplicationRecord
  belongs_to :profile
  belongs_to :user
  belongs_to :client

конец

class DefaultAddress 
  belongs_to :user
end

я получаю всех пользователей и соответствующие профили через rest-api, используя виноград

@all_profiles =  @all_profiles || 
                TransitiveUserProfile.includes(:profile).where(
              "profile_id IN (?)", application.profile_ids)
present @users, with: Identity::V3::UserEntity, all_profiles: @all_profiles #@user = User.all - around 700 users

я написал класс UserEntity

class UserEntity < Grape::Entity
  expose :id, as: :uniqueId
  expose :trimmed_userid, as: :userId
  expose :nachname, as: :lastName
  expose :vorname, as: :firstName
  expose :is_valid, as: :isValid
  expose :is_system_user, as: :isSystemUser
  expose :email
  expose :utc_updated_at, as: :updatedAt
  expose :applications_and_profiles


  def email  
    object.default_address_email
  end

   def applications_and_profiles
    app_profiles = @all_app_profiles.where(user_id: object.id).collect{|t|  {name: t.profile.unique_id, rights: t.profile.profilrechte} }
    [{:appl_id=>"test", :profiles=>app_profiles}]
  end

 end

Я столкнулся с проблемой, когда я пытаюсь получить всех пользователей и профили, это занимает более 15 секунд. столкнулся с проблемой в следующем коде (требуется время, чтобы получить связанный объект).

 def email  
    object.default_address_email
  end

   def applications_and_profiles
    app_profiles = @all_app_profiles.where(user_id: object.id).collect{|t|  {name: t.profile.unique_id, rights: t.profile.profilrechte} }
    [{:appl_id=>"test", :profiles=>app_profiles}]
  end 

как я могу решить эффективным способом (обычно менее 5 секунд)


person Siddu h    schedule 13.11.2019    source источник
comment
Код не совсем ясен, но похоже, что вы выполняете запрос N+1. Итак, короткий ответ: не делайте этого. (в частности, похоже, что для каждого user в User.all вы вызываете TransitiveUserProfile.all.where("profile_id=?", 12).where(user_id: object.id), а затем создаете экземпляры всех записей из этой коллекции, поэтому около 700+1 запросов, если я правильно понимаю)   -  person anothermh    schedule 13.11.2019
comment
Нет, я не думаю, что это проблема N+1, я получаю всех пользователей, сохраняю и получаю все профили, хранящие @app_profiles(TransitiveUserProfile.all.where(profile_id=?, 12)) и получаю пользователей(User.all) . Используя Grape-API для каждого пользователя, я отфильтровываю записи из уже имеющихся в @app_profiles, используя условие where(@all_app_profiles.where(user_id: object.id)). есть ли альтернативный способ исправить это   -  person Siddu h    schedule 13.11.2019
comment
Имейте в виду эти две вещи: 1 - ваш Entity используется для рендеринга каждой записи в @users, поэтому для каждой user в @users вы вызываете метод applications_and_profiles; 2 - вызов Model.where не создает экземпляры записей, поэтому ваш @all_app_profiles не имеет записей в памяти, поэтому, когда вы, наконец, вызываете @all_app_profiles.where().collect, ЭТО происходит, когда записи извлекаются в память, поэтому вы запускаете этот запрос один раз для каждого пользователя, следовательно это 700+1 или N+1. (и ваши журналы должны показывать это). Подробнее см. здесь.   -  person anothermh    schedule 13.11.2019
comment
@Sidduh Я считаю, что anothermh верен, это N + 1, и вы должны быть в состоянии подтвердить это, проверив свои журналы SQL в своей консоли, он будет запускать кучу похожих запросов. В качестве быстрого решения попробуйте @all_app_profiles.where(user_id: object.id).includes(:profile).collect { |t| ... }   -  person max pleaner    schedule 13.11.2019
comment
Всем спасибо - наконец-то решилось с помощью - @all_app_profiles.select {|i| i.user_id == object.id}.collect {|t| {имя: t.profile.unique_id, unique_id: t.profile.unique_id, права: t.profile.profilrechte} }   -  person Siddu h    schedule 13.11.2019


Ответы (2)


app_profiles = @all_app_profiles.where(user_id: object.id).collect{|t| {имя: t.profile.unique_id, права: t.profile.profilrechte} }

Приведенный выше sql создает проблему N+1, которая замедляет работу. Вы можете проверить свои журналы относительно этого. Лучший способ решить эту проблему — использовать активную загрузку, как показано ниже (как предложено @max pleaner и @anothermh)

app_profiles = @all_app_profiles.where(user_id: object.id).includes(:profile ).collect{|t| {имя: t.profile.unique_id, права: t.profile.profilrechte} }

Для получения дополнительной информации о Eager Loading вы можете перейти по ссылке ниже:

1) https://medium.com/@codenode/10-tips-for-eager-loading-to-avoid-n-1-queries-in-rails-2bad54456a3f

2) https://blog.heroku.com/solving-n-plus-one-queries

person Manju Sagar    schedule 13.11.2019

Спасибо всем, да, это была проблема N+1. наконец, я смог решить, используя @all_app_profiles.select {|i| i.user_id == object.id}.collect {|t| {name: t.profile.unique_id, unique_id: t.profile.unique_id, rights: t.profile.profilrechte} } вместо @all_app_profiles.where(user_id: object.id).collect{|t| {name: t.profile.unique_id, rights: t.profile.profilrechte} } [{:appl_id=>"test", :profiles=>app_profiles}]

person Siddu h    schedule 13.11.2019