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。博客里也有相应文章。

**优化--后端**: 计数缓存counter_cache; rack-mini-profiler(2300🌟) ; bullet(5000✨):侦测N+1query的更多相关文章

  1. 性能优化工具 MVC Mini Profiler

    性能优化工具 MVC Mini Profiler   MVC MiniProfiler是Stack Overflow团队设计的一款对ASP.NET MVC.WebForm 以及WCF 的性能分析的小程 ...

  2. [MySQL性能优化系列]提高缓存命中率

    1. 背景 通常情况下,能用一条sql语句完成的查询,我们尽量不用多次查询完成.因为,查询次数越多,通信开销越大.但是,分多次查询,有可能提高缓存命中率.到底使用一个复合查询还是多个独立查询,需要根据 ...

  3. Unity性能优化(1)-官方教程The Profiler window翻译

    本文是Unity官方教程,性能优化系列的第一篇<The Profiler window>的简单翻译. 相关文章: Unity性能优化(1)-官方教程The Profiler window翻 ...

  4. 转 cocos2d-x 优化(纹理渲染优化、资源缓存、内存优化)

    概述 包括以下5种优化:引擎底层优化.纹理优化.渲染优化.资源缓存.内存优化   引擎优化 2.0版本比1.0版本在算法上有所优化,效率更高.2.0版本使用OpenGl ES 2.0图形库,1.0版本 ...

  5. MySQL优化-一 、缓存优化

    body { font-family: Helvetica, arial, sans-serif; font-size: 14px; line-height: 1.6; padding-top: 10 ...

  6. Hibernate性能优化之EHCache缓存

    像Hibernate这种ORM框架,相较于JDBC操作,需要有更复杂的机制来实现映射.对象状态管理等,因此在性能和效率上有一定的损耗. 在保证避免映射产生低效的SQL操作外,缓存是提升Hibernat ...

  7. 秋色园QBlog技术原理解析:性能优化篇:缓存总有失效时,构造持续的缓存方案(十四)

    转载自:http://www.cyqdata.com/qblog/article-detail-38993 文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文 ...

  8. 如何在ASP.NET Core Web API中使用Mini Profiler

    原文如何在ASP.NET Core Web API中使用Mini Profiler 由Anuraj发表于2019年11月25日星期一阅读时间:1分钟 ASPNETCoreMiniProfiler 这篇 ...

  9. CDN网络(二)之配置和优化CDN核心缓存软件--squid

    前言 squid是众多CDN厂商使用的核心缓存软件,都在已有的基础上进行二次开发.在部署squid的时候,建议遵循下面的规范. 1. 使用大内存服务器 对于热点文件,我们让squid用内存缓存,这样大 ...

随机推荐

  1. Hadoop生态上几个技术的解释:hive、pig、hbase 关系与区别

    hadoop生态圈 Pig 一种操作hadoop的轻量级脚本语言,最初又雅虎公司推出,不过现在正在走下坡路了.当初雅虎自己慢慢退出pig的维护之后将它开源贡献到开源社区由所有爱好者来维护.不过现在还是 ...

  2. Windows 7 Ultimate(旗舰版)SP1 32/64位官方原版下载地址

    MSDN于2011年5月12日,最新发布简体中文Windows 7 Ultimate 旗舰版 SP1 DVD镜像安装包,分32位和64位两个版本.最新发行代号分别是:677486(32位),67740 ...

  3. New动态分配 Delete 释放内存

    在C++中,对于变量和对象都是编译器在编译时分配好的,对于数组初始化时,无法确定多少内存,很容意造成大开小用的情况. new  动态分配 一般格式:1. 指针变量名 =new  类型标识符; 2.指针 ...

  4. X-UA-Compatible也无法解决的IE11兼容问题

    3月8日接到一位用户的电话,说写博客时编辑器显示不出来.浏览器用的是披着360外衣的IE11,编辑器用的是CuteEditor. 当时电脑上没安装IE11,用IE10测试正常,心想应该是一个手到擒来的 ...

  5. 解决ORA-29857:表空间中存在域索引和/或次级对象 & ORA-01940:无法删除当前连接的用户问题 分类: oracle sde 2015-07-30 20:13 8人阅读 评论(0) 收藏

    今天ArcGIS的SDE发生了一点小故障,导致系统表丢失,所以需要重建一下SDE数据库,在删除SDE用户和所在的表空间过程中遇到下面两个ORA错误,解决方法如下: 1)删除表空间时报错:ORA-298 ...

  6. flask源码剖析--请求流程

    想了解这篇里面的内容,请先去了解我另外一篇博客Flask上下文 在了解flask之前,我们需要了解两个小知识点 偏函数 import functools def func(a1,a2): print( ...

  7. MegaCli 监控raid状态 限戴尔服务器

    MegaCli 监控raid状态 MegaCli是一款管理维护硬件RAID软件,可以通过它来了解当前raid卡的所有信息,包括 raid卡的型号,raid的阵列类型,raid 上各磁盘状态,等等.通常 ...

  8. Python开发【Django】:CMDB基础

    浅谈ITIL TIL即IT基础架构库(Information Technology Infrastructure Library, ITIL,信息技术基础架构库)由英国政府部门CCTA(Central ...

  9. 解决Android中ListView列表只显示一项数据的问题

    思路:获取每项item的高度,并相加,再加上分割线的高度,作为整个ListView的高度,方法如下: public static void setListViewHeightBasedOnChildr ...

  10. GPIO模拟SPI

    上次用gpio模拟i2c理解i2c协议.相同的,我用gpio模拟spi来理解spi协议. 我用的是4线spi,四线各自是片选.时钟.命令/数据.数据. 数据在时钟上升沿传递,数据表示的是数据还是命令由 ...