0

i'm facing performance issue using grape api. i have following models:

class Profile
   has_many :transitive_user_profiles
end

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

end

class DefaultAddress 
  belongs_to :user
end

i'm getting all users and respective profiles through rest-api using grape

@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

i have written UserEntity class

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

i'm facing issue, when i try to get all users and profiles it takes more than 15 secs. facing issue in following code(taking time to get associated object).

 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 

how can i resolve with efficient way(normally less than 5 secs)

Siddu hadapad
  • 3,023
  • 5
  • 15
  • 26
  • 1
    The code isn't exactly clear but it looks like you're doing an [N+1 query](https://stackoverflow.com/q/97197/3784008). So the short answer is: don't do that. (specifically, it looks like for each `user` in `User.all` you are calling `TransitiveUserProfile.all.where("profile_id=?", 12).where(user_id: object.id)` and then instantiating all records from that collection, so around 700+1 queries if I'm reading this right) – anothermh Nov 13 '19 at 02:16
  • No, i don't think this is N+1 problem, im getting all users and storing in and getting all profiles storing @app_profiles(TransitiveUserProfile.all.where("profile_id=?", 12)) and getting users(User.all). Using grape-api for each user, im filtering records from already present in @app_profiles using where condition(@all_app_profiles.where(user_id: object.id)). is there any atlernate way to fix this – Siddu hadapad Nov 13 '19 at 02:26
  • Keep in mind these two things: 1 - your `Entity` is used for rendering each record in `@users`, so for each `user` in `@users` you are calling the `applications_and_profiles` method; 2 - calling `Model.where` doesn't instantiate the records, so your `@all_app_profiles` does not have the records in memory, so when you finally call `@all_app_profiles.where().collect` THAT is when the records get pulled into memory, hence you are running that query once for each user, hence it is 700+1 aka N+1. (and your logs should show that) See [here](https://stackoverflow.com/a/35084280/3784008) for more. – anothermh Nov 13 '19 at 02:58
  • 2
    @Sidduh I believe anothermh is correct, it is N+1 and you should be able to confirm this by checking your SQL logs in your console, it will be firing off a bunch of similar queries. As a quick fix try `@all_app_profiles.where(user_id: object.id).includes(:profile).collect { |t| ... }` – max pleaner Nov 13 '19 at 04:22
  • Thanks all- finally iresolved using - @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} } – Siddu hadapad Nov 13 '19 at 16:14

2 Answers2

1

app_profiles = @all_app_profiles.where(user_id: object.id).collect{|t| {name: t.profile.unique_id, rights: t.profile.profilrechte} }

The above sql creates a N+1 problem, which will make the performace slower. You can check your logs regarding this. Better way to resolve the issue is use eager loading as shown below(As suggested by @max pleaner and @anothermh )

app_profiles = @all_app_profiles.where(user_id: object.id).includes(:profile ).collect{|t| {name: t.profile.unique_id, rights: t.profile.profilrechte} }

For more information regarding Eager Loading you can follow the below link:

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

Manju Sagar
  • 36
  • 1
  • 5
0

Thanks all, yes it was N+1 issue. finally i was able to solve using @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} } instead of @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}]

Siddu hadapad
  • 3,023
  • 5
  • 15
  • 26