对优化Ruby on Rails性能的一些办法的探究

2019-09-25 09:39:32于海丽

Tasks  id  name Tags  id  name Tasks_Tags  tag_id  task_id

加载任务以及它们的 Rails 标签,你会这样做:

这段代码有问题,它为每个标签创建了对象,花费很多内存。可选择的解决方案,将标签在数据库预加载。

tasks = Task.select <<-END
  *,
  array(
  select tags.name from tags inner join tasks_tags on (tags.id = tasks_tags.tag_id)
  where tasks_tags.task_id=tasks.id
  ) as tag_names
 END
 > 0.018 sec

这只需要内存存储额外一列,有一个数组标签。难怪快 3 倍。

2.2.2 数据集合

我所说的数据集合任何代码去总结或者分析数据。这些操作可以简单的总结,或者一些更复杂的。以小组排名为例。假设我们有一个员工,部门,工资的数据集,我们要计算员工的工资在一个部门的排名。

SELECT * FROM empsalary;
 depname | empno | salary
-----------+-------+-------
 develop |  6 | 6000
 develop |  7 | 4500
 develop |  5 | 4200
 personnel |  2 | 3900
 personnel |  4 | 3500
 sales  |  1 | 5000
 sales  |  3 | 4800

你可以用 Ruby 计算排名:

salaries = Empsalary.all
salaries.sort_by! { |s| [s.depname, s.salary] }
key, counter = nil, nil
salaries.each do |s|
 if s.depname != key
 key, counter = s.depname, 0
 end
 counter += 1
 s.rank = counter
end

Empsalary 表里 100K 的数据程序在 4.02 秒内完成。替代 Postgres 查询,使用 window 函数做同样的工作在 1.1 秒内超过 4 倍。

SELECT depname, empno, salary, rank()
OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;
 depname | empno | salary | rank 
-----------+-------+--------+------
 develop |  6 | 6000 | 1
 develop |  7 | 4500 | 2
 develop |  5 | 4200 | 3
 personnel |  2 | 3900 | 1
 personnel |  4 | 3500 | 2
 sales  |  1 | 5000 | 1
 sales  |  3 | 4800 | 2

4 倍加速已经令人印象深刻,有时候你得到更多,到 20 倍。从我自己经验举个例子。我有一个三维 OLAP 多维数据集与 600k 数据行。我的程序做了切片和聚合。在 Ruby 中,它花费了 1G 的内存大约 90 秒完成。等价的 SQL 查询在 5 内完成。

2.3 优化 Unicorn

如果你正在使用Unicorn,那么以下的优化技巧将会适用。Unicorn 是 Rails 框架中最快的 web 服务器。但是你仍然可以让它更运行得快一点。

2.3.1 预载入 App 应用

Unicorn 可以在创建新的 worker 进程前,预载入 Rails 应用。这样有两个好处。第一,主线程可以通过写入时复制的友好GC机制(Ruby 2.0以上),共享内存的数据。操作系统会透明的复制这些数据,以防被worker修改。第二,预载入减少了worker进程启动的时间。Rails worker进程重启是很常见的(稍后将进一步阐述),所以worker重启的速度越快,我们就可以得到更好的性能。