Import / Speicher Fresser

Guten Tag,

ich kämpfe mit dem Memory Limit und den Product Upserts.
Pro Upsert auf ein existierendes Produkt werden 2 MB Speicher belegt und nicht wieder freigegeben.
Fehlt evtl. ein clear? Wie in SW5 mit ORM?

Werde nun mal als Alternative eine Bulk Lösung bauen.

Viele Grüße
Ottscho
 

Edit: Bulk bringt nicht wirklich viel.
Hab nun nen 100er Step. Habe z.B. 170MB RAM Belegung ohne Product Upsert.
Dann übergebe ich die 100 Artikel für das Upsert und der RAM springt auf 430MB.

Mhh, jemand eine Idee? Ergenis ist auf jeden Fall schon mal besser.

Bei 2048MB Memory Limit komme ich ohne Bulk auf ca. 550 Artikel bis der Prozess ans Limit kommt und knallt.
Mit Bulk komme ich zwischen 600-700 Artikel. Hier knallst es dann ebenfalls.

Bin für jeden Tipp dankbar. Und wir sprechen hier nur von einem Update. Sprich alle Artikel bestehen bereits!

 

Moin zusammen,

scheinbar haben sich mit dem Thema noch nicht so viele beschäftigt? Shopware Importplugin über die Repositories.
Aktuell geht es bei mir nur um 6000 Produkte. Wenn man anhand meinem Beispiel hochrechnnet, bräuchte ich hier fast 24GB um diese ohne Abbruch zu importieren.

Ich habe aber auch SW5 Projekte mit 60.000 und mehr Artikel.

Habe mich ein wenig durch den Code debugt und immer beim Ausführen der Klasse EntityWriterInterface -> upsert und EntityWriterGatewayInterface -> execute werden pro Produkt mehrere MB RAM belegt und nicht mehr freigegeben. Somit summiert sich das ganze recht schnell auf.

Bei den Properties hat der Bulk Import sehr viel gebracht (Zuordnung Artikel zu Property). Bei den Produkten an sich leider nichts.

Für jede Idee / Tipp bin ich dankbar.

Beste Grüße
ottscho

Würde ich nicht empfehlen 60k Artikel in einem Rutsch zu aktualisieren. Ich versuche so wenig wie möglich auf die Symfony Repos zurückgreifen zu müssen. Mein Import funktioniert so - Warenwirtschaft exportiert XML File auf Server. Dort habe ich ein Python Skript, was die Produkte filtert, aufbereitet dann alle Artikel in einem Rutsch per LOAD DATA INFILE in MysQl Tabellen lädt. Danach wird mit Mysqldump alles auf den Shopserver kopiert. Der ganze Prozess dauert bei rund 20k Artikeln unter 2 Minuten. Jetzt kann ich z.b. alle Bestände oder Preise in jeweils einem einzigen Query aktualisieren. Das dauert auch nur Millisekunden. Für Artikelneuanlage oder Update von Properties läuft ein Cronjob der alle 2 Minuten maximal 500 Artikel aktualisiert über die Produkt Repo (natürlich mit flock, damit der cron wartet). Da die Daten eh schon auf dem Shopserver sind, spare ich mir auch die HTTP-Requests der API sondern kann gleich die Repos (und mehr) verwenden.

 

Hallo Alex,

danke für deine Antwort. Deine Lösung geht aber um Shopware komplett herum.
Ich würde gerne Shopware Mittel nutzen. So wie ich es in SW5 bereits auch mache. Es geht mir nicht direkt um die Geschwindigkeit.
Aber der RAM ist schon wichtig. [@Moritz Naczenski](http://forum.shopware.com/profile/14574/Moritz Naczenski “Moritz Naczenski”)‍ Was sagst du dazu?

Beste Grüße
Ottscho

 

Hey,

liest du denn nebenbei auch viel über die Repositories?

Eine grundsätzliche Idee: Wie wäre es, wenn du je 10 Produkte eine Message in die Message Queue legst? Der Message Queue Handler schreibt dann die Produkte.

Das Schreiben in die Queue läuft dann sehr schnell. Das Schreiben der Produkte kannst du dann parallelisieren in dem z. B. 5 Worker gleichzeitig laufen lässt im Hintergrund.

So in kurz funktioniert etwa das Elasticsearch indexieren. Beim Indexieren Command werden für je 50 Produkte eine Message in die Queue getan. Dann startet man / hat mehrere Worker laufen, damit man schnell die Aufgaben beendet kriegt :slight_smile:

Das behebt zwar nicht den Fall, den du im Thread hast. Aber du wärst dann viel besser aufgestellt, da du es parallelisieren kannst. 

 

Hey @Shyim‍, freut mich von dir zu lesen.
Ich lese uncached den Artikel, um herauszufinden ob dieser bereits existiert. Dies mit media und children, da ich im Update-Fall darauf zugreife.
(dieser RAM usaged hält sich aber in Grenzen)

$context = \Shopware\Core\Framework\Context::createDefaultContext();
        $criteria = (new Criteria())
            ->addFilter(new EqualsFilter('productNumber', $articleNr))
            ->addAssociation('media')
            ->addAssociation('children')
            ->setLimit(1);

$entities = $context->disableCache(function (\Shopware\Core\Framework\Context $context) use ($criteria) {
            return $this->productRepository->search($criteria, $context);
});

Im Update Prozess mache ich dann nur noch ein UPSERT auf die porperty und product Repository. Wobei das UPSERT auf die property repository als BULK gut läuft und auch viel bringt. Ich hätte lieber bei den Produkten gerne eine direkte Ausgabe der Importergebnisse.

Warum zieht das schreiben der Entities so viel Speicher?
Hängt es evtl. mit dem writeContext zusammen, da dieser nur einmal initialisiert wird und dann aufgebläht wird.

Danke dir & viele Grüße
ottscho

 

 

 

Hallo,

ich habe dasselbe Problem. Ein UPSERT auf die Produkte lässt den RAM schnell volllaufen. Ich nutze 200er Stapel. Je Upsert werden ca. 120 MB zusätzlich belegt.

Hast du eine Möglichkeit gefunden, den Speicher freizugeben oder einen anderen Workaround?

Viele Grüße

Sebastian

ist die APP_ENV prod?

1 Like

Stehe vor dem selben Problem. Konntest du es inzwischen mit Boardmitteln lösen oder hast du dir eine Lösung „drumherum“ gebaut?

Super Tipp von Shyim. Mit APP_ENV auf prod reduziert sich der Speicherverbrauch auf 250MB bei mir.

Ausserdem sehr hilfreich bei mir gewesen in 250er Batches zu arbeiten, d.h. fuer den Insert oder Delete uebers Product Repository die Inhalte von 250 Produkten sammeln und nur einmal ausfuehren. Hat die Ausfuehrungszeit auch extrem verbessert.

Gruß
Alexander

Es scheint so, als ob Produkte mit vielen Varianten das Problem verursachen:
Wir haben ein Produkt mit 250 Varianten und machen folgenden einfachen Upsert:

$productData = [
                    'id' => $product->getId(),
                    'stock' => $stock,
                    'deliveryTimeId' => $deliveryTimeId,
                    'restockTime' => $restockTime
 ];

$upsertData[] = $productData;

                // only save data every x times, to reduce overhead
 if ($i % $saveStepWidth === 0) {
                    $mem_usage = memory_get_usage(true);
                    file_put_contents(__DIR__ . "/test.txt", " ProductNumber: " . $productNumber . PHP_EOL, FILE_APPEND);
                    file_put_contents(__DIR__ . "/test.txt", round($mem_usage / 1048576, 2) . " megabytes" . PHP_EOL, FILE_APPEND);
                    $this->productRepository->upsert($upsertData, $context);
                    $upsertData = [];
                    $mem_usage = memory_get_usage(true);
                    file_put_contents(__DIR__ . "/test.txt", round($mem_usage / 1048576, 2) . " megabytes after productRepository->upsert" . PHP_EOL, FILE_APPEND);
}

Über 1,4GB Steigerung bei EINEM EINZIGEN UPSERT
ProductNumber: XMP98212294
768.5 megabytes
2254.5 megabytes after productRepository->upsert

Das ist der genutzte Konsolenbefehl:

cd /var/www/XXXX/XXXX/XXX/web/bin && APP_ENV=prod /opt/php-8.0/bin/php -d max_execution_time=0 -d memory_limit=4096M console e-com:import-products --no-debug

Wir haben den Befehl auch bereits mit 8GB getestet und leider wird der Cronjob auch hier gekilled - beim Versuch 5000 Produkte zu importieren.

Habe hierzu auch schon ein Issue erstellt:

Der Symfony-EventDispater ist das Problem, der pro Produkt ca. 2MB an Speicher konsumiert und nicht mehr freigibt. Habe dazu auch ein Forenbeitrag geschrieben.

2MB pro Produkt wären ja noch aushaltbar - es sind aber 1,4GB pro Produkt und der Speicher wird auch nicht mehr freigegeben.

Dann würden ja maximal zwei Produkte aktualisiert werden können bei 1024MB. Das kann ich nicht wirklich nachvollziehen.

Produkt hat 500 Varianten - eine Variante davon wird durch den upsert oben aktualisiert - dann entstehen diese memory leaks - Shopware rechnet/indexiert hier vermutlich noch irgendetwas bei allen Varianten.

Jede Variante ist beim Upsert wahrscheinlich ein eigenes Produkt, 500 x 2MB sind schon einmal 1GB. Kommt der Sache schon näher.

Wir haben das Problem bei Importen auch, soweit ich weiß ist Shopware hier bereits an einer Lösung dran.
Das Problem ist hierbei im Produktindexer zu finden, bei mir vor allem an der Stelle hier platform/ProductIndexer.php at trunk · shopware/platform · GitHub

Die Lösung wird hier (so vermute ich, habe keine Bestätigung) so aussehen, dass das Ganze ins asynchrone verlagert wird um kleinere Blöcke zu bilden.

Hilft zwar auch keinem so richtig, aber ich dachte ich erwähn es mal :slight_smile:

@dominikmank
OK ist ja schon einmal schön, dass Shopware da dran arbeitet allerdings bringt die asynchrone Lösung einem größeren B2B-Shop überhaupt nichts - wenn dieser ständig mit SAP kommuniziert und ein Produkt 1,5GB Speicher frisst, brauchen wir bei 5000 Produkten ja Arbeitsspeicher im TB Bereich - ich bin wirklich großer Fan von SW6 aber an den Insert/Update/Upsert/Delete Befehlen ist noch viel Arbeit reinzustecken bevor größere Kunden damit arbeiten können.
Generell führt der memory-Fresser dazu, dass es auch im Backend zu Fehlern beim Speichern/Löschen/Hinzufügen von Artikeln mit vielen Varianten kommt.
Zum Beispiel sowas:

php.CRITICAL: Fatal Error: Out of memory (allocated 824180736) (tried to allocate 663552 bytes) {"exception":"[object] (Symfony\Component\ErrorHandler\Error\OutOfMemoryError(code: 0): Error: Out of memory (allocated 824180736) (tried to allocate 663552 bytes) at /home/meinedomain.com/apitest/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:262)"} [] [2022-01-10T12:33:26.426271+00:00] request.CRITICAL: Uncaught PHP Exception Symfony\Component\ErrorHandler\Error\OutOfMemoryError: "Error: Out of memory (allocated 824180736) (tried to allocate 663552 bytes)" at /home/meinedomain.com/apitest/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php line 262 {"exception":"[object] (Symfony\Component\ErrorHandler\Error\OutOfMemoryError(code: 0): Error: Out of memory (allocated 824180736) (tried to allocate 663552 bytes) at /home/meinedomain.com/apitest/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:262)"} []
 https://meinedomain.com/api/_action/sync Payload: {key: "write", action: "upsert", entity: "product",…} action: "upsert" entity: "product" key: "write" payload: [{id: "c1000001070000000000000000086388", versionId: "0fa91ce3e96a4bc2be4bd9ce752c3425",…}] 0: {id: "c1000001070000000000000000086388", versionId: "0fa91ce3e96a4bc2be4bd9ce752c3425",…} description: "22x28cms" id: "c1000001070000000000000000086388" versionId: "0fa91ce3e96a4bc2be4bd9ce752c3425"

Wir sind jetzt bei 32 GB Arbeitsspeicher und es reicht immer noch nicht, um ein simples Update-Statement auszuführen für ein Lagerbestandsupdate.

Edit:
Noch einmal um klar zu stellen, in welchen Dimensionen wir hier sprechen - ich habe soeben versucht ein Produkt mit 625 Varianten zu aktualisieren - also lediglich das:

$productData = [
                    'id' => $product->getId(),
                    'stock' => $stock,
                    'deliveryTimeId' => $deliveryTimeId,
                    'restockTime' => $restockTime
 ];

Für eine Variante hat es 1,3GB benötigt:
ProductNumber: CGP9128234
1028.5 megabytes
2330.5 megabytes after productRepository->upsert

Es scheint so, als ob ein Update der anderen Varianten des Artikels dann keine gravierenden Folgen für den Arbeitsspeicher haben (da diese vermutlich durch ein Update der einen Variante bereits vorgemerkt für das Indexing sind)

Wenn ich aber 50 dieser Produkte mit einer Anzahl von 600+ Varianten habe, benötige ich mindestens 50*1,3GB = 65GB für das Lagerbestandsupdate von 5000 Produkten

Hinzu kommt, dass der Lagerbestand bei zwei unserer B2B-Kunden alle 30 Minuten aktualisiert werden müsste - hier könnte noch über 1 Stunde verhandelt werden - ist momentan unmöglich mit SW6

@Moritz_Naczenski
Habt ihr ein Testsystem mit einer solchen Anzahl an Produkten und vor allem Produkten mit vielen Varianten (500+)? Ansonsten kann ich euch gerne eines von uns bereitstellen.

@dominikmank
Vielen Dank für den Tipp.
Ich habe jetzt die einzelnen Methoden einmal auskommentiert und es liegt definitiv an dieser Zeile:

if ($message->allow(self::SEARCH_KEYWORD_UPDATER)) {
                //$this->searchKeywordUpdater->update(array_merge($ids, $childrenIds), $context);
            }

Wenn ich diesen auskommentiere, kommt der gesamte Import mit 300MB RAM klar!

Ich habe das wieder aktiviert und bin ich die Datei platform/SearchKeywordUpdater.php at eb827637a1bb6edd35487dfe59b2b761dcccf685 · shopware/platform · GitHub

Hier liegt das Problem am Iterator:

while ($products = $iterator->fetch()) {
            /** @var ProductEntity $product */
            foreach ($products as $product) {
                // overwrite fetched products if translations for that product exists
                // otherwise we use the already fetched product from the parent language
                $existingProducts[$product->getId()] = $product;
            }
        }

Bei diesen Zeilen wächst der RAM.
Weiter geht es mit der Datei:

$result = $this->repository->search(clone $this->criteria, $this->context);

Und hier ist dann die nächste Ebene des Problems.

Damit landen wir dann hier:

796.5 megabytes repo search before → RepositoryIterator.php
796.5 megabytes → EntityRepository.php
Dispatched EventName product.search.result.loaded → EntityRepository.php
830.5 megabytes after 3 → EntityRepository.php
830.5 megabytes repo search after → RepositoryIterator.php

→ könnte also irgendein Problem beim Event „product.search.result.loaded“ sein?

EntityRepository.php:

public function upsert(array $data, Context $context): EntityWrittenContainerEvent
    {
        $affected = $this->versionManager->upsert($this->definition, $data, WriteContext::createFromContext($context));
        $event = EntityWrittenContainerEvent::createWithWrittenEvents($affected, $context, []);
        $this->eventDispatcher->dispatch($event);

        return $event;
    }

Ohne $this->eventDispatcher->dispatch($event); keine memory Probleme.
Gleiche Probleme auch bei den Event dispatchern in den Methoden update(), create() - delete() vemutlich auch aber habe ich nicht getestet.

Guten Morgen,
wir haben aktuell genau das selbe Problem mit unseren Preisen. Ist hier schon etwas neues bekannt?
Unser Preisimport hatte mal ohne Probleme funktioniert.

Liebe Grüße