2012年2月24日 星期五

Ruby on Rails Memory Usage

最近公司產品銷量不錯,但是Server Side使用的Memory量卻大爆發,這會造成Server很大的負擔。
檢查後發現是因為Ruby on Rails使用的memory以驚人的速度在增加,
只要一有request進來就增加,一個request使用的Memory量也非常多,
而且即使request結束很久了,也完全沒有release的跡象。
這怎麼想都有問題,研究了好一陣子後,終於發現問題所在,結論如下:

1. ActiveRecord非常巨大,需要謹慎使用。
因為我們的database中有很多原本就存在的table,而且這些table未來也還會持續增加。
但這些資料rails也需要存取,又想運用到ActiveRecord來做db存取,
因此就採用動態開ActiveRecord object的形式來解決這個問題。

當時找到了些方式來幫我動態建立ActiveRecord object:

def create_class(class_name, superclass, &block)
klass = Class.new superclass, &block
Object.const_set class_name, klass
end



def create_model(klass_name, db_table_name)
create_class(klass_name, ActiveRecord::Base) do
set_table_name db_table_name.to_sym
end
end


這樣子只需要呼叫create_model,給入klass_name,跟這個class要對應到的table name,就搞定了。
只是後來測試發現,這部分非常非常吃Memory。


2. Ruby的Garbage Collection跟我想像中不一樣。
當初在解memory問題時,一開始有想到上面寫法可能有問題。
但是想像中,如果我重複宣告了class name相同的object,那舊的object應該會被GC掉才對。
因為舊的object再也沒有人可以access它了應該會被自動清掉,不然就會造成memory leak。
但是看來我的想像有問題。
因為每個request進來時,object一直被重新建立,
但是舊的object佔用的memory卻一直沒有被釋放出來也一直再那裡。
所以才會有上面說的,每個request都造成Memory增加,即使過了很久,也沒有下降的趨勢。


3. 當時採用的暫時解法是,再crontab裡面寫,讓系統一段時間就去touch restart。
(我們是用apache+passenger,這個組成其實也是個問題啦XD)
而每次touch後,memory就會釋放出來了,然後再重新開始堆開,算差不多的時間,就讓他再touch一次。
不是個根本解,不過卻也成功的讓我們多爭取到了不少時間,把問題解決。
感謝Felix建議!


4. 現在的解法是,把建立object全部移到initializer裡面進行。
這樣子就會全部共用同一個object,而不會每一個request都建立了一份。
於是memory的使用狀況就平穩下來了,再也不會直線上升了!喔耶!


5. 那個時候因為非常懷疑是memory leak的問題,所以也看了些相關文章。
關鍵字就是ruby,memory leak,memory usage,garbage collection之類的。
也有些可以幫助尋找memory leak的gem可以試用:
像是oinkmemprof
再來也可以裝newrelic,這會讓監控memory使用狀況容易許多!
當然newrelic還有其他更棒的用途,是個很值得用的監控工具!



總結,果然每de個bug,就會多了解很多阿!
哈哈哈,還有超多要學的啦!

沒有留言:

張貼留言