Als ich vor kurzem für einen Kunden seinen Produktstamm von über 120.000 Tausend Produkten in einen neuen Magento Shop importieren sollte, bin ich wieder mal an die Grenzen von Magento gestoßen.
Jeder, der bereits einmal die hauseigene Import-Funktion von Magento genutzt hat, weiß, dass ein Import – je nach verwendeter Hardware – auch mal zwei Sekunden pro Produkt benötigt. Außerdem war es die Anforderung des Kunden, seine auf mehrere CSV-Dateien verteilten Produkte als konfigurierbare Produkte zu importieren, da je nach Variante unterschiedliche Preise möglich sein mussten.
Wie sich im Nachhinein herausstellte, musste der Produktstamm mehrmals importiert werden, da immer wieder Änderungen an den Produktdaten übernommen werden mussten. Daher war es die Richtige Entscheidung, einen eigenen Import zu schreiben. Dieser war zugegebenermaßen auf diesen Einzelfall zugeschnitten, an Performance dann aber auch kaum zu überbieten.
Vorweg: Am Ende brauchte der selbst entwickelte Importer nicht circa zwei Sekunden pro Produkt, sondern schaffte pro Sekunde circa 20 (konfigurierbare) Produkte!
Die Umsetzung erfolgte am Magento-Core vorbei und fügt die Produkte “von Hand” in die Datenbank ein. Um dabei alle Attribute, Verknüpfungen und Abhängigkeiten zu berücksichtigen, habe ich zuerst die Attribut-Tabellen inklusive der möglichen Attributwerte geladen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // $_p ist hier ein beliebiges bestehendes Produkt im Shop // Dieser weg vereinfacht es an die Produktattribute heran zu kommen // ATTRIBUTDATENBANK AUFBAUEN // ATTRIBUTDATENBANK AUFBAUEN $_pa = $_p->getTypeInstance($_p)->getEditableAttributes($_p); $_a = array(); $_av = array(); $_tables = array(); foreach($_pa AS $_ak=>$_ao) { $_a[$_ak] = array("id"=>$_ao->getId(),"code"=>$_ak,"table"=>$_ao->getBackendTable(),"type"=>$_ao->getFrontendInput(),"source"=>$_ao->getSourceModel(),"label"=>$_ao->getFrontend()->getLabel()); $_av[$_ao->getId()] = array(); if(!in_array($_ao->getBackendTable(), $_tables)) { $_tables[] = $_ao->getBackendTable(); } } // ATTRIBUTWERTDATENBANK AUFBAUEN $results = $read->fetchAll("SELECT eao.option_id AS oid, eao.attribute_id AS aid, eaov.store_id AS sid, eaov.value AS v FROM eav_attribute_option AS eao, eav_attribute_option_value AS eaov WHERE eao.option_id = eaov.option_id"); foreach($results AS $r) { if(!isset($_av[$r["aid"]])) { $_av[$r["aid"]] = array(); } $_av[$r["aid"]][$r["oid"]] = array("s"=>$r["sid"],"v"=>$r["v"]); } |
Danach wurden die Produktoptionsdatenbank und der Kategoriebaum geladen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | // PRODUKTOPTIONSDATENBANK AUFBAUEN $results = $read->fetchAll("SELECT CONCAT(entity_id,'##',attribute_id) AS k, value FROM catalog_product_entity_int"); $_po = array(); foreach($results AS $r) { $_po[$r["k"]] = $r["value"]; } // KATEGORIEDATENBANK AUFBAUEN $rootId = $store->getRootCategoryId(); $_cache = array(); $_categoryCache = array(); if (!$rootId) { return array(); } $rootPath = '1/'.$rootId; if (empty($_categoryCache[$store->getId()])) { $collection = Mage::getModel('catalog/category')->getCollection() ->setStore($store) ->addAttributeToSelect('name'); $collection->getSelect()->where("path like '".$rootPath."/%'"); foreach ($collection as $cat) { $pathArr = explode('/', $cat->getPath()); $namePath = ''; for ($i=2, $l=sizeof($pathArr); $i<$l; $i++) { $name = $collection->getItemById($pathArr[$i])->getName(); $namePath .= (empty($namePath) ? '' : '/').trim($name); } $cat->setNamePath($namePath); } $cache = array(); foreach ($collection as $cat) { $cache[strtolower($cat->getNamePath())] = $cat; $cat->unsNamePath(); } $_categoryCache[$store->getId()] = $cache; } $cache =& $_categoryCache[$store->getId()]; |
Es ist nötig diese Daten alle vorher zu laden um zum Beispiel Produkte in bestehende Kategorien einzufügen und um keine Duplikate in den Attributtabellen zu erhalten.
Nun kann mit dem eigentlichen Import begonnen werden.
Zuerst wird überprüft, ob es die Zielkategorie für das Produkt im Shop bereits gibt. Da der Kategoriebaum bereits so gut wie vollständig war, habe ich eventuell fehlende Kategorien per Magento-Core eingefügt. Hier spielte die Geschwindigkeit also keine große Rolle.
Ab jetzt können die Produktdaten selber per SQL direkt in die Datenbank geschrieben werden. Dabei waren Einträge in folgende Tabellen notwendig:
- catalog_product_entity (inkl der zugehörigen Datentyp-Tabellen)
- catalog_product_enabled_index
- catalog_category_product
- catalog_category_product_index
- catalog_product_super_attribute
- catalog_product_super_attribute_label
- catalog_product_super_attribute_pricing
- catalog_product_super_link
- cataloginventory_stock_item
- cataloginventory_stock_status
- catalog_product_website
- eav_attribute_option
- eav_attribute_option_value
Bei der Menge an zusammenhängenden Inserts pro Produkt ist es wichtig das in einer Datenbanktransaktion zu bündeln, da bei Problemen ansonsten eine Menge Datenmüll in den Tabellen zurück bleiben kann.
Weitere Performancesteigerungen lassen sich beim Import durch “Multiple Inserts” und den Betrieb des Import-Scripts direkt auf dem Server, der auch die Datenbank hostet, erreichen.
Auch wenn ich den kompletten Sourcecode hier nicht veröffentlichen kann, so hat man anhand des Artikels trotzdem einen guten Start wenn man vor ähnlichen Problemen steht.


