rack-mini-profiler 这个 gem,可以永远显示网页的加载时间。(2300✨)开发环境和产品环境都可以用。(生成非常详细的报告)

  • development环境,直接使用gem 'rack-mini-profiler'
  • production环境,
  • 1.  gem 'rack-mini-profiler', require: false ,让
  • 2.  然后运行bundle exec rails g rack_profiler:install, 这样在开发环境下也可以使用。

以下服务可以收集网站实际营运的数据,找出哪些部分是效能不好的地方加以改善:

后端效能提速的方向

对后端来说,一个方向是提供 Rails 和 Ruby 代码的效能,一个方向是提供数据库方面的效能。

主要是query查询SQL的效能提升空间大。

1. 避免N + 1的query

includes():让你存取Post模型的关联对象user的属性,却不会产生额外的查询语句。用于简单的join的执行速度的改进。

可以嵌套,可以指定多个关系,可以附加where(具体看API)

对关联,可以使用:

def index
 @posts = Post.includes(:user).page(params[:page])
end

SELECT "posts".* FROM "posts" LIMIT ? [["LIMIT", 2]]
 SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 5)

def show
  @post = Post.find(params[:id])
  @comments = @post.comments.includes(:user)
end

多个关联,和加嵌套:

def index
  @posts = Post.includes(:user, :liked_users, {:comments => :user}).page(params[:page])
end

{:comments => :user}是因为post关联comments,下一层comments关联了user。

<td><%= post.comments.map{ |c| c.user.display_name }%></td>

加上条件查询:

可以在include()后面直接加上where()。

使用scope(name, body, &block),预先设置查询的rails语法. 就是一个类方法。返回一个ActiveRecord::Relation。

可以把返回结果当array,用Enumerable的方法。

scope可以连接scope或类方法。

例子:

如果希望在index页面只显示公开的comments,即comment的status属性的值是public,
同时不希望产生N+1query,

改MVC模型:

则在Models/post.rb中添加:

has_many :comments, -> { where( status: "public")}, class_name: "Comment"

在Controllers/posts_controller.rb中的index方法中修改:

{:comment => :user}改为{:visible_comment => :user}

在views/posts/index.html.erb中修改:

post.comments为post.visble_comments


Bullet在开发时协助侦测 N+1 queries 

gem https://github.com/flyerhzm/bullet (5500✨)

注意:在配置时,注释掉不需要的gem的功能。然后打开页面,如果遇到N+1 query就会弹出窗口。

Rails.application.configure do

config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true     #弹出提示框。
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true  #在terminal显示提升
  Bullet.add_footer = true   #在当前页角显示提升
end

end


activerecord内存的优化:

1.不要用all,可以使用分页的gem。 will_paginate 或 kaminari

2. 真要捞全部数据,使用批处理方法 find_each ,find_in_batches。

例子: 在数据库内新增一个字段,并赋值。

db/migrate/2017XXXXXXXXXX_add_date_to_posts.rb

   class AddDateToPosts < ActiveRecord::Migration[5.1]
def change
+ add_column :posts, :date, :date
+
+ Post.find_each do |post|
+ post.date = post.created_at.to_date
+ post.save( :validate => false )
+ end
end
end

3. Preload技术,使用复杂的语法糖(复杂的Sql)一次性从数据库捞出所有想要的数据,存入了内存。

对不同的需求,使用不同的调用方法从内存中获取记录。

def show
@post = Post.find(params[:id])

if current_user
    all_comments = @post.comments.where("status = ? OR (status = ? AND user_id = ?)","public", "private", current_user.id).includes(:user)
    @comments = all_comments.select{ |x| x.status == "public" }
    @my_comments = all_comments.select{ |x| x.status == "private" }
  else
    @comments = @post.comments.visible.includes(:user)
  end
end

对应的查询语法:

SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? AND (status = 'private' OR (status = 'public' AND user_id = 1))  [["post_id", 1]]

4. count和size: size可以返回已经加载的collection的数量,如果没有加载则调用count的sql方法。所以在collection中都用size,

总结:sql优化,是优化已经实现的功能。不可能有先见之明。

5. pluck()选择字段,在只需要记录的部分字段的情况下使用的。可以节省内存。直接返回一个数组。

Post.all.map{|x| x.id } 是先捞出输出生成ActiveRecord,然后再生成数组

Post.pluck(:id)直接捞需要的数据生成数组,不会生成ActiveRecord。

pluck()和select()用有区别,select()有2个用法:

1。select()返回的是activerecord对象集合。

⚠️比较好的写法是: Post.select(:id, :column_name, ...)这样会带上id。否则返回的activerecord对象不带id索引.

2。select{|x| 条件 } :这是另一种用法,返回数组对象集合。


数据库的sql优化:

使用复杂的sql语法,进行如报表之类的计算。因为在数据库中的计算,比调出数据再用Ruby计算的速度快很多。

因此, 如COUNT, MAX, MIN, SUM, AVG等都直接用数据库计算比较好。

对应的Rails语法糖:count(), maximum(), minimum(), sum(), average().

例子:

SQL:

SELECT  posts.*, COUNT(subscriptions.id) as subscriptions_count FROM "posts" INNER JOIN "subscriptions" ON "subscriptions"."post_id" = "posts"."id" GROUP BY posts.id

Rails语法糖:

Post.joins(:subscriptions).group("posts.id").select("posts.*, count(subscriptions.id) as subscriptions_count")

group()和select()可以换顺序,不影响SQL。

解释:group by 用于集合计算的函数并分组显示结果。


使用计数缓存counter_cache

比如以前没有使用counter_cache,现在已经有了一堆相关的记录。可以:

1. rails g migration AddXxxToXxxs xxx:type,然后在db/migrate/XXX_add_xxx中添加:

2. add_column :users, :posts_count, :integer, default: 0, null: false

User.pluck(:id).each  {|id| User.reset_counters(id, :posts)}

⚠️:reset_counters(id, *counters, touch: nil)

3. 在posts.rb中加上 belongs_to :user, counter_cache: true

4. 在user的posts数量发生变化时,可以更新它使用update_counters(id, counters, touch: nil)

User.update_counters(user.id,  posts_count: 1 )

⚠️:id, 想要更新一个counter的对象的id或者,一数组ids

⚠️:counters是hash, 要更新的字段的名字做key,字段的值是value.

⚠️: 如果设置选项touch:true代表更新时间戳updated_at。

Rails 内建的 Counter Cache 功能比较简单,如果你需要更多功能,请参考 https://github.com/magnusvk/counter_culture 这个 gem。

小结论:什么时候用逆规范化做优化?

如果不常显示该数据, 可以用纯 SQL 的方式来解决。但是如果需要经常显示该数据,就可以考虑用逆规范化的方式,将数据缓存下来。这样效能可以更好。但是缺点就是需要维护该数据的正确性,要写的 Ruby 代码也比较多。

考量:读取的频率 v.s. 更新缓存数据的成本。

比如:点赞数,关注数。无需时时更新。可以用逆规范化。


改进render partial的效能(比较小)

<% @posts.each do |post| %>
  <%= render partial: "post", locals: {post: post}%>
<% end %>

改为用a collection of partials:
<%= render partial: "post", collection: @posts, as: :post%>


数据库索引 (加快查询速度,但会占内存)

add_index(table_name, column_name, options={}) ,各种例子的用法见API

以下需要加上index:

  • 外部键(Foreign key)
  • 会被排序的字段(被放在order方法中)
  • 会被查询的字段(被放在where方法中)
  • 会被group的字段(被放在group方法中)
  • order("id desc")等同于created_at desc,效能更好。这是因为id是integer格式,而created_at是datetime格式。因此⚠️index对不同的数据格式的效率也不同。

效率上Boolean > integer > String > Date > Datetime


内存缓存

超高流量的网站会需要用到缓存来进一步提升后端效能。本教程没有提及如何做缓存,有兴趣的同学请直接看老师的Rails 实战圣经

Rails的各类内存缓存,比如俄罗斯套娃机制,可以看guide。博客里也有相应文章。

最新文章

  1. Selenium3.0 自动化测试
  2. ssh项目部署到weblogic中问题总结
  3. lsattr, chattr
  4. Fastcgi介绍和php中fastcgi的应用
  5. android定位GPS定位 代码实现
  6. 也谈SSO,一个简单实用的单点登录Demo
  7. HDU 3923 Invoker 【裸Polya 定理】
  8. ASP.NET MVC中Area的另一种用法
  9. 一个普通的 Zepto 源码分析(二) - ajax 模块
  10. 局部变量,全局变量初始值问题----C与指针练习题4.14.1
  11. bootstrap模态框显示时被遮罩层遮住了
  12. 如何加快C++代码的编译速度 转 ccache
  13. mySql插入网页地址失败
  14. 【Jetty】Jetty 的工作原理以及与 Tomcat 的比较
  15. Linux 硬件信息命令
  16. minifilter
  17. springboot工程pom的两种配置方式
  18. OC ARC之循环引用问题(代码分析)
  19. Linux which/whereis/locate命令详解
  20. jQuery和Prototype的兼容性和冲突的五种解决方法

热门文章

  1. Win7系统右上角没有搜索怎么办?Win7找回资源管理器中的搜索框
  2. Spring源码学习之BeanFactory体系结构
  3. ASP.NET的页面执行过程
  4. JS/Java/Python格式化金额
  5. Python使用pyMysql模块插入数据到mysql的乱码解决
  6. 原!上线遇到的问题, java序列化关键字transient 修饰的属性变成null了
  7. Git学习-->如何通过Shell脚本自动定时将Gitlab备份文件复制到远程服务器?
  8. EOS Dapp开发(1)-基于Docker的开发环境搭建
  9. 基因芯片与NGS区别[转载]
  10. 20165324_mybash