Import-Skript mit hohem Speicherverbrauch

Ich versuche einen Import-Test von unserem bestehenden Board durchzuführen. Wir haben etwa 25 Millionen Beiträge zu importieren (normale Beiträge + private Nachrichten), und um dies zu beschleunigen, habe ich mehrere Kopien des Import-Skripts erstellt, die parallel ausgeführt werden, um die Last der Themen aufzuteilen. Dies funktionierte einige Tage lang einwandfrei, und im Laufe der Zeit stellte ich fest, dass der Speicherverbrauch für jeden Prozess langsam auf etwa 2 GB anstieg. Schließlich lief der Server dann mit dem 16-Millionen-Beiträge-Marker aus dem Speicher und tötete die MySQL-Quelldatenbank.

Ich habe den Systemspeicher von 24 GB auf 32 GB erhöht, aber jetzt verbraucht beim Versuch, selbst nur einen Import-Prozess neu zu starten und dort fortzufahren, wo er aufgehört hat, dieser Prozess gleich zu Beginn etwa 10 GB Speicher, bevor er überhaupt mit dem Importieren von Beiträgen beginnt. Während ich zuvor 8 parallele Import-Prozesse ausführen konnte, passen jetzt nur noch 2 in den größeren Speicherpool. Warum gibt es diese enorme Diskrepanz zwischen dem Speicherverbrauch bei einer sauberen Installation und dem Speicherverbrauch beim Neustart eines Imports nach einem Fehler? Gibt es eine Möglichkeit, diesen Speicherverbrauch zu reduzieren, damit ich den Import-Prozess wieder beschleunigen kann? Ein Server mit 128 GB – 256 GB Speicher wäre unverhältnismäßig teuer (und nach dem Import nicht mehr erforderlich), und der Betrieb mit nur 2 Import-Prozessen würde bedeuten, dass der Import Wochen dauern wird.

Das klingt nach einer Regex, die in einer Schleife steckt, oder so ähnlich. Gib Debug-Meldungen aus und überspringe die problematische Zeile beim Import.

Der Speicherverbrauch scheint ausschließlich während der Abschnitte „Lade bestehende Beiträge…

Hast du dir die Skripte für den Massimport angesehen? Sie könnten einen geringeren Speicherbedarf haben.

Aber es stimmt, dass die Import-Skripte eine Zuordnung von alten Benutzer-, Themen- und Beitrags-IDs zu den neuen IDs im Speicher halten. Das bedeutet einiges an RAM, besonders wenn du mehrere Kopien erstellen möchtest.

Du verstehst doch, dass du nach dem ersten Import den Vorgang erneut ausführst, um nur die neuen Daten zu importieren, und dass dies dann viel schneller geht, oder? Also, nachdem du einen Monat auf den ersten Import gewartet hast, wird der abschließende Import nicht so lange dauern.

Mir sind keine Skripte für den Massenimport bekannt. Befinden sich diese irgendwo im Verzeichnis import_scripts?

Ja, genau dort habe ich Schwierigkeiten. Während des ersten Imports, bei dem acht Import-Prozesse liefen, war alles vergleichsweise gut, bis dem System der Speicher ausging. Wenn ich jetzt versuche, den Import-Prozess neu zu starten und dort fortzufahren, wo er abgebrochen wurde, verbraucht jeder Prozess etwa das Fünffache an Speicher im Vergleich zu dem Moment, als er beim ersten Mal abgestürzt ist.

Wir müssen einen vollständigen Import durchführen, um einen ordentlichen Test zu haben und realistische Erwartungen für den Zeitpunkt der tatsächlichen Migration zu setzen. Derzeit habe ich immer noch kein klares Verständnis dafür, was ich in Bezug auf Aspekte wie die Leistung erwarten kann. Zudem habe ich festgestellt, dass die Datenbankgröße bereits bei 16 Millionen Beiträgen über 50 % größer ist als unsere aktuelle Datenbank – das ist schon eine Überraschung. Die lange Importdauer macht dies nicht unmöglich, aber es wäre zweifellos viel bequemer, wenn die Erwartung in Tagen statt in Wochen formuliert wäre.

Siehe discourse/script/bulk_import at main · discourse/discourse · GitHub.

Für Themen und Beiträge ist es ohnehin nicht wirklich machbar, parallele Imports durchzuführen, da man einen Beitrag in einem Thema nicht importieren kann, wenn das Thema und alle vorherigen Beiträge noch nicht importiert sind. Man könnte zwar parallele Prozesse für Benutzer und Themen haben, aber nicht für Beiträge, es sei denn, man schreibt das Skript so um, dass es alle Beiträge eines Themas lädt, was parallele Imports ermöglichen würde; das ist certainly umsetzbar, funktioniert aber nicht so, wie die Skripte, die ich verwendet habe. Aber Sie werden immer noch das Problem haben, dass sie eine alte-zu-neue-ID-Map im RAM behalten.

25 Millionen Beiträge zu importieren ist nicht gerade bequem. :slight_smile:

Genau das mache ich. Ich habe die Themen so aufgeteilt, dass jedes Thema nur von einem Prozess bearbeitet wird. Die Aufteilung ist nicht perfekt, aber sie ist um ein Vielfaches schneller als ein einzelner linearer Prozess.

Das ist der verwirrendste Teil. Ich gehe davon aus, dass dies schrittweise während des Imports geschieht, und das ist auch der Grund, warum ich eine zunehmende Speichernutzung für jeden Prozess über einen Zeitraum von drei Tagen beobachtet habe. Sie lagen jeweils bei etwa 2 bis 2,5 GB Speicher, bevor die Datenbankverbindung verloren ging.

Behält jeder Prozess nur das Mapping für die Beiträge, die er importiert hat? Falls ja, könnte das erklären, warum der Speicherverbrauch nach einem Neustart des Imports explosionsartig angestiegen ist.

Ich denke schon. Und die anderen werden nicht korrekt funktionieren, da ihnen die Tage fehlen, die von anderen Prozessen importiert wurden. Ich glaube nicht, dass dein Ansatz funktionieren wird.

Du musst entweder die Bulk-Import-Skripte untersuchen oder base.rb umschreiben, um auf andere Weise die Verknüpfungen zu den Import-IDs auf dem Laufenden zu halten.

Die Wahrscheinlichkeit ist hoch, dass du mehr Wochen mit dem Debuggen deines Codes verbringst, als wenn du einfach wartest. Die CPU-Geschwindigkeit eines einzelnen Prozesses ist der beste Weg, um die Dinge zu beschleunigen.

Bisher habe ich bei diesem Ansatz keine Probleme festgestellt, obwohl ich mir vorstellen kann, dass es bei einigen der Methoden, die nach dem Post-Import ausgeführt werden, zu Schwierigkeiten kommen könnte. Ich werde wahrscheinlich sicherstellen wollen, dass alles nach diesem Punkt stoppt, und dann eine Ausführung mit einem einzelnen Thread durchführen, um sicherzustellen, dass der Rest sauber abgewickelt wird.

Das gesagt, ist dein Vorschlag, die Import-ID-Links anders zu behandeln, in jedem Fall wahrscheinlich gut. Es ist nicht sehr effizient, für die Dauer des Skripts eine willkürlich große Menge an Daten in einigen Variablen zu speichern.