Magentoperformance

Performanceprobleme bei Magento erkennen, analysieren und beheben

27. November 2012
von Sebastian
Keine Kommentare

Zu langsames Anlegen und Ändern von Produkten aufgrund des Indexers

Dass der bei Magento mitgelieferte Produkt-Importer weder schnell noch sehr flexibel ist, ist bekannt. Auf Grund dessen musste ich für ein aktuelles Kundenprojekt wieder ein speziell zugeschnittenen Importer entwicklen, welcher einen Satz definierter Produktattribute aus dem ERP-System des Kunden regelmäßig in den Magento-Shop importiert. Die Entwicklung ging gut voran, doch die ersten Tests zeigten, dass der Import sehr langsam lief: für die große Anzahl an Produkten zu langsam.

Was das Problem war, verrät der Titel dieses Blogeintrags bereits: Die Indexer von Magento waren dahingehend eingestellt, dass sie bei jedem “Produkt-Update” die Änderungen in den Index übernehmen. Diese Einstellung ist in Ordnung, solange nur einzelne, manuelle Produktupdates vom Backend aus erfolgen. Bei Massen-Änderungen führen diese allerdings zu erheblich verminderter Geschwindigkeit und zu erhöhter Last auf dem Datenbank-Server.

Die einfachste Lösung ist, vor Beginn der Produktupdates den Indexer zu deaktivieren und erst nach allen Updates den Indexer wieder zu aktivieren. Gegebenenfalls muss der Index-Prozess dann einmal von Hand angestoßen werden.

Um einen, oder wie im folgenden Beispiel gleich alle, Indexer temporär zu deaktivieren und im Nachhinein wieder zu aktivieren kann folgender Code-Schnipsel verwendet werden:

// Liste aller Indexer erhalten
$indexerCollection = Mage::getSingleton('index/indexer')->getProcessesCollection(); 
 
// durch Liste der Indexer iterieren..
foreach ($indexerCollection as $indexer) {
  // .. und diese in den manuellen Modus versetzen
  $indexer->setMode(Mage_Index_Model_Process::MODE_MANUAL)->save();
}
 
// hier Produktupdates ausführen
 
foreach ($indexerCollection as $indexer)
{
  // und nach den Produktupdates wieder aktivieren
  $indexer->setMode(Mage_Index_Model_Process::MODE_REAL_TIME)->save();
}

Je nach Art der vorgenommenen Produktupdates kann es auch ausreichen nur ein Teil der Indexer kurzfristig zu deaktivieren.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

18. Oktober 2012
von Sebastian
Keine Kommentare

“Warum ist der jetzt so langsam?” – über die Wichtigkeit von XDebug

Wieder einmal ein Alltagsproblem: Eine Kunde möchte, neben ein paar Änderungen und einer ERP-Connector-Erweiterungen mit seinem Shop auf unser Shared-Hosting umziehen. Soweit kein Problem. Ich habe eine Kopie aus dem Liveshop auf unserem Server erstellt. Da vor dem Go-Live ohnehin ein aktueller Kunden- und Produktstamm eingespielt werden muss leerte ich die Kunden und Produktdatenbank im Shop fast vollständig. Lediglich ein paar Datensätze zum Testen habe ich übrig gelassen.

Nachdem alle vom Kunden gewünschten Änderungen umgesetzt wurden, habe ich test weise noch einmal den kompletten Produktstamm importiert… und da ist das Problem! Das Shop-Frontend ist wunderbar schnell, das Admin-Backend ist aber unbenutzbar. Entweder “unendlich” langsam, oder der Webserver-Prozess wird vor Ende durch Speicher- oder Ausführungszeit-Limits abgebrochen.

“Warum ist der jetzt so langsam? Liegt es am etwas schwächeren Server? Liegt es an den Codeänderungen? Aber wir haben doch gar nichts an den Backend-Modulen geändert?”

Wenn das Frontend auch ohne Cache schnell ist das Backend aber nicht, dann liegt es nicht an der Server-Hardware. Mit wenig manuellem Debuggen bin ich nur bis zum einem “CollectionLoad” gekommen, ab da wurde das Debugging aber fast unmöglich. Das Problem wurde dann aber schnell klar, nachdem die Technikabteilung das Modul XDebug auf dem Server aktiviert hatte und ich die langsamen Shop-Backend-Bereiche noch einmal aufgerufen habe.


In Zeile 36 sieht man, das die ProductCollection eigentlich fertig geladen wurde. Ab Zeile 37 wird der Standard-Event “Collection_Load_After” gefeuert UND von genau der einen Drittanbieter-Erweiterung gefangen welche ich im Zuge der Shopanpassungen installieren sollte. Bis zu dem Zeitpunkt sind, wie man in der zweiten Spalte erkennen kann, auch erst 0.7 Sekunden vergangen. Eine Zeile weiter – Nummer 41 – sind aber schon ca. 120 Sekunden vergangen und die MAX_EXECUTION_TIME von PHP ist erreicht.

Nachdem ich das Problem innerhalb der Erweiterung behoben hatte, war der Shop auch im Backend wieder so schnell wie zuvor. Bevor man ein Problem”mit Hardware tot schlägt” oder lange manuell versucht zu Debuggen sollte man besser einmal einen Debugversuch mit XDebug unternehmen. Entweder mit dem oben gezeigten Aufruf-Stack oder mit den von XDebug erzeugten Cachegrind-Dateien. Wie das genau geht habe ich bereits im Artikel “Magento Code-Profiling mit Xdebug” beschrieben.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

5. September 2012
von Sebastian
2 Kommentare

Produkte schneller speichern

Steht man vor der Aufgabe ein Produktobjekt in Magento zu speichern wird das oft einfach mit einem “save()” erledigt:

$product = Mage::getModel("catalog/product")->load(1);
$product->setName("Produkt mit neuem Namen");
$product->save();

Diese Variante des Speicherns ist aber nicht besonders performant, da das gesammte Produktobjekt in der Datenbank aktualisiert wird. Je nach Anzahl der EAV-Attribute kann das eine sehr aufwändige Operation werden.

Für den Fall das wirklich nur einzelne Attributwerte eines Produkts aktualisiert werden sollen, sollte man stattdessen den Weg über das Resource-Model gehen. Übertragen auf das Beispiel von oben würde der Code dann so aus:

$product = Mage::getModel("catalog/product")->load(1);
$product->setName("Produkt mit neuem Namen");
$product->getResource()->saveAttribute($product,"name");

Die genutzte Funktion “saveAttribute()” findet man im Magento-Core unter /app/code/core/Mage/Eav/Model/entity/Abstract.php. “Schlank” sieht diese Methode zwar auch nicht aus, sie beschränkt sich aber wirklich nur auf das Speichern von einem Attribut.

Im Gegensatz dazu lädt die Funktion “save()” aus dem ersten Codebeispiel ein eventuell nur teilweise geladenes Produkt zuerst komplett und stellt dann für alle Attribute die Aktualisierungs-Statements zusammen. Ohne Frage die letztendlich aufwändigere Funktion.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

20. Juni 2012
von Sebastian
Keine Kommentare

Extra Boost durch Full Page Caching: Varnish Cache und Magento

Da wir bei basecom nicht nur für Magento Shops entwickeln, sondern auch für viele Kunden deren Magento Hosting betreuen, beschäftigt sich auch unsere Technikabteilung mit der Performanceoptimierung.
Vor ein paar Tagen haben wir daher eine weitere Möglichkeit zur Performance Steigerung getestet. Diese Möglichkeit kann sogar gleichzeitig mit memcache genutzt werden.

Unter dem Varnish Cache kann man sich eine Art Proxy-Server zwischen Shop und Webseitenbesucher vorstellen, der alle Anfragen an den Shop entgegennimmt und entscheidet, ob eine Version aus dem Cache ausgeliefert werden kann oder ob der Aufruf an den Shop durchgereicht werden muss. Unterstützend dazu wird in Magento die Erweiterung “PageCache powered by Varnish” installiert. Diese Erweiterung hängt sich an einen Routerevent (controller_front_init_routers) von Magento und passt dort die HTTP Header für ein besseres Zusammenspiel mit dem Varnish Server an. Einstellungsmöglichkeiten dafür gibt es im Magento-Verwaltungsbereich unter dem Punkt “Advanced“->”System“:

Varnish Konfigurationsmöglichkeiten im Magento Backend

Varnish Konfigurationsmöglichkeiten im Magento Backend

Bei Aufsetzen des Varnish Servers (Installation Ubuntu) bringt dieser eine allgemeine Konfigurationsdatei (/etc/default/varnish ) mit, welche unter anderem den verwendeten Arbeitsspeicher, das Steuerinterface oder die Threading-Einstellungen regelt. Hier der Abschnitt mit der allgemeinen Serverkonfiguration, welcher der leicht abgewandelten Beispielkonfiguration aus der der Konfigurationsdatei entspricht:

DAEMON_OPTS="-a :80 \
             -T localhost:6082 \
             -f /etc/varnish/default.vcl \
             -u varnish -g varnish \
             -S /etc/varnish/secret \
	     -p thread_pool_add_delay=2 \
             -p thread_pools=2 \
             -p thread_pool_min=400  \
             -p thread_pool_max=4000 \
             -p session_linger=50 \
             -p sess_workspace=262144 \
             -s malloc,512m"

Eine weitere Konfigurationsdatei die Varnish mitliefert befindet sich im Verzeichnis /etc/varnish (siehe Zeile 3 der Serverkonfiguration). Die default.vcl ist von Haus aus relativ leer und nicht auf Magento abgestimmt. Die PageCache Erweiterung bringt aber bereits eine optimierte Version der Datei mit welche händisch kopiert werden muss.

Diese wird von der oben erwähnten Magento-Erweiterung erstellt und lässt sich als eine Art Regelsatz verstehen, welcher dem Varnish Server genaue Instruktionen gibt, wann welche Seiten und Dateien im Cache abgelegt werden dürfen und wann nicht.

Interessant sind jetzt natürlich noch die Performanceunterschiede in der Praxis. Die “Werbetrommel” spricht von einem bis zu 100x schnellerem Shop. In der Praxis hatte ich beispielsweise bei einer Katalogseite eine deutlich kürzere Antwortzeit im Browser feststellen können:

Varnish deaktiviert, Magento Cache deaktiviert: 950ms

Varnish deaktiviert, Magento Cache aktiviert: 480ms

Varnish aktiviert, Magento Cache deaktiviert: 25ms

In unserem Fall war der Varnish Server nicht auf dem selben Server installiert wie der Shop selbst, da es sich um ein “Shared Hosting System” handelt. Die localhost Installation ist aber möglich und bietet sich besonders bei VM-Systemen an, bei denen jeder Shop eine eigene virtuelle Maschine hat.

In der localhost-Variante muss dann noch der eigentliche Webserver des Shops (Apache, etc.) auf einen anderen Port umgestellt werden, damit der Varnish Server auf dem Port 80 alle eingehenden Anfragen abfangen kann. Installiert man den Caching Server auf einer anderen Maschine, muss der DNS Eintrag des Shops dahingehend geändert werden, als dass dieser nicht mehr auf den Shop sondern auf den Varnish Server zeigt.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

22. März 2012
von Sebastian
Keine Kommentare

Schneller Import von konfigurierbaren Produkten

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.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

4. Januar 2012
von Sebastian
Keine Kommentare

“sales_flat_quote” Tabelle zu groß

Mit diesem Blogeintrag möchte ich hier eine neue Kategorie im Magento Performance-Blog eröffnen: die Alltagsprobleme. Kleine Codeschnipsel oder Tipps die zwischendurch hilfreich sein können. Der Großteil der Einträge in dieser Kategorie werden wahrscheinlich direkten Bezug zu Problemen stehen vor denen ich in dem Moment stehe. Ich hoffe das ich damit ein paar Gleichgesinnten helfen kann.

Los geht’s mit einem teilweise etwas langsamen Shop und meinem Kollegen aus der Technik-Abteilung der mich auf eine relativ große Datenbank in einem Magento-Shop aufmerksam machte. Das auf allen unseren Servern übliche nächtliche Backup der MySQL Datenbank betrug stattliche 1,5GB.

Da der Shop auf einer etwas älteren Magento-Version basiert, sind dort leider noch nicht so viele Aufräum-Cronjobs integriert wie es in den aktuellen Versionen der Fall ist.

Die üblichen Verdächtigen – nämlich die “log_XXX” Tabellen – waren in diesem Fall aber nicht die Schwergewichte. Scheinbar legten die unterschiedlichsten Suchmaschinen-Crawling-Bots gerne, und häufig irgendwelche Produkte in den Warenkorb des Shops, was die Tabellen “sales_flat_quote” und “sales_flat_quote_address” enorm anwachsen ließen. Jeweils ca 1,6 Millionen Einträge beinhalteten diese.

Da Magento diese scheinbar nicht selbst aufräumt habe ich dort mit einem nächtlichen Cronjob abhilfe geschaffen:


// Magento Core includieren und initialisieren
require_once './app/Mage.php';
Mage::app('admin');

// Datenbank-Objekt erstellen
$write = Mage::getSingleton('core/resource')->getConnection('core_write');

// Alle Einträge aus der 'sales_flat_quote' löschen die seit mindestens 4 Tagen nicht geändert wurden
$write->query("DELETE FROM `sales_flat_quote` WHERE updated_at < DATE_SUB(Now(),INTERVAL 4 DAY)");

Die “log_url_XXX”- und “log_visitor_XXX”-Tabellen können natürlich im gleichen Zug mit geleert werden. Das Ergebnis des kleinen Cronjobs ist übrigens eine auf ein sechstel reduzierte Größe des Datenbank-Backups.

Herausgefunden das es Suchmaschinen waren, habe ich übrigens anhand der Tabellenspalte “sales_flat_quote.remote_ip”. Eine whois-Abfrage der dort auftretenden IP-Adressen hat unter anderem Google und Baidu als “Täter” identifiziert.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

18. November 2011
von Sebastian
Keine Kommentare

Den Magento-Cache für eigene Module nutzen

Von Haus aus ist der Cache in der Standard-Installation bereits gut in das System integriert. Aber bei vielen Shops wird es nicht bei der Standardinstallation bleiben. Es kommen eigene oder Drittanbieter-Module hinzu. Da man bei Letzteren oft nicht weiß wie diese implementiert wurden, können solche Module  zum Beispiel durch aufwändige Datenbank-Operationen die Shop-Performance stark beeinträchtigen.

Muss nun ein bestehendes Modul beschleunigt werden oder steht eine Eigenentwicklung an? Hier ein paar Tipps:

Block-Caching für eigene Module einschalten / steuern:

Um dem eigenen Modul das bereits in Magento integrierte Block-Caching beizubringen ist nicht viel Arbeit notwendig:


public function __construct()
{
  parent::__construct();
  ...
  // In der Funktion __construct() des Moduls folgende Zeilen ergänzen
  $this->addData(array(
    'cache_lifetime' => 1800,
    'cache_tags'     => array("MYMODULE_BLOCK"),
    'cache_key'      => "MYMODULE_BLOCK_ONE"
  ));
  // Ende
}

“cache_lifetime” Gibt den Zeitraum (in Sekunden) an, in welchem das einmal erzeugte Template im Cache bleibt und nicht neu erzeugt wird.

“cache_tags” Anhand von Cache-Tags entscheidet Magento welche Blöcke vorzeitig aus dem Cache gelöscht werden müssen wenn sich zum Beispiel Produktdaten ändern. Mögliche, bereits in Magento verwendete Cache-Tags können im jeweiligen Modul nachgelesen werden. Um beim Beispiel der Produktdaten zu bleiben sollte im eigenen Modul dann folgender Cache-Tag verwendet werden: Mage_Catalog_Model_Product::CACHE_TAG

“cache_key” Anhand dieser ID sucht Magento den Block im Cache. Wenn es einen Cacheeintrag mit der ID findet wird dieser ausgeliefert, und der Block wird nicht noch einmal neu erzeugt.

ACHTUNG! Unbedingt darauf achten, dass hier eine eindeutige ID gewählt wird. Ansonsten werden eventuell falsche Blöcke an den Browser geschickt. Außerdem bedeutet ein fester Cache-Key, dass immer derselbe, bereits gecachte Block ausgeliefert wird. Egal ob beispielsweise im Shop-Frontend gerade ein Gast oder ein Bestandkunde die Seite anzeigt. Hat man also einen Block/Template das von anderen Faktoren, wie Kunden oder Produkten abhängt und anhand solcher Daten generiert wird ist hier höchste Vorsicht geboten. Je nach Funktionalität werden bestimmte Informationen Webseitenbesuchern angezeigt, die diese eventuell nicht sehen dürfen.

Magento-Collections anstelle von direktem SQL nutzen:

Ein allgemeiner Tipp:  Man sollte, besonders bei oft wiederkehrenden Abfragen immer die Magento-Collections und Ressource-Modelle für Datenbankabfragen nutzen. Magento legt bereits angeforderte Collection-Daten nämlich automatisch im Cache ab, was spätere Abfragen enorm beschleunigen kann.

Wenn kein Weg an direktem SQL vorbei führt, kann zumindest versuchst werden einzelne Berechnungsergebnisse oder ganze Datenbank-Antworten von Hand im Cache abzulegen.

Den Cache für eigene Daten nutzen:

Lassen sich eigene SQL-Abfragen oder sonstige zeitaufwendige Berechnungen nicht vermeiden, so kann man wiederverwendbare Ergebnisse auch selber im Cache ablegen:

...
$_cachekey = "MYMODULE_BERECHNETE_DATEN";
$_cachetags = array("MYMODULE_BERECHNETE_DATEN_CACHETAG");

// Magento Cache Object holen
$_cache = Mage::app()->getCache();

//liegen die Daten bereits im Cache?
if(!$_cache || !$_cache->test($_cachekey))
{
  // Daten nicht im Cache gefunden
  $_data = ... // Aufwendige Berechnungen

  if($_cache)
  {
    // Berechnungen im Cache sichern
    $_cache->save($data,$_cachekey,$_cachetags,null, 10)
  }
}
else
{
  // Daten im Cache gefunden
  $_data = $_cache->load($_cachekey);
}
...

Die Funktion “$_cache->save()” erwartet beim Aufruf folgende Parameter, von denen jedoch lediglich der erste verpflichtend ist:

  • Die zu cachenden Daten [mixed] (je nach Konfiguration kann es hier auch nötig sein die Daten bereits in serialisierter Form als String zu übergeben)
  • Ein Cachekey [string]
  • Die Cachetags [array([string])]
  • Die Cachezeit in Sekunden [integer]
  • Priorität der Daten [int, 0-10]
Weitere Erläuterungen finden sich in den Kommentaren zur Implementierung der Funktion in der Klasse “ZEND_CACHE_CORE” im Verzeichnis /lib/Zend/Cache/core.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

3. November 2011
von Sebastian
2 Kommentare

Den Magento Cache verstehen und einrichten

Im Idealfall wird ein Produkt in einem Onlineshop öfter vom Kunden angeschaut als vom Shopbetreiber bearbeitet. Oder anders gesagt: Es gibt mehr lesende als schreibende Zugriffe. Lesende Zugriffe sind natürlich schneller erledigt als schreibende, bei einem viel besuchten Shop ist das Erzeugen einer Produktseite mit allen Informationen dank der komplexen Datenbank-Struktur (EAV-Modell) aber dennoch eine aufwendige Operation.

In Magento sind aus diesem Grund unterschiedliche Beschleunigungs-Mechanismen eingebaut. Zum einen werden seitens der Datenbank zusätzlich zu den EAV-Tabellen noch Flat-Tabellen genutzt, auf die ich noch in einem zukünftigen Blogeintrag genauer eingehen werde. Zum anderen werden bereits erzeugte Ergebnisse wie zum Beispiel gerenderte HTML-Blöcke oder Sammlungsdaten (Collections) im Cache gesichert, damit diese bei späteren Abfragen schneller abgerufen werden können.

Nach dem Aufsetzen und Konfigurieren des Shops werden daher auch oft alle zu Verfügung stehenden Cache-Optionen in der Magento Administration aktiviert. Wird nun ein Datensatz im Cache abgelegt, erstellt Magento eine Datei mit den zu speichernden Daten im Ordner /var/cache . Da ein Abruf des Datensatzes dann aber immer noch ein Zugriff auf die Festplatte ist, hört die Performanceoptimierung hier natürlich nicht auf.

Neben diesem Dateisystemcache bietet Magento von Haus aus auch andere Caching-Backends. Genauer gesagt werden diese bereits vom in Magento verwendeten Zend-Framework bereit gestellt. Der Dateisystemcache ist hier nur die langsamste, dafür auf fast jeder Serverumgebung verfügbare Variante für das Caching.

Bei einigen stark frequentierten Shops für Kunden konnte ich auf das Memcache- oder das APC-Caching zurückgreifen. Hierfür mussten wir auf dem Webserver (alternativ auch extern) zuerst die Server-Module der jeweiligen Technologie installieren. Bei Memcache ist das ein Deamon-Prozess auf dem Server plus PHP-Modul, bei APC ist es nur ein zusätzliches PHP-Modul.

Ohne genauer auf die jeweilige Technologie einzugehen reicht hier denke ich die Aussage, das Memcache und APC die Datensätze im Arbeitsspeicher und nicht im Dateisystem ablegen. Der Performancegewinn ist hier also enorm.

Am Beispiel Memcache  möchte ich die notwendigen Einstellungen in Magento hier nun einmal durchspielen. Voraussetzung ist natürlich das entweder der Webhoster überhaupt einen Memcache-Server anbieten kann. Oder, wie in meinem Fall, das die firmeneigene Techik-Abteilung im Nachbarraum die Memcache-Einrichtung auf dem Server übernimmt.

1. Serverkonfiguration prüfen

Eine .php-Datei mit “phpinfo()” auf dem Server erstellen und prüfen ob das Memcache-Modul geladen wird. Es sollte ein Bereich ähnlich diesem zu finden sein:

Memcache auf der phpinfo()-Seite

Memcache auf der phpinfo()-Seite

Der zweite Schritt ist es zu prüfen ob der Memcache-Server läuft. Hier empfehle ich einfach direkt ein Monitoring-Tool zu installieren: http://code.google.com/p/phpmemcacheadmin

Clusteransicht des phpmemcacheadmin

Clusteransicht des phpmemcacheadmin

In der Konfiguration des phpmemcacheadmin muss lediglich die Adresse des Memcache-Servers eingetragen werden um eine umfangreiche Statistik über die Nutzung des Memcache-Servers zu erhalten.

2. Memcache in Magento einrichten

Vorweg  der Code für die /app/etc/local.xml

...
<cache>
 <backend>memcached</backend>
 <memcached>
  <servers>
   <server>
    <host><![CDATA[127.0.0.1]]></host>
    <port><![CDATA[11211]]></port>
    <persistent><![CDATA[1]]></persistent>
   </server>
  </servers>
  <compression><![CDATA[0]]></compression>
  <cache_dir><![CDATA[]]></cache_dir>
  <hashed_directory_level><![CDATA[]]></hashed_directory_level>
  <hashed_directory_umask><![CDATA[]]></hashed_directory_umask>
  <file_name_prefix><![CDATA[]]></file_name_prefix>
 </memcached>
</cache>
...

Dieser Code muss innerhalb des global-Tags eingebunden und konfiguriert werden. Speziell müssen der Host und der Port des Memcache-Servers eingetragen werden. Hier sind noch ein paar Konfigurationseinstellungen für den File-Cache integriert auf den Magento im Fehlerfall des Memcache zurückgreifen kann.

Prinzipiell sollte man solche Umbauarbeiten eh nur auf planmäßig offline genommen Shops oder Testsystemen vornehmen, dennoch gilt es folgendes zu beachten:

Man sollte man nicht direkt die Sessionverwaltung mit in den Memcache umziehen und unbedingt vor der Memcache-Konfiguration in Magento mindestens den Config-Cache deaktivieren. Funktioniert der Memcache-Zugriff nämlich nicht korrekt, hat man sich aus dem eigenen Shop-Backend ausgesperrt und das Entfernen des obigen Codes aus der local.xml schafft auch keine Abhilfe, da diese Einstellung wahrscheinlich noch irgendwo zwischengespeichert wurde.

Falls das doch einmal passieren sollte kann man in der Magento-DB in der Tabelle core_cache_option die einzelnen Cache-Typen von Hand deaktivieren. In älteren Magento-Versionen wurden diese Einstellungen noch in der Datei cache.ser im /app/etc Ordner gespeichert. Diese kann man einfach löschen und danach den Magento-Cache Ordner leeren.

Weitere mögliche Konfigurationen für das Caching finden sich in der von Magento mitgelieferten Beispieldatei /app/etc/local.xml.additional. Hier findet man Beispiele wie auch noch die Sessionverwaltung entweder in die Datenbank oder den Memcache-Server verlegt wird.

Hat alles funktioniert sollte sich das Magento-Grundsystem nun schneller “anfühlen”. Je nach Implementierung und gewähltem Shop-Design können die Geschwindigkeitszuwächse aber stark variieren.

Wie man auch Drittanbieter-Erweiterungen oder nachlässig erstellte Shop-Designs optimiert werde ich in einem der folgenden Blogeinträge behandeln.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

13. Oktober 2011
von Sebastian
1 Kommentar

Magento Code-Profiling mit Xdebug

Im ersten Beitrag möchte ich mich hier mit der Optimierung der PHP-Ausführungszeit auf dem Server beschäftigen.

Magento ist zwar ein komfortables und flexibles System, aber die komplexe Struktur und die hohe Anzahl an Quellcode-Dateien fordern ihren Preis. Denn der Aufruf einer Katalogseite enthält nicht immer nur die Produkte der Kategorie, sondern eventuell auch andere Blöcke. Je nach Shopdesign können die Up/Cross-Selling- oder  ”Kunden die diesen Artikel gekauft haben.. “-Blöcke den Aufruf der Seite deutlich verlangsamen.

Besonders bei Shopdesigns von Dritt-Anbietern wurde da vielleicht nicht unbedingt auf Performance geachtet.

Wie findet man dort nun die paar Zeilen Code, die den Server in die Knie zwingen?

Die PHP Erweiterung Xdebug war für mich bisher das Hilfsmittel der Wahl! Dabei handelt es sich um einen Profiler der den kompletten Ablauf einer Serveranfrage aufzeichnet und als Trace-Datei ablegt. Unter anderem wird dabei jeder Funktions- und Unterfunktions-Aufruf mit der jeweils benötigten Ausführungszeit protokolliert. Die Einstiegstiefe in den Call-Stack reicht je nach Modulkonfiguration bis auf das Level der php-internen Funktionen herunter.

Um Xdebug zu verwenden muss dieses erst auf dem Server installiert werden (danke nochmal an unsere Technik Abteilung).

Ein paar Hinweise zu Xdebug-Konfiguration:

  • Der Config-Switch xdebug.profiler_enable_trigger=1 bewirkt das Xdebug nur den PHP-Aufruf aufzeichnet wenn der URL der Parameter XDEBUG_PROFILE angehangen wurde. Das ist in sofern sinnvoll, da ein Aufruf mit Xdebug den Seitenaufruf aufgrund des Loggings selber noch einmal verlangsamt. Mit dieser Einstellung kann man gezielter nur die Seiten analysieren die zu langsam sind.
  • Es ist irrelevant das Xdebug den Seitenaufruf noch weiter verlangsamt, da sich das Logging proportional auf alle Funktionsaufrufe in der Trace-Datei auswirkt. 
  • Je nach Magento Cache-Einstellungen, Shop-Design und gewählter Seite kann liegt nach dem Trace eine Log-Datei auf dem Server, die auch mal Dateigrößen im dreistelligen MB Bereich annehmen. Hier kann man pauschal schonmal sagen: Je kleiner, je besser ist das System bereits optimiert.
  • Es lohnen sich erstmal keine Trace-Aufzeichnung bei ausgeschaltetem Magento-Cache. Es sollte die Endkonfiguration getestet werden, die auch im Produktiv-System der Magento-Installation eingestellt ist.

Um die erzeugte Log-Datei zu analysieren bieten sich jetzt unterschiedliche Werkzeuge an. Ich persönlich nutze WinCacheGrind für Windows.

WinCacheGrind mit Magento Trace-File

WinCacheGrind mit Magento Trace-File

In der linken Spalte wird der vollständige Call-Stack des Seitenaufrufs dargestellt. Wählt man hier einen Knoten – welcher einem Funktionsaufruf entspricht – aus, werden rechts Funktionsaufrufe aufgelistet die innerhalb der gewählten Funktion passiert sind.

Das wirklich hilfreiche ist nun diese Liste nach der Spalte Cumulated zu sortieren. Dabei werden die Ausführungszeiten von jedem Aufruf inklusiver aller Aufrufe in Unterfunktionen aufsummiert, also des gesamten Call-Stacks ab dem Knoten bis hin zu den php-internen Funktionen.

Von hier aus kann man bequem tiefer in den Call-Stack eintauchen und die Performance-Bremsen finden. Aufgrund der komplexen Seitenaufruf-Struktur von Magento macht es Sinn sich den Page Request Flow vorher verinnerlicht zu haben.

 

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.

15. September 2011
von Sebastian
Keine Kommentare

Los geht’s mit magentoperformance.de

Mit diesem Beitrag starte ich nun meinen Blog über das eCommerce Shopsystem Magento. Hauptsächlich wird es darum gehen, wie man das System beschleunigt. Die Kunden sollen schließlich kaufen und nicht darauf warten, dass der Online-Shop die Seite lädt.

Ich versuche hier Themen aus meiner täglichen Arbeit mit dem Shopsystem für die Allgemeinheit aufzubereiten. Dabei wird es nicht nur um Codeoptimierung am System selbst, sondern auch um Datenbank- und Server-Optimierung gehen.

Auch werde ich die Linkliste noch etwas ausbauen, wenn ich weitere interessante Blogs und Seiten zum Thema finde.

Benötigen Sie mehr Informationen oder Hilfe bei der Umsetzung in Ihrer Magento Installation? Wenden Sie sich einfach an den Ansprechpartner für Online-Shops bei meinem Arbeitgeber basecom.