2016-04-21 31 views
5

我有Ruby代碼是或多或少這個樣子的紅寶石不釋放內存

offset = 0 
index = 1 

User.establish_connection(..) # db1 
class Member < ActiveRecord::Base 
    self.table_name = 'users' 
end 

Member.establish_connection(..) #db2 

while true 
    users = User.limit(10000).offset(offset).as_json ## for a Database 1 
    offset = limit * index 
    index += 1 
    users.each do |u| 
    member = Member.find_by(name: u[:name]) 
    if member.nil? 
     Member.create(u) 
    elsif member.updated_at < u[:updated_at] 
     member.update_attributes(u) 
    end 
    end 
    break if break_condition 
end 

我所看到的是,RSS內存(HTOP)保持增長,並在一個點上達到10GB。我不確定爲什麼會發生這種情況,但內存似乎永遠不會被Ruby釋放回操作系統。

我知道有一個很長的問題列表與此內聯。我甚至嘗試通過代碼改變看起來像這樣(最後3行專門).i.e手動運行GC.start結果仍然相同。

while true 

.... 
... 
... 
users = nil 
GC.start 
break if break_condition 
end 

測試了這個關於Ruby版本2.2.22.3.0

編輯:其他細節

1)操作系統。

DISTRIB_ID=Ubuntu 
DISTRIB_RELEASE=15.04 
DISTRIB_CODENAME=vivid 
DISTRIB_DESCRIPTION="Ubuntu 15.04" 

2)ruby通過rvm安裝並編譯。

3)ActiveRecord的版本4.2.6

+1

'什麼時候?你是不是指'while'? – matt

+1

'或多或少看起來像這樣'也許最好是顯示確切的代碼? – fl00r

+0

@ fl00r準確的代碼期望班級或模型名稱已更改 – Viren

回答

2

我不能告訴你的內存泄漏的源頭,但我確實間諜一些低掛水果。

但首先,兩件事情:

  1. 你肯定ActiveRecord的是從一個數據庫中的數據複製到另一個正確的方法是什麼?我非常有信心,事實並非如此。每個主要的數據庫產品都具有強大的導出和導入功能,並且您將看到的性能比在Ruby中執行要好很多倍,並且您始終可以從您的應用程序中調用這些工具。在繼續沿着這條道路走下去之前,仔細想一想。

  2. 數字10,000從哪裏來?您的代碼表明,您知道一次獲取所有記錄並不是一個好主意,但10,000個記錄仍然很多。通過簡單地嘗試不同的數字,你可能會看到一些收益:例如100或1000。

這就是說,讓我們深入到這行做:

users = User.limit(10000).offset(offset).as_json 

第一部分,User.limit(10000).offset(offset)創建表示查詢一個ActiveRecord :: Relation對象。當你調用as_json時,會執行查詢,它將實例化10,000個用戶模型對象並將它們放入數組中,然後從每個這些用戶對象的屬性構造一個Hash。 (看看ActiveRecord::Relation#as_jsonhere的源代碼。)

換句話說,你正在實例化10,000個User對象,只有在獲得它們的屬性後才拋出它們。

所以,一個快速勝利就是完全跳過這一部分。只要選擇的原始數據:

user_keys = User.attribute_names 

until break_condition 
    # ... 
    users_values = User.limit(10000).offset(offset).pluck(user_keys) 

    users_values.each do |vals| 
    user_attrs = user_keys.zip(vals).to_h 
    member = Member.find_by(name: user_attrs["name"]) 
    member.update_attributes(user_attrs) 
    end 
end 

ActiveRecord::Calculations#pluck返回陣列與從每個記錄的值的數組。在user_values.each循環內部,我們將該值數組轉換爲哈希。無需實例化任何用戶對象。

現在讓我們來看看這個:

member = Member.find_by(name: user_attrs["name"]) 
member.update_attributes(user_attrs) 

這從數據庫中選擇記錄,實例化一個成員對象,然後更新數據庫,10,000次的記錄在while循環的每次迭代。這是正確的方法如果您需要驗證來更新該記錄時運行。如果您不需要驗證運行,不過,你可以再節省,時間和內存,沒有任何實例化對象:

Member.where(name: user_attrs["name"]).update_all(user_attrs) 

不同的是,ActiveRecord::Relation#update_all不會從數據庫中選擇記錄或實例一個成員對象,它只是更新它。你在上面的評論中說過,你對name列有一個唯一的約束,所以我們知道這隻會更新一條記錄。

做出這些更改後,您仍必須處理這樣一個事實,即您必須在while循環的每次迭代中執行10,000次UPDATE查詢。再次考慮使用數據庫的內置導出和導入功能,而不是試圖讓Rails執行此操作。

+0

感謝您的回答。道歉複製到不同的數據庫不是那麼簡單,因此不能使用pg_import和pg_dump。 – Viren

+0

我已更新代碼以顯示副本如何工作。 – Viren

+0

不過,有更好的方法來做到這一點。你基本上在'updated_at'上做了一個簡單的條件[upsert](http://stackoverflow.com/questions/17267417/how-to-upsert-merge-insert-on-duplicate-update-in-postgresql) 。如果數據位於同一個數據庫中的兩個單獨的表中,則可以使用相同的條件進行JOIN以使行被插入。由於它們不在同一個數據庫中,因此可以將其導出並導入到名稱不同的表中,或者使用[postgres_fdw](http://www.postgresql.org/docs/9.3/static/postgres-fdw。 html)直接連接到其他數據庫。 –