石锅拌饭

Tag: rubyonrails

财帮子性能优化简报

by Robin Lu on Sep.23, 2007, under Uncategorized

财帮子从三月底正式上线到现在,已经快半年了,网站的流量差不多每月翻一翻。

最近这个星期,每天晚上8点到11点的高峰期,每个小时页面浏览一般在5万到7万,最高可以接近10万。除去由web server处理的静态文件等请求,每个小时由rails处理的请求从10万到30万,峰值可以达到200req/s。

与此同时,根据基金理财网站的特点,浏览高峰期,计算量高峰期和缓存失效高峰期刚好重合,我们接受的是比一般ruby on rails应用更严峻的挑战。

有人说财帮子已经是国内规模最大的ruby on rails应用之一了,这点我们无法考证,但随着量的增长,我们的确对ruby on rails的性能有了越来越多的认识。

别指望一劳永逸

从建站初期,我们就采取了各种性能优化的措施,SQLSession, Fragmeng Cache, Memcached…所有你能从教科书里看到的。但是,很多问题是慢慢暴露出来的,你需要根据实际情况来调整缓存策略,修改程序结构,优化算法,甚至优化rails本身。随着量的不断增长,碰到的问题会越来越特殊,书本、社区包括搜索引擎都很难再给你提供现成的解决方案,完全变成对你经验和能力的挑战。财帮子两个星期以前,遇到严重的性能问题,最终我们采用了相当非主流的部署方案和打了自己补丁的web server,成功度过了难关。

你不在美国

没有Google Adsense来养活你,人工费用和带宽、机位费也不成比例,你不可能有点性能问题就靠加服务器来解决。绝不是否定scale out的策略,但在此之前,一定要尽可能挖掘现有程序的潜力,发现已有和潜在的性能问题并解决。否则,盲目的scale out可能只是掩盖了真正的问题,并让你花冤枉钱。

rails不是你的SQL专家

activerecord成功封装了数据库接口,并提供了很多灵活便捷的功能。但是,完全依赖activerecord和ruby,你可能碰到严重的性能问题。find中的include和select选项能带来一些好处,但也非常有限。有时候你不得不写复杂难看的SQL,来代替优美的rails语句。rails纯粹主义在这时候不能为你赚到CPU。即便使用了缓存机制,而且缓存命中率很高,在访问量基数变大时,缓存释放和重建也有可能增大到相当的数量。数据库性能优化仍然是rails应用不可回避的问题。

log是你最好的朋友

log是所有性能调试的起点,从development log到production log,从rails log到web server log,不同的log有不同的侧重点,学会分析log,每个log都可能为你提供解决问题的蛛丝马迹。另外,要熟练使用benchmark结合log做profiling,当real time远大于db time + rendering time的时候,这点尤其总要。

12 Comments :, , more...

TextMate遇到Rails Console

by Robin Lu on Jul.20, 2007, under Uncategorized

做Ruby On Rails开发,肯定用到Rails Console。头脑里有什么想法,都可以马上在console里先实验一下,调试、测试更是离不开。唯一麻烦的就是readline提供的行编辑功能实在有限,尤其当你有一段代码,需要修改来修改去在console里运行,能累出一头汗来。
我写了一个简单的TextMate command,可以在TextMate里完成代码,然后按一个快捷键(我目前的设置是ctrl+apple+c),文档中的代码或者被选中的代码就可以在console里执行,并将结果输出到一个新窗口。你不需要离开TextMate,就可以玩console了。
如果你也需要这个功能,可以下载这个文件:
run in console.tmCommand

4 Comments :, more...

rails缓存机制的几个问题

by Robin Lu on Apr.28, 2007, under Uncategorized

ruby on rails提供了一些内建的cache机制,我们比较多的用到了其中的fragment cache。在实际使用过程中,发现了一些问题,如果你不注意,performance和正确性可能都会受到影响。

cache的重复读取问题

这是从Agile Web Development with Rails中抄下来的一段代码:

1
2
3
4
5
6
7
def list 
  @dynamic_content = Time.now.to_s 
  unless read_fragment(:action => 'list') 
    logger.info("Creating fragment") 
    @articles = Article.find_recent 
  end 
end

相信大家也都是用判断read_fragment返回值的方法来看cache是否存在。但你有没有想过,read_fragment这个函数的真正含义是读取cache。在view里,helper cache又会把cache的内容读一遍。假设你的cache用的是FileStore,下面是cache.rb中read的实现:

1
2
3
def read(name, options = nil) #:nodoc:
    File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
end

两次调用read_fragment就是两次独立的文件IO,而且每次都全文读入。第一次读完全是浪费。
解决方案:
一个是在controller里将read_fragment返回的内容保存在一个instance variable里,再到view里显示。这个做法的缺点是无法在view里使用现成的helper cache了,需要重写一个类似的helper,判断相关的instance variable而不是再去read。
另一个方法是自己写一个check_fragment,用File.exist?去判断,效率要高得多。

cache判断的原子性问题

还是上面那段代码:

1
2
3
4
5
6
7
def list 
  @dynamic_content = Time.now.to_s 
  unless read_fragment(:action => 'list') 
    logger.info("Creating fragment") 
    @articles = Article.find_recent 
  end 
end

假设controller在read_fragment的时候,cache存在,但就在运行出了这个unless之后,render view之前,cache被清除了,这完全有可能发生。就在render view的时候,helper cache发现cache不存在,看看下面的代码(仍然来自Agile Web Development with Rails),会发生什么?

1
2
3
4
5
6
7
< % cache do %> < !- Here's the content we cache -> 
  <ul> 
    < % for article in @articles -%> 
      <li><p>< %= h(article.body) %></p></li> 
    < % end -%> 
  </ul> 
< % end %> < !- End of the cached content ->

@articles是nil,你的程序抛出异常了!
解决方法:
没有什么万灵药,要根据具体的情况来分析。最简单的就是在view里判断@articles是否存在,总比抛异常好。还有一个方法就是把@articles的初始化放到view的cache do里面,如果嫌view里代码太多,就放一个helper里。

可怕的expire_fragment

其实只有一半是可怕的。
如果用FileStore,expire_fragment做的其实就是删除文件。假如你指定了是哪个cache,没问题。但expire_fragment还支持正则表达式,假如你指定的是一个正则表达式,它会干什么?看action_controller/caching.rb里的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def delete_matched(matcher, options) #:nodoc:
  search_dir(@cache_path) do |f|
    if f =~ matcher
      begin
        File.delete(f)
      rescue Object => e
        # If there's no cache, then there's nothing to complain about
      end
    end
  end
end
...
def search_dir(dir, &amp;callback)
  Dir.foreach(dir) do |d|
    next if d == "." || d == ".."
    name = File.join(dir, d)
    if File.directory?(name)
      search_dir(name, &amp;callback)
    else
      callback.call name
    end
  end
end

遍历所有cache目录下的文件,所有。然后一个一个去和正则表达式匹配。如果你不幸有上千个cache fragment,知道有多慢么?自己试试吧。
解决方案:
首先不是一个解决方案,是一个忠告:不要传给expire_fragment一个正则表达式。
其次,你可能可以写一个expire_fragment_from(root_path, reg),让它从一个指定的路径下做这样的遍历。然后优化cache fragment的目录结构,尽量缩小作用域。

Ruby On Rails是一个很好的框架,但仍然有要改进的地方。如果你在使用rails cache上有什么心得,欢迎交流。

7 Comments :, , more...

财帮子基金理财社区上线

by Robin Lu on Mar.29, 2007, under Uncategorized

两个月没写blog,bloglines里积攒了七千条未读信息,不是失踪了,实在是忙,现在总算可以看看我们的成果了。
财帮子-基金理财社区
财帮子是一个基金理财在线服务,刚刚迈出第一步,说起来也就是基金信息、个人基金管理、数据分析、论坛这些东西,但希望能在功能和用户体验上可以给大家一些不同的感觉。
经过一段时间的小范围用户测试使用,现在拿出来见见人,欢迎拍砖。

19 Comments :, , , more...

打开一个rails工程需要几步

by Robin Lu on Jan.24, 2007, under Uncategorized

通常,在mac下,打开一个rails工程,开始工作,要经历以下步骤:

  1. 用TextMate打开rails工程。
  2. 打开iTerm。
  3. 运行gem_server,为查帮助做好准备。
  4. 进入rails工程的目录,运行mongrel_rails start。
  5. 在iTerm里再开一个tab,进入rails工程目录,运行tail -f log/development.log。
  6. 打开Safari,开两个tab,一个打开http://localhost:8808看gem帮助,一个打开http://0.0.0.0:3000。

比把大象放到冰箱里复杂多了。
做了一个TextMate Bundle的命令,用TextMate打开rails工程后,开一个rb文件(不打开文件没法运行bundle命令),然后按苹果-ctrl-o,从2到6就自动帮你做了。另外还加了一个命令,只要按苹果-ctrl-l就可以直接唤出那个显示log的窗口。这下方便多了。
这个Bundle缺省认为你使用iTerm和Mongrel。
点击下载打开rails工程的TextMate Bundle

4 Comments :, more...

Archives

Browse by tags