近期一个项目要上线(终于要交付了),交付前进行了一些大数据量下的性能优化,心得记录下来:
1. 在开发的初期就要使用大量的数据。10K 比较合适。 有生产数据最好(比如需要跟其他系统进行交互),没有的话,就自己生成(factory girl). 很多时候,打开一个性能糟糕的页面,100条数据下看不出来什么,但是10K数据下就看出速度来了。
2. 使用设计良好的MVC模式。该放在MODEL里的,绝对不要放在CONTROLLER里,更不要放在VIEW里。原因是:
2.1 耦合严重。我们要 decouple.
2.2 很多性能测试工具,难以测试出页面中的方法,但是可以测试出MODEL中的方法。
2.3 放在MODEL中,容易写单元测试(测试代码易于编写,那么实现代码就易于编写)。
2. 另外,最好一次性的读取数据,例如,你需要读取3条记录,最好使用这样的方式:
@device = Device.where("id in (1,2,3)")
它会生成这样形式的 SQL: select .... where id in (1,2,3)
而绝对不要在你的页面中 :
[1,2,3].each { |i| Device.where("id = #{i}") }
因为后者会链接数据库 3 次, 而前者只一次。 想一想,如果你的某个循环需要重复链接数据库1000次,该是什么效果? 所以。... 回头再详述一下。(TODO)
3. WEBSERVICE 用的越少越好。 回头专门详述。 总之,这个是拖累系统速度的毒瘤。能不用就绝对不用,非用不可的话,一定要用缓存。
4. 缓存一定要用。可以不用MEMED CACHE这样重量级的,但是起码轻量级的一定要用。例如:
config.cache_store = :memory_store, {:expires_in => 60}
使用 observer与之配合,效果更好。
在上一个项目用,我用cache来缓存系统的常量。它们是可被配置的,保存在数据库中,一般情况下不会被修改。 回头我也写个文章来记述一下。
5. 尽量少与其他 的系统交互。 这个也很拖累性能。(跟webservice 的理由一样,一个response 需要2~4秒,客户体验不好)。 一定要加缓存。 例如,目前这个系统的“authorization" (授权),不是使用cancan或者 本地的数据库表来授权,而是要访问远程的某个服务器,以GET方式读取某个用户的权限。
一般读取一次需要1~2秒,如果某个页面有5个地方需要授权,那么打开这个页面就要消耗10秒左右的时间。 所以,一定要做缓存。。。
TODO 同上面第四点,写个专门的文章。。。
6. 使用SQLITE3的话,不要使用transaction. 可以使用delayed_job. 因为一旦使用了TRANSACTION, 那么很容易出现 锁死的情况。用户看到的就是 500 错误。 这个在生产环境下是不可接受的。 哪怕速度慢一点也没关系,我们用一个沙漏啥的小动画来告诉用户,这个东西在后台处理,需要等多少秒。用户也就知道了:哦,它在正常工作,等一会儿我就可以继续操作了。 (这个特别有效,比如说: 上传100K个数据的 CSV, 对它进行验证,读取,验证重复,导入的话,普通方式1000秒, 使用了transaction 300秒, 但是我们用delayed_job的话,用户1秒后就可以看到响应结果(正常处理。。。啥的文字),就会体验很好。 )
7. 同六,凡是会让用户等待过久的地方,使用DELAYED——JOB。
8. 很多功能实现起来很容易,但是需要对某个功能进行进度控制,结果控制的话,就非常麻烦。例如:
8.1 单纯的导入100K个数据,非常容易。
8.2 要求导入100K 个数据, 要求对某个属性进行验证,使之不能重复,也容易。
8.3 在8.2的基础上,还要求显示结果,成功的有多少,失败的有多少。这个就稍有些复杂了。你的代码开始凌乱,有了坏味道,特别是单元测试不够丰富的时候,原有的良好结构开始破坏。
8.4 在8.3的基础上,你使用了delayed_job,这个时候,要求用户看到进度(比如30%), 这个时候,你的代码的复杂度,是8.1的10倍, 8.2的 5倍。 起码在我目前这个项目中是这样的。
9. 数据的冗余很多情况下也是必要的。例如看这些功能:
9.1 我们要调试100K个设备
9.2 调试完毕后,记录每个设备的调试信息。
9.3 某天,我们删除这些设备。
9.4 管理员查看调试记录,发现很多调试信息都丢失了。为什么?
原来,系统的实现方式,是每个调试信息(debug_info) 对应于 设备(device)。那么一旦 设备 被删掉之后,那么调试信息也就没有了。这个是不被允许的。所以,比较理想的办法,是在每个调试信息中,加上对应设备的信息。 虽然看起来比较冗余,但是这是一种平衡。
我们目前上线的系统中,几乎每个模块都有这样的功能和需求。所以。。。哎。。。
10. 保持开发机器和生产环境的机器 环境要一致。这里的一致包括:
1. 操作系统。 哪怕一个是CENTOS 一个是 UBUNTU, 将就吧,但最好一模一样
2. 语言版本。 要么都是RUBY 1.8.7,要么都是 RUBY 1.9,要么都是 RVM。
3. 数据库, 绝对不能一个是MYSQL, 一个是SQLITE3.
4. 最好都能访问一个网络,不要出现一个处于内网一个处于外网,一个能访问GIT一个不能,一个能访问通过域名 ooxx.com 访问 授权服务器,另一个则只能通过 ip 访问。
5. 最好VIM的版本都是一样的。不能一个 7.3一个 6.9 ,一个有很多PLUGIN, 另一个没有任何PLUGIN。
6. 速度要一致。(生产环境的服务器的 SSH 速度不能太慢,例如不要一个在美国一个在大陆,否则 开发人员SSH 生产服务器之后,敲一下键盘3秒钟之后才看到结果,估计调试一个BUG要走神20次)
以上几点,任何一个不满足都是有麻烦的。
11. 一定使用rails3 的 asset pipeline.
这个效果相当明显。记得最初的项目,我们用了20个JS库, 以及好多个CSS, 每次查看网络通信的时候,发现由于每个JS,CSS,IMAGE都要让客户端/服务器重新发送一个 request/response,时间占用的很多。使用了asset pipeline之后,性能改善太多,最显著的就是:只有一个 JS, 一个 CSS 文件。而且RAILS3提供的文档(优化 APACHE, NGINX )也对缓存非常有效。现在图片也缓存的非常好。 打个比方, 原来需要消耗10秒才能发送完毕CSS/JS/图片,现在只需要2~5秒左右。太棒了。
12. 一定要使用coffee javascript. 这个不但提高开发效率,还提高执行效率,还能提供压缩。 想起了 HIBERNATE V。S。 RAW SQL。
1. 在开发的初期就要使用大量的数据。10K 比较合适。 有生产数据最好(比如需要跟其他系统进行交互),没有的话,就自己生成(factory girl). 很多时候,打开一个性能糟糕的页面,100条数据下看不出来什么,但是10K数据下就看出速度来了。
2. 使用设计良好的MVC模式。该放在MODEL里的,绝对不要放在CONTROLLER里,更不要放在VIEW里。原因是:
2.1 耦合严重。我们要 decouple.
2.2 很多性能测试工具,难以测试出页面中的方法,但是可以测试出MODEL中的方法。
2.3 放在MODEL中,容易写单元测试(测试代码易于编写,那么实现代码就易于编写)。
2. 另外,最好一次性的读取数据,例如,你需要读取3条记录,最好使用这样的方式:
@device = Device.where("id in (1,2,3)")
它会生成这样形式的 SQL: select .... where id in (1,2,3)
而绝对不要在你的页面中 :
[1,2,3].each { |i| Device.where("id = #{i}") }
因为后者会链接数据库 3 次, 而前者只一次。 想一想,如果你的某个循环需要重复链接数据库1000次,该是什么效果? 所以。... 回头再详述一下。(TODO)
3. WEBSERVICE 用的越少越好。 回头专门详述。 总之,这个是拖累系统速度的毒瘤。能不用就绝对不用,非用不可的话,一定要用缓存。
4. 缓存一定要用。可以不用MEMED CACHE这样重量级的,但是起码轻量级的一定要用。例如:
config.cache_store = :memory_store, {:expires_in => 60}
使用 observer与之配合,效果更好。
在上一个项目用,我用cache来缓存系统的常量。它们是可被配置的,保存在数据库中,一般情况下不会被修改。 回头我也写个文章来记述一下。
5. 尽量少与其他 的系统交互。 这个也很拖累性能。(跟webservice 的理由一样,一个response 需要2~4秒,客户体验不好)。 一定要加缓存。 例如,目前这个系统的“authorization" (授权),不是使用cancan或者 本地的数据库表来授权,而是要访问远程的某个服务器,以GET方式读取某个用户的权限。
一般读取一次需要1~2秒,如果某个页面有5个地方需要授权,那么打开这个页面就要消耗10秒左右的时间。 所以,一定要做缓存。。。
TODO 同上面第四点,写个专门的文章。。。
6. 使用SQLITE3的话,不要使用transaction. 可以使用delayed_job. 因为一旦使用了TRANSACTION, 那么很容易出现 锁死的情况。用户看到的就是 500 错误。 这个在生产环境下是不可接受的。 哪怕速度慢一点也没关系,我们用一个沙漏啥的小动画来告诉用户,这个东西在后台处理,需要等多少秒。用户也就知道了:哦,它在正常工作,等一会儿我就可以继续操作了。 (这个特别有效,比如说: 上传100K个数据的 CSV, 对它进行验证,读取,验证重复,导入的话,普通方式1000秒, 使用了transaction 300秒, 但是我们用delayed_job的话,用户1秒后就可以看到响应结果(正常处理。。。啥的文字),就会体验很好。 )
7. 同六,凡是会让用户等待过久的地方,使用DELAYED——JOB。
8. 很多功能实现起来很容易,但是需要对某个功能进行进度控制,结果控制的话,就非常麻烦。例如:
8.1 单纯的导入100K个数据,非常容易。
8.2 要求导入100K 个数据, 要求对某个属性进行验证,使之不能重复,也容易。
8.3 在8.2的基础上,还要求显示结果,成功的有多少,失败的有多少。这个就稍有些复杂了。你的代码开始凌乱,有了坏味道,特别是单元测试不够丰富的时候,原有的良好结构开始破坏。
8.4 在8.3的基础上,你使用了delayed_job,这个时候,要求用户看到进度(比如30%), 这个时候,你的代码的复杂度,是8.1的10倍, 8.2的 5倍。 起码在我目前这个项目中是这样的。
9. 数据的冗余很多情况下也是必要的。例如看这些功能:
9.1 我们要调试100K个设备
9.2 调试完毕后,记录每个设备的调试信息。
9.3 某天,我们删除这些设备。
9.4 管理员查看调试记录,发现很多调试信息都丢失了。为什么?
原来,系统的实现方式,是每个调试信息(debug_info) 对应于 设备(device)。那么一旦 设备 被删掉之后,那么调试信息也就没有了。这个是不被允许的。所以,比较理想的办法,是在每个调试信息中,加上对应设备的信息。 虽然看起来比较冗余,但是这是一种平衡。
我们目前上线的系统中,几乎每个模块都有这样的功能和需求。所以。。。哎。。。
10. 保持开发机器和生产环境的机器 环境要一致。这里的一致包括:
1. 操作系统。 哪怕一个是CENTOS 一个是 UBUNTU, 将就吧,但最好一模一样
2. 语言版本。 要么都是RUBY 1.8.7,要么都是 RUBY 1.9,要么都是 RVM。
3. 数据库, 绝对不能一个是MYSQL, 一个是SQLITE3.
4. 最好都能访问一个网络,不要出现一个处于内网一个处于外网,一个能访问GIT一个不能,一个能访问通过域名 ooxx.com 访问 授权服务器,另一个则只能通过 ip 访问。
5. 最好VIM的版本都是一样的。不能一个 7.3一个 6.9 ,一个有很多PLUGIN, 另一个没有任何PLUGIN。
6. 速度要一致。(生产环境的服务器的 SSH 速度不能太慢,例如不要一个在美国一个在大陆,否则 开发人员SSH 生产服务器之后,敲一下键盘3秒钟之后才看到结果,估计调试一个BUG要走神20次)
以上几点,任何一个不满足都是有麻烦的。
11. 一定使用rails3 的 asset pipeline.
这个效果相当明显。记得最初的项目,我们用了20个JS库, 以及好多个CSS, 每次查看网络通信的时候,发现由于每个JS,CSS,IMAGE都要让客户端/服务器重新发送一个 request/response,时间占用的很多。使用了asset pipeline之后,性能改善太多,最显著的就是:只有一个 JS, 一个 CSS 文件。而且RAILS3提供的文档(优化 APACHE, NGINX )也对缓存非常有效。现在图片也缓存的非常好。 打个比方, 原来需要消耗10秒才能发送完毕CSS/JS/图片,现在只需要2~5秒左右。太棒了。
12. 一定要使用coffee javascript. 这个不但提高开发效率,还提高执行效率,还能提供压缩。 想起了 HIBERNATE V。S。 RAW SQL。