[RUBY] Eine Geschichte über die Reduzierung des Speicherverbrauchs auf 1/100 mit find_in_batches

Umgang mit großen Datenmengen bei reduziertem Speicherverbrauch

Schreiben Sie Code in Rails mit Blick auf den Speicher?

Rails verwendet Rubys ** Garbage Collection * 1 **, sodass Sie Code schreiben können, ohne sich Gedanken über die Freigabe von Speicher machen zu müssen.

* 1 Ruby sammelt Objekte, die nicht mehr verwendet werden, und gibt den Speicher automatisch frei.

Daher gibt es Fälle, in denen der Produktionsserver plötzlich ausfällt (aufgrund eines Speicherfehlers), ohne es zu bemerken, obwohl die Implementierung so ist, dass der Speicher unwissentlich aufgebraucht wird.

Der Grund, warum ich das sagen kann, ist, dass dieses Phänomen an dem Ort aufgetreten ist, an dem ich gerade arbeite.

Ich war für die Implementierung verantwortlich, habe aber viel aus dieser Erfahrung gelernt, daher werde ich eine Notiz hinterlassen, damit ich sie nicht vergesse.

Ursachenforschung

Zunächst müssen Sie untersuchen, wo der Speicherfehler auftritt.

Ich habe "ObjectSpace.memsize_of_all" verwendet, um die Speichernutzung in Rails zu untersuchen.

Mit dieser Methode können Sie die Speichernutzung untersuchen, die von allen lebenden Objekten in Bytes verbraucht wird.

Legen Sie diese Methode als Prüfpunkt an der Stelle fest, an der der Ausführungsprozess wahrscheinlich abfällt, und untersuchen Sie ständig, wo viel Speicherplatz verbraucht wird.

■ Verwendungsbeispiel zum Überprüfen der Speichernutzung

class Hoge
  def self.hoge
    puts 'Anzahl der Objektspeicher vor Speichererweiterung durch Karte'
    puts '↓'
    puts ObjectSpace.memsize_of_all <====Kontrollpunkt
    array = ('a'..'z').to_a
    array.map do |item|             <==== ①
      puts "#{item}Anzahl der Objektspeicher von"
      puts '↓'
      puts ObjectSpace.memsize_of_all <====Kontrollpunkt
      item.upcase
    end
  end
end

■ Ausführungsergebnis

irb(main):001:0> Hoge.hoge
Anzahl der Objektspeicher vor Speichererweiterung durch Karte
↓
137789340561

Anzahl der Objektspeicher von a
↓
137789342473

Anzahl der Objektspeicher in b
↓
137789342761

Anzahl der Objektspeicher in c
↓
137789343049

Anzahl der Objektspeicher in d
↓
137789343337

Anzahl der Objektspeicher von e
↓
137789343625

.
.
.

Anzahl der Objektspeicher von x
↓
137789349097
Anzahl der Objektspeicher von y
↓
137789349385
Anzahl der Objektspeicher in z
↓
137789349673
=> ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]

Anhand dieses Ausführungsergebnisses können Sie erkennen, dass die von der Karte übergebenen Daten zuerst sofort im Speicher erweitert werden und der Speicherverbrauch dort gestiegen ist. (Teil ①)

Sie können auch sehen, dass der Speicherverbrauch mit jedem Schleifenprozess zunimmt.

Wenn der Vorgang wie dieser Beispielcode einfach ist, gibt es kein Problem.

Wenn die übertragene Datenmenge groß ist und die durch die Schleifenverarbeitung durchgeführte Implementierung kompliziert ist, wird der Speicherverbrauch verringert.

** Ich erhalte einen Speicherfehler (ein Fehler, der auftritt, wenn die Speicherverarbeitung nicht mithalten kann). ** **.

Diese Untersuchung wurde auch durch das obige Verfahren untersucht, und als Ergebnis wurde der Schluss gezogen, dass ein Speicherfehler auftrat, weil die übertragene Datenmenge groß war und die umfangreiche Verarbeitung des Ausspuckens von Abfragen mit Karte implementiert wurde.

Gegenmaßnahme

Ich verstehe die Ursache.

Lassen Sie uns als nächstes über Gegenmaßnahmen nachdenken.

Die ersten Maßnahmen, die ich mir ausgedacht habe, sind die folgenden drei.

1.Erhöhen Sie das Gedächtnis mit der Kraft des Geldes
2. Thread(Faden)Parallelverarbeitung mit
3.Stapelverarbeitung

1. Erhöhen Sie das Gedächtnis mit der Kraft des Geldes

Um ehrlich zu sein, ist dies die schnellste und Sie müssen nur die Speicherspezifikationen des Servers mit der Kraft des Geldes erhöhen, also lasst uns das tun!

Ich dachte.

Es gibt keine andere speicherintensive Implementierung als diesen Prozess, daher hielt ich es für dumm, nur für diesen Teil Geld auszugeben, also habe ich diese Idee gestoppt.

2. Parallelverarbeitung mit Thread durchführen

Ich habe mir die Parallelverarbeitung von Ruby als nächste Gegenmaßnahme ausgedacht, aber wenn der Engpass die Verarbeitungszeit (Timeout) ist, ist dies korrekt, da es schneller ist, wenn mehrere Threads parallel eingerichtet und berechnet und zusammengeführt werden, diesmal jedoch Da der Engpass der Speicherdruck aufgrund eines Speicherfehlers ist, ändert sich die Datenmenge, die von mehreren Threads verarbeitet wird, nicht. Daher wird erwartet, dass am Ende ein Speicherfehler auftritt. Daher habe ich diese Idee gestoppt.

3. Stapelverarbeitung

Die Hauptursache für diesen Speicherfehler ist ein Speicherfehler, der auftritt, wenn eine große Datenmenge gleichzeitig erweitert wird und die Verarbeitung mit hoher Last in einer Schleife wiederholt wird.

Daher dachte ich, dass es gut wäre, wenn eine große Datenmenge implementiert werden könnte, während Speicher gespart wird, indem sie durch Stapelverarbeitung in Einheiten wie 1.000 aufgeteilt wird, ohne den Speicher sofort zu erweitern.

Rails bietet eine Methode namens "find_in_batches", mit der standardmäßig 1000 Elemente gleichzeitig verarbeitet werden können.

Beispiel) 10,1 für 000,Teilen Sie in 000 Prozesse und in 10 Batch-Prozesse.
find_in_Ein Bild, das weniger Speicher benötigt, indem die Verarbeitung mit Stapeln eingeschränkt wird.

Fazit

** Stapelverarbeitung mit find_in_batches **

Implementierung

Sobald Sie wissen, wie Sie damit umgehen sollen, müssen Sie es nur noch implementieren.

Lassen Sie es uns implementieren. (Da es nicht möglich ist, den Buchungskreis tatsächlich anzuzeigen, wird nur das Bild angezeigt.)

■ Implementierungsimage

User.find_in_batches(batch_size: 1000) do |users|
  #Etwas zu verarbeiten
end

Selbst wenn 10.000 Benutzerdaten erfasst werden, werden 1000 mithilfe von find_in_batches verarbeitet.

Mit anderen Worten, es ist ein Bild, das in 10.000 / 1000 = 10 Prozesse unterteilt ist.

Ergebnis

Der Speicherverbrauch wurde auf 1/100 reduziert.

Ideen zum Besseren

** Der größte Nachteil dieser Implementierung ist jedoch, dass sie zu viel Verarbeitungszeit benötigt. ** **.

Wenn Sie Heroku usw. verwenden, führt diese Implementierung zu einem ** RequestTimeOut-Fehler * 1 **.

* 1 In Heroku führt eine Verarbeitung, die 30 Sekunden oder länger dauert, zu einem Request Time Out-Fehler.

Daher denke ich, dass es besser ist, diese Implementierung mit hoher Lastverarbeitung auf die Hintergrundverarbeitung zu verschieben.

Wenn Sie Rails verwenden, können Sie dies mit Sidekiq tun.

Ich denke, Sie sollten mit dem folgenden Verfahren arbeiten.

STEP1. find_in_Verwenden Sie Stapel, um den Speicherverbrauch zu reduzieren

STEP2.Wenn STEP1 abgeschlossen ist, dauert es einige Zeit, sollte jedoch ohne Speicherfehler funktionieren.
Die Verarbeitung dauert jedoch einige Zeit. Verschieben Sie diesen Prozess daher in den Hintergrund.

Zusammenfassung

Zuerst dachte ich, es sei eine nervige Aufgabe.

Ich habe viel gelernt und bin froh, dass ich es jetzt implementiert habe.

Referenz

https://techblog.lclco.com/entry/2019/07/31/180000 https://qiita.com/kinushu/items/a2ec4078410284b9856d

Recommended Posts

Eine Geschichte über die Reduzierung des Speicherverbrauchs auf 1/100 mit find_in_batches
Die Geschichte des Versuchs, JAVA File zu bedienen
[PHP] Geschichte der Ausgabe von PDF mit TCPDF + FPDI
Eine Geschichte über das Bemühen, JAR-Dateien zu dekompilieren
Eine Geschichte über die Entwicklung von ROS namens Rosjava mit Java
Eine Geschichte über die Erstellung von PKIX-Pfaden schlug fehl, als versucht wurde, mit Jenkins eine Tomcat-Bereitstellung durchzuführen
Eine Geschichte, die bei NotSerializableException steckt
Eine Geschichte, die von String () von Interface abhängig ist und von JdkDynamicAopProxy vertreten wird
Eine verwirrte Geschichte über einen ternären Operator mit mehreren bedingten Ausdrücken
Eine Geschichte über Missverständnisse im Umgang mit Java-Scannern (Memo)
Eine Geschichte, die ich mit Java nur schwer herausfordern konnte
[Hinweis] Eine Geschichte über das Ändern von Java-Build-Tools mit VS-Code
Eine Geschichte über die Verbindung zu einem CentOS 8-Server mit einem alten Ansible
Eine Geschichte über das Erreichen der League Of Legends-API mit JAVA
Eine Geschichte über die Schwierigkeit, ein Testframework an Java 6 auszurichten
Die Geschichte des Jobwechsels von einem christlichen Pastor (Lehrling) zu einem Webingenieur
Eine Geschichte über das Konvertieren von Zeichencodes von UTF-8 in Shift-jis in Ruby
Eine Geschichte über das Senden einer Pull-Anfrage an MinGW, um die libgr-Version zu aktualisieren
Eine Geschichte, die süchtig nach Platzhaltern für JDBC-Vorlagen ist
Eine kleine süchtig machende Geschichte mit def initialize
Die Geschichte, dass der Umgang mit alten Daten ärgerlich ist
Beachten Sie, dass Junit 4 zu Android Studio hinzugefügt wurde
Eine Geschichte, die süchtig nach EntityNotFoundException von getOne of JpaRepository ist
Eine Geschichte über die Java 11-Unterstützung für Webdienste
[Rails] Rails neu, um eine Datenbank mit PostgreSQL zu erstellen
Eine Geschichte, die Zeit brauchte, um eine Verbindung herzustellen
Konvertieren Sie eine Zeichenfolge mit swift in ein zeichenweises Array
Eine sehr nützliche Geschichte über Rubys Struct-Klasse
Eine Geschichte über das Erstellen eines Builders, der den Builder erbt
Übergang zu einem View Controller mit Swift WebKit
Eine Geschichte voller Javas Standardeingabescanner
Rippen Sie eine CD mit Ubuntu 18.04 LTS auf MP3
Die Geschichte eines neuen Ingenieurs, der einen leidenschaftlichen Programmierer liest
Ich habe versucht, den Block mit Java zu brechen (1)
[Jackson] Eine Geschichte über das Konvertieren des Rückgabewerts des Big Decimal-Typs mit einem benutzerdefinierten Serializer.
Eine Geschichte über das Erstellen eines Dienstes, der mithilfe einer API für maschinelles Lernen Verbesserungen an einer Website vorschlägt
Die Geschichte des Besuchs der Docker + k8s-Lernsitzung [JAZUG Women's Club x Java Women's Club]
Die Geschichte, Sprint-Boot mit Kubernetes (GKE) auszuführen und keine Verbindung zu CloudSQL herzustellen
Bei der Installation von npm auf Laravel Homestead ist beim Abgleichen des Prüfsummenwerts ein Fehler aufgetreten.