简体   繁体   English

我的Rails应用程序中的Postgres性能问题

[英]Postgres performance issue in my Rails app

I'm using derailed_benchmark gem to track my app performance: 我正在使用derailed_benchmark gem来跟踪我的应用程序性能:

$ PATH_TO_HIT="/api/v2/feed.json?per_page=30&page=1&category_name=Feed" USER_SERVER=webrick TEST_COUNT=20 bundle exec derailed exec perf:stackprof

==================================
  Mode: cpu(1000)
  Samples: 20708 (0.42% miss rate)
  GC: 3219 (15.54%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      4720  (22.8%)        4694  (22.7%)     block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_cache
       542   (2.6%)         502   (2.4%)     ActiveSupport::Inflector#underscore
       413   (2.0%)         413   (2.0%)     ActiveSupport::PerThreadRegistry#instance
       364   (1.8%)         364   (1.8%)     ActiveRecord::QueryMethods#validate_order_args
       309   (1.5%)         309   (1.5%)     block in ActiveSupport::Inflector#apply_inflections
       282   (1.4%)         282   (1.4%)     ThreadSafe::NonConcurrentCacheBackend#[]
       257   (1.2%)         257   (1.2%)     ActiveRecord::Relation#initialize
       410   (2.0%)         235   (1.1%)     ActiveRecord::Relation#initialize_copy
       229   (1.1%)         229   (1.1%)     ActiveRecord::Delegation::DelegateCache#relation_delegate_class
       212   (1.0%)         212   (1.0%)     block in ActiveRecord::Relation::Merger#merge
       562   (2.7%)         198   (1.0%)     ActiveRecord::QueryMethods#preprocess_order_args
       190   (0.9%)         189   (0.9%)     ActiveRecord::Core::ClassMethods#arel_table
       181   (0.9%)         181   (0.9%)     JSON#parse
       175   (0.8%)         175   (0.8%)     ActiveRecord::Relation#reset
       165   (0.8%)         165   (0.8%)     ActiveRecord::Attribute#initialize
       153   (0.7%)         153   (0.7%)     ActiveRecord::Relation#values
       151   (0.7%)         151   (0.7%)     ActiveRecord::Inheritance::ClassMethods#base_class
       333   (1.6%)         151   (0.7%)     ActiveRecord::Scoping::Default::ClassMethods#build_default_scope
       144   (0.7%)         144   (0.7%)     Skylight::Normalizers::ActiveRecord::SQL#extract_rust
       142   (0.7%)         142   (0.7%)     ActiveRecord::QueryMethods#joins_values
       138   (0.7%)         138   (0.7%)     block (4 levels) in Class#class_attribute
       195   (0.9%)         133   (0.6%)     ActiveRecord::DynamicMatchers#respond_to?
       158   (0.8%)         121   (0.6%)     ActiveRecord::QueryMethods#where_values=
       125   (0.6%)         115   (0.6%)     ActiveRecord::Reflection::AssociationReflection#klass
       113   (0.5%)         113   (0.5%)     ActiveRecord::Result#initialize_copy
       110   (0.5%)         110   (0.5%)     Arel::Table#initialize
       193   (0.9%)         109   (0.5%)     ActiveRecord::ConnectionAdapters::PostgreSQL::Utils#extract_schema_qualified_name
       114   (0.6%)         106   (0.5%)     Arel::Nodes::Binary#hash
       104   (0.5%)         104   (0.5%)     ActiveRecord::QueryMethods#extending_values
        99   (0.5%)          99   (0.5%)     ActiveRecord::QueryMethods#order_values

How can I fix the "block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_cache" performance issue? 如何修复“ActiveRecord :: ConnectionAdapters :: PostgreSQLAdapter#exec_cache中的块”性能问题?

UPDATE UPDATE

After running same command with "config.middleware.delete "ActiveRecord::QueryCache" in my config/application.rb the results are: 在我的config / application.rb中使用“config.middleware.delete”ActiveRecord :: QueryCache“运行相同的命令后,结果如下:

==================================
  Mode: cpu(1000)
  Samples: 21116 (0.42% miss rate)
  GC: 2213 (10.48%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      5619  (26.6%)        5600  (26.5%)     block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_cache
      2268  (10.7%)        2268  (10.7%)     block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache
       421   (2.0%)         383   (1.8%)     ActiveSupport::Inflector#underscore
       304   (1.4%)         304   (1.4%)     ActiveSupport::PerThreadRegistry#instance
       294   (1.4%)         294   (1.4%)     block in ActiveSupport::Inflector#apply_inflections
       270   (1.3%)         270   (1.3%)     ThreadSafe::NonConcurrentCacheBackend#[]
       245   (1.2%)         245   (1.2%)     ActiveRecord::Relation#initialize
       229   (1.1%)         229   (1.1%)     ActiveRecord::QueryMethods#validate_order_args
       219   (1.0%)         219   (1.0%)     ActiveRecord::Delegation::DelegateCache#relation_delegate_class
       207   (1.0%)         207   (1.0%)     ActiveRecord::Inheritance::ClassMethods#base_class
       285   (1.3%)         188   (0.9%)     ActiveRecord::Relation#initialize_copy
       184   (0.9%)         184   (0.9%)     ActiveRecord::Attribute#initialize
       181   (0.9%)         179   (0.8%)     ActiveRecord::Core::ClassMethods#arel_table
       175   (0.8%)         175   (0.8%)     Skylight::Normalizers::ActiveRecord::SQL#extract_rust
       165   (0.8%)         165   (0.8%)     block in ActiveRecord::Relation::Merger#merge
       147   (0.7%)         147   (0.7%)     block (4 levels) in Class#class_attribute
       374   (1.8%)         145   (0.7%)     ActiveRecord::QueryMethods#preprocess_order_args
       113   (0.5%)         113   (0.5%)     ActiveRecord::Relation#values
       112   (0.5%)         112   (0.5%)     ActiveRecord::QueryMethods#joins_values
       171   (0.8%)         109   (0.5%)     ActiveRecord::ConnectionAdapters::PostgreSQL::Utils#extract_schema_qualified_name
        99   (0.5%)          99   (0.5%)     Arel::Table#initialize
        97   (0.5%)          97   (0.5%)     ActiveRecord::Relation#reset
       271   (1.3%)          96   (0.5%)     ActiveRecord::Scoping::Default::ClassMethods#build_default_scope
       107   (0.5%)          95   (0.4%)     ActiveRecord::Reflection::AssociationReflection#klass
        93   (0.4%)          93   (0.4%)     ActiveRecord::QueryMethods#order_values
       125   (0.6%)          93   (0.4%)     ActiveRecord::QueryMethods#where_values=
        88   (0.4%)          88   (0.4%)     ActiveRecord::Reflection::ThroughReflection#active_record
       106   (0.5%)          87   (0.4%)     Skylight::Trace#start
        81   (0.4%)          81   (0.4%)     ActiveRecord::QueryMethods#check_cached_relation
        80   (0.4%)          80   (0.4%)     ActiveRecord::QueryMethods#where_values

UDPATE 2 UDPATE 2

After running the query with "wall time" mode and not "cpu time" mode, this is the results: 使用“wall time”模式而不是“cpu time”模式运行查询后,结果如下:

==================================
  Mode: wall(1000)
  Samples: 41424 (1.92% miss rate)
  GC: 3648 (8.81%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      4780  (11.5%)        4718  (11.4%)     block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_cache
      2783   (6.7%)        2783   (6.7%)     block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache
      1088   (2.6%)        1088   (2.6%)     ActiveSupport::PerThreadRegistry#instance
       934   (2.3%)         934   (2.3%)     ThreadSafe::NonConcurrentCacheBackend#[]
      1031   (2.5%)         926   (2.2%)     ActiveSupport::Inflector#underscore
       739   (1.8%)         739   (1.8%)     block in ActiveSupport::Inflector#apply_inflections
       626   (1.5%)         626   (1.5%)     ActiveRecord::Relation#initialize
       589   (1.4%)         589   (1.4%)     ActiveRecord::Delegation::DelegateCache#relation_delegate_class
       577   (1.4%)         577   (1.4%)     ThreadSafe::NonConcurrentCacheBackend#get_or_default
       549   (1.3%)         549   (1.3%)     ActiveRecord::Attribute#initialize
       497   (1.2%)         497   (1.2%)     block in ActiveRecord::Relation::Merger#merge
       497   (1.2%)         497   (1.2%)     ActiveRecord::QueryMethods#validate_order_args
       702   (1.7%)         424   (1.0%)     ActiveRecord::Relation#initialize_copy
       419   (1.0%)         417   (1.0%)     ActiveRecord::Core::ClassMethods#arel_table
       384   (0.9%)         384   (0.9%)     ActiveRecord::Inheritance::ClassMethods#base_class
       383   (0.9%)         383   (0.9%)     block (4 levels) in Class#class_attribute
       358   (0.9%)         358   (0.9%)     Skylight::Normalizers::ActiveRecord::SQL#extract_rust
       329   (0.8%)         329   (0.8%)     ActiveRecord::Base.logger
       321   (0.8%)         321   (0.8%)     rescue in Net::BufferedIO#rbuf_fill
       315   (0.8%)         315   (0.8%)     ActiveRecord::Core#update_attributes_from_transaction_state
       314   (0.8%)         314   (0.8%)     ActiveRecord::ConnectionAdapters::AbstractAdapter#type_map
       795   (1.9%)         298   (0.7%)     ActiveRecord::QueryMethods#preprocess_order_args
       284   (0.7%)         284   (0.7%)     Arel::Table#initialize
       279   (0.7%)         279   (0.7%)     ActiveRecord::Relation#values
       278   (0.7%)         278   (0.7%)     ActiveRecord::Relation#reset
       734   (1.8%)         264   (0.6%)     ActiveRecord::Scoping::Default::ClassMethods#build_default_scope
       263   (0.6%)         263   (0.6%)     ActiveRecord::QueryMethods#joins_values
       394   (1.0%)         258   (0.6%)     ActiveRecord::ConnectionAdapters::PostgreSQL::Utils#extract_schema_qualified_name
     15323  (37.0%)         249   (0.6%)     ActiveRecord::Querying#find_by_sql
       257   (0.6%)         246   (0.6%)     ActiveRecord::Reflection::AssociationReflection#klass

I'm building my feed.json using index.json.jbuilder, this is how it looks: 我正在使用index.json.jbuilder构建我的feed.json,它的外观如下:

json.battles @battles do |battle|
  if (battle.products.size == 2)
    battle_results = battle.calculate_results
    json.(battle, :id)
    vote = battle.votes.find_by(user_id: current_user.id) 
    json.voted vote.present?
    if vote
      json.product_voted vote.product.id == battle.products[0].id ? "first" : "second"
    end
    json.mybattle battle.try(:user).try(:id) == current_user.id
    json.user do 
      username = ""
      if (battle.try(:user).try(:nickname).present?)
        username = battle.try(:user).try(:nickname)
      else
        username = battle.try(:user).try(:name).try(:downcase).try(:delete,' ')
      end
      json.username username
      json.user_id battle.try(:user_id)
      json.profile_image battle.try(:user).try(:image) || ""
      json.full_name battle.try(:user).try(:name) || "" 
    end
    json.votes battle_results[:votes]
    json.created_at time_ago_in_words(battle.created_at) + " ago"
    json.title battle.title
    json.first_product do
      first_product = battle.products[0]
      json.id first_product.id
      json.voted first_product.votes.find_by(user_id: current_user.id).present?
      json.percentage battle_results[:percentage_product_one]
      # json.percentage_after_voting battle_results[:percentage_after_voting_product_one]
      json.name first_product.name
      json.price SearchFunctions.convert_currency(first_product.price.to_s, current_user.currency_code, 'USD')
      # json.price first_product.price.to_s
      json.url first_product.url
      if first_product.images["sub"] && first_product.images["sub"].kind_of?(Array)
        first_product.images["sub"] =  first_product.images["sub"].first(10)
      end
      json.images first_product.images
      json.manufacturer first_product.manufacturer
      json.description first_product.description
      json.is_user_saved first_product.saved_products.find_by(user_id: current_user.id).present?
      json.saved_count first_product.saved_products.length
    end
    json.second_product do
      second_product = battle.products[1]
      json.id second_product.id
      json.voted second_product.votes.find_by(user_id: current_user.id).present?
      json.percentage battle_results[:percentage_product_two]
      # json.percentage_after_voting battle_results[:percentage_after_voting_product_two]
      json.name second_product.name
      json.price SearchFunctions.convert_currency(second_product.price.to_s, current_user.currency_code, 'USD')
      # json.price second_product.price.to_s
      json.url second_product.url
      if second_product.images["sub"] && second_product.images["sub"].kind_of?(Array)
        second_product.images["sub"] =  second_product.images["sub"].first(10)
      end
      json.images second_product.images
      json.manufacturer second_product.manufacturer
      json.description second_product.description
      json.is_user_saved second_product.saved_products.find_by(user_id: current_user.id).present?
      json.saved_count second_product.saved_products.length
    end
  end
end

Well, this may be silly answer, but you probably should decrease or optimise number of queries executed during building feed.json. 嗯,这可能是愚蠢的答案,但您可能应该减少或优化在构建feed.json期间执行的查询数量。 Check for N+1 queries, for example maybe you are loading author for each feed item or something like that. 检查N + 1个查询,例如,您可能正在为每个Feed项目或类似内容加载作者。

You can use bullet gem to help you find N+1 queries. 您可以使用bullet gem来帮助您查找N + 1个查询。 And you can fix those by adding includes to your queries. 您可以通过在查询中添加包含来解决这些问题。

Update Based on json view. 更新基于json视图。

It is pretty big json and it looks like you use a lot of models from your application. 这是相当大的json,看起来你使用了很多来自你的应用程序的模型。 BY looking at this it is impossible to give you any clear guidelines on how to optimise this. 通过观察这一点,我们无法就如何优化这一点给出任何明确的指导。 You should probably paste your whole app here, but I think that's not appropriate for SO. 您可能应该将整个应用程序粘贴到此处,但我认为这不适合SO。

For sure there is a lot space for improvement on database side, but I think your best and easiest way is to just cache this view. 当然,数据库方面还有很大的改进空间,但我认为最好也是最简单的方法就是缓存这个视图。 By caching I mean using fragment caching for each separate battle. 通过缓存,我的意思是为每个单独的战斗使用片段缓存。

json.battles @battles do |battle|
  if (battle.products.size == 2)
    json.cache! "#{battle.id}/#{battle.updated_at}" do
      battle_results = battle.calculate_results
      json.(battle, :id)
      vote = battle.votes.find_by(user_id: current_user.id) 
      json.voted vote.present?
      if vote
        json.product_voted vote.product.id == battle.products[0].id ? "first" : "second"
      end
      json.mybattle battle.try(:user).try(:id) == current_user.id
      json.user do 
        username = ""
        if (battle.try(:user).try(:nickname).present?)
          username = battle.try(:user).try(:nickname)
        else
          username = battle.try(:user).try(:name).try(:downcase).try(:delete,' ')
        end
        json.username username
        json.user_id battle.try(:user_id)
        json.profile_image battle.try(:user).try(:image) || ""
        json.full_name battle.try(:user).try(:name) || "" 
      end
      json.votes battle_results[:votes]
      json.created_at time_ago_in_words(battle.created_at) + " ago"
      json.title battle.title
      json.first_product do
        first_product = battle.products[0]
        json.id first_product.id
        json.voted first_product.votes.find_by(user_id: current_user.id).present?
        json.percentage battle_results[:percentage_product_one]
        # json.percentage_after_voting battle_results[:percentage_after_voting_product_one]
        json.name first_product.name
        json.price SearchFunctions.convert_currency(first_product.price.to_s, current_user.currency_code, 'USD')
        # json.price first_product.price.to_s
        json.url first_product.url
        if first_product.images["sub"] && first_product.images["sub"].kind_of?(Array)
          first_product.images["sub"] =  first_product.images["sub"].first(10)
        end
        json.images first_product.images
        json.manufacturer first_product.manufacturer
        json.description first_product.description
        json.is_user_saved first_product.saved_products.find_by(user_id: current_user.id).present?
        json.saved_count first_product.saved_products.length
      end
      json.second_product do
        second_product = battle.products[1]
        json.id second_product.id
        json.voted second_product.votes.find_by(user_id: current_user.id).present?
        json.percentage battle_results[:percentage_product_two]
        # json.percentage_after_voting battle_results[:percentage_after_voting_product_two]
        json.name second_product.name
        json.price SearchFunctions.convert_currency(second_product.price.to_s, current_user.currency_code, 'USD')
        # json.price second_product.price.to_s
        json.url second_product.url
        if second_product.images["sub"] && second_product.images["sub"].kind_of?(Array)
          second_product.images["sub"] =  second_product.images["sub"].first(10)
        end
        json.images second_product.images
        json.manufacturer second_product.manufacturer
        json.description second_product.description
        json.is_user_saved second_product.saved_products.find_by(user_id: current_user.id).present?
        json.saved_count second_product.saved_products.length
      end
    end
  end
end

I added some code at line 3. I just wrote this out of my head, I am not exactly sure if syntax is 100% correct, but it should give you a hint. 我在第3行添加了一些代码。我刚刚写了这篇文章,我不确定语法是否100%正确,但它应该给你一个提示。 Also remember that caching is not enabled in development environment. 还要记住,在开发环境中未启用缓存。 To enable it you need to set config.action_controller.perform_caching = true in config/environments/developement.rb 要启用它,您需要在config/environments/developement.rb设置config.action_controller.perform_caching = true

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM