2016-04-21 6 views
5

Ich habe Ruby-Code, der mehr oder weniger wie folgt aussehenRuby-Speichers nicht die Freigabe

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 

Was da ich bin ist, dass der RSS-Speicher (htop) weiter wachsen und an einem Punkt erreicht er 10GB. Ich bin mir nicht sicher, warum das passiert, aber Erinnerung scheint von Ruby nie wieder auf das Betriebssystem freigegeben zu werden.

Ich bin mir bewusst, dass es eine lange Liste von Fragen gibt, die damit in Verbindung stehen. Ich habe sogar versucht, durch den Code zu ändern, um so auszusehen (letzte 3 Zeile spezifisch) .i.e Laufen GC.start manuell Ergebnis immer noch dasselbe.

while true 

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

testeten diese auf Ruby-Version 2.2.2 und 2.3.0

EDIT: Andere Detail

1) OS.

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

2) Rubin installiert und erfüllt über RVM.

3) Active Version 4.2.6

+1

'wann'? Meinst du "während"? – matt

+1

'mehr oder weniger so aussehen 'ist es vielleicht besser, genauen Code zu zeigen? – fl00r

+0

@ fl00r Es ist genau Code erwarten, dass der Klassen- oder Modellname geändert wird – Viren

Antwort

2

Ich kann Ihnen nicht sagen, die Quelle des Speicherlecks, aber ich Spion etwas niedrig hängenden Früchte.

Aber zuerst zwei Dinge:

  1. Sind Sie sicher, dass Active der richtige Weg ist, Daten von einer Datenbank in einer anderen zu kopieren? Ich bin sehr zuversichtlich, dass es nicht ist. Jedes wichtige Datenbankprodukt verfügt über robuste Export- und Importfunktionen, und die Leistung, die Sie dort sehen, wird um ein Vielfaches besser sein als in Ruby, und Sie können diese Tools immer in Ihrer App aufrufen. Denken Sie darüber nach, bevor Sie diesen Weg fortsetzen.

  2. Woher kommt die Nummer 10.000? Ihr Code schlägt vor, dass Sie wissen, dass es keine gute Idee ist, alle der Datensätze auf einmal zu holen, aber 10.000 ist immer noch eine Menge von Datensätzen. Sie können einige Gewinne sehen, indem Sie einfach verschiedene Zahlen ausprobieren: 100 oder 1000, sagen wir.

das gesagt ist, wollen wir in graben, was diese Linie zu tun ist:

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

Der erste Teil, User.limit(10000).offset(offset) schafft eine Activerecord :: Relation Objekt darstellt Ihre Anfrage. Wenn Sie as_json darauf aufrufen, wird die Abfrage ausgeführt, die 10.000 Benutzermodellobjekte instanziiert und in ein Array einfügt, und anschließend wird aus jedem dieser Benutzerobjektattribute ein Hash erstellt. (Werfen Sie einen Blick auf die Quelle für ActiveRecord::Relation#as_jsonhere.)

Mit anderen Worten, Sie erstellen 10.000 Benutzerobjekte nur, um sie wegwerfen, nachdem Sie ihre Attribute haben.

Also, ein schneller Gewinn ist es, diesen Teil ganz zu überspringen. Markieren Sie die Rohdaten:

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 gibt einen Array von Arrays mit den Werten aus jedem Datensatz.Innerhalb der user_values.each Schleife drehen wir dieses Werte-Array in einen Hash. Es müssen keine Benutzerobjekte instanziiert werden.

Lassen Sie uns jetzt einen Blick auf diese:

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

Dieser einen Datensatz aus der Datenbank auswählt, instanziiert ein Mitglied Objekt und aktualisiert anschließend den Datensatz in der Datenbank-10.000-mal in jeder Iteration der while Schleife . Dies ist der korrekte Ansatz Wenn Sie müssen überprüft werden, wenn der Datensatz aktualisiert wird. Wenn Sie nicht Validierungen ausführen müssen, obwohl, können Sie Zeit und Speicher durch, wieder speichern, keine Objekte instanziieren:

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

Der Unterschied besteht darin, dass ActiveRecord::Relation#update_all nicht den Datensatz aus der Datenbank auswählt oder instanziiert ein Member-Objekt, es aktualisiert es nur. Sie haben in Ihrem Kommentar oben gesagt, dass Sie eine eindeutige Einschränkung für die Spalte name haben, daher wissen wir, dass dadurch nur ein einzelner Datensatz aktualisiert wird.

Nachdem Sie diese Änderungen vorgenommen haben, müssen Sie immer noch mit der Tatsache kämpfen, dass Sie in jeder Iteration der Schleife while 10.000 UPDATE-Abfragen durchführen müssen. Erneut sollten Sie in Erwägung ziehen, die integrierte Export- und Importfunktionalität Ihrer Datenbanken zu verwenden, anstatt zu versuchen, Rails dies zu ermöglichen.

+0

Danke für die Antwort. Entschuldigen Sie die Kopie zu anderer Datenbank ist nicht dieses geradeaus folglich kann pg_import und pg_dump nicht benutzen. – Viren

+0

Ich habe den Code aktualisiert, um zu zeigen, wie die Kopie funktioniert. – Viren

+0

Dennoch gibt es bessere Möglichkeiten, dies zu tun. Du tust im Grunde ein [upsert] (http://stackoverflow.com/questions/17267417/how-to-upsert-merge-insert-on-duplicate-update-in-postgresql) mit einer einfachen Bedingung auf 'update_at' .Wenn sich die Daten in zwei separaten Tabellen in derselben Datenbank befinden, können Sie einen JOIN mit der gleichen Bedingung ausführen, um die Zeilen hochzuladen. Da sie sich nicht in derselben Datenbank befinden, können Sie entweder in eine Tabelle mit einem anderen Namen exportieren und importieren oder [postgres_fdw] verwenden (http://www.postgresql.org/docs/9.3/static/postgres-fdw. html) um direkt mit der anderen Datenbank zu verbinden. –