<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Lieber Linux &#187; Cache</title>
	<atom:link href="http://www.lieber-linux.de/tag/cache/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.lieber-linux.de</link>
	<description>Linux und Open Source Software im Blog</description>
	<lastBuildDate>Thu, 26 Jan 2012 17:58:46 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2</generator>
		<item>
		<title>lieber-linux.de wieder auf eigenem Server</title>
		<link>http://www.lieber-linux.de/2009/11/lieber-linux-de-wieder-auf-eigenem-server/</link>
		<comments>http://www.lieber-linux.de/2009/11/lieber-linux-de-wieder-auf-eigenem-server/#comments</comments>
		<pubDate>Sat, 28 Nov 2009 10:12:19 +0000</pubDate>
		<dc:creator>Nudge</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[APC]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Cache]]></category>
		<category><![CDATA[memcache]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://www.lieber-linux.de/?p=672</guid>
		<description><![CDATA[Seit gestern weilt Lieber Linux wieder auf einem eigenen Server. Der Umzug klappte ganz hervorragend, soweit bekannt, gab es bisher keine Probleme. An dieser Stelle möchte ich noch einmal ganz herzlich &#8220;Danke!&#8221; sagen an seerose.biz, wo Lieber Linux in den letzten Monaten für lau gehostet worden war. Mit dem Umzug einher gab es zwei kleine [...]]]></description>
			<content:encoded><![CDATA[<p>Seit gestern weilt <em><strong>Lieber Linux</strong></em> wieder auf einem eigenen Server. Der Umzug klappte ganz hervorragend, soweit bekannt, gab es bisher keine Probleme. An dieser Stelle möchte ich noch einmal ganz herzlich &#8220;<em>Danke!</em>&#8221; sagen an <a href="http://www.seerose.biz">seerose.biz</a>, wo <em><strong>Lieber Linux</strong></em> in den letzten Monaten für lau gehostet worden war.</p>
<p>Mit dem Umzug einher gab es zwei kleine Änderungen, zum einen wurde der Blog auf WordPress Version 2.8.6 aktualisiert, zum anderen wurden <strong>Memcache</strong> als Daten-Cache und <strong>APC</strong> als PHP-Bytecache integriert. Dadurch sollte der Server Inhalte nun schneller ausliefern. Ich habe vor, hier noch etwas mehr Zeit zu investieren und durch Profiling herauszubekommen, wo man sich noch verbessern kann.</p>
<p>Also immer dran bleiben! <img src='http://www.lieber-linux.de/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.lieber-linux.de/2009/11/lieber-linux-de-wieder-auf-eigenem-server/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Das wäre mit caching nicht passiert</title>
		<link>http://www.lieber-linux.de/2009/11/das-ware-mit-caching-nicht-passiert/</link>
		<comments>http://www.lieber-linux.de/2009/11/das-ware-mit-caching-nicht-passiert/#comments</comments>
		<pubDate>Sun, 08 Nov 2009 22:14:13 +0000</pubDate>
		<dc:creator>Nudge</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[Witziges]]></category>
		<category><![CDATA[Cache]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[msdn]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://www.lieber-linux.de/?p=657</guid>
		<description><![CDATA[Hab ich grad bei heise.de entdeckt: &#8220;Die Internetnutzer weltweit haben im September insgesamt 3,9 Milliarden Stunden und damit 14,5 Prozent ihrer Surfzeit auf Microsoft-Webseiten verbracht.&#8221; &#8211; das wäre mit Caching auf dem Webserver aber nicht passiert! Mal im Ernst: Kennt jemand eine langsamere Seite als msdn? Dagegen ist selbst WordPress ein Fest in Lichtgeschwindigkeit.]]></description>
			<content:encoded><![CDATA[<p>Hab ich grad bei heise.de entdeckt: &#8220;<em>Die Internetnutzer weltweit haben im September insgesamt 3,9 Milliarden Stunden und damit 14,5 Prozent ihrer Surfzeit auf Microsoft-Webseiten verbracht.</em>&#8221; &#8211; das wäre mit Caching auf dem Webserver aber nicht passiert! <img src='http://www.lieber-linux.de/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
<p>Mal im Ernst: Kennt jemand eine langsamere Seite als msdn? Dagegen ist selbst WordPress ein Fest in Lichtgeschwindigkeit. <img src='http://www.lieber-linux.de/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.lieber-linux.de/2009/11/das-ware-mit-caching-nicht-passiert/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Memcache &#8211; gib mir Speed!</title>
		<link>http://www.lieber-linux.de/2009/10/memcache-gib-mir-speed/</link>
		<comments>http://www.lieber-linux.de/2009/10/memcache-gib-mir-speed/#comments</comments>
		<pubDate>Sat, 17 Oct 2009 11:28:22 +0000</pubDate>
		<dc:creator>Nudge</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tipp]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[Cache]]></category>
		<category><![CDATA[memcache]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[Server]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://www.lieber-linux.de/?p=625</guid>
		<description><![CDATA[Ich habe diese Woche den memcache-daemon memcached in Version 1.4.2 mit der PHP-extension memcache ausprobiert und bin einfach nur begeistert. Was ist denn Memcache? Memcache ist ein Dienst, der es erlaubt, Daten im Arbeitsspeicher vorzuhalten. Daneben erlaubt er es auch noch, dies über mehrere Server zu verteilen, also ein richtiges Speicher-Netzwerk aufzubauen. Und Arbeitsspeicher ist [...]]]></description>
			<content:encoded><![CDATA[<p>Ich habe diese Woche den memcache-daemon memcached in Version 1.4.2 mit der PHP-extension memcache ausprobiert und bin einfach nur begeistert.</p>
<h3>Was ist denn Memcache?</h3>
<p>Memcache ist ein Dienst, der es erlaubt, <strong>Daten im Arbeitsspeicher</strong> vorzuhalten. Daneben erlaubt er es auch noch, dies über mehrere <strong>Server</strong> zu verteilen, also ein richtiges Speicher-Netzwerk aufzubauen. Und Arbeitsspeicher ist eine der schnellsten Zugriffsformen auf Daten, die wir zur Zeit haben. Memcache kann durch das <strong>Zwischenspeichern</strong> von Daten, die sonst mühsam aus anderer Stelle geholt werden müssen, das Leben leichter und angenehm schneller gestalten.</p>
<p><span id="more-625"></span>Dazu muss man sagen, dass ja Webanwendungen von Seitenaufruf zu Seitenaufruf jeweils neu initialisiert werden müssen. Als Benutzer in einer Sitzung (<strong>Session</strong>) ist einem das oft nicht bewusst, aber es kostet den Server bei jedem Klick eine entsprechende Zeit, die Session zu restaurieren und die Daten wieder so herzustellen, damit die nächste Aktion ausgeführt werden kann. Die Sessions werden in einer Datei auf dem Server abgelegt. Das heißt also auch Festplattenaktivität pro Klick. Das ist doof. Noch dümmer, dass man nicht wirklich viel in solch eine Session legen kann und sollte. Je größer das Session-File, desto langsamer wird jeder einzelne Aufruf. Dinge also, die nicht wirklich essentiell sind, holt man also immer wieder neu aus einer <strong>Datenbank</strong> oder anderen Quellen.</p>
<p>Daneben gibt es noch <strong>Session-unabhängige Daten</strong>. Bei einem News-Portal also die aktuellen Beiträge zum Beispiel. Doch die sollte man nicht jedem der Millionen von Nutzern in die Session legen. Dabei sind diese ständig gefragt, was macht man also damit? Memcache ist hier die richtige Lösung. Mit Memcache lassen sich diese nutzerunabhängigen Daten genau einmal speichern, aber für jeden Nutzer in hoher <strong>Geschwindigkeit</strong> bereitstellen. Das macht richtig Spaß.</p>
<h3>Was geht in Memcache rein?</h3>
<p>Das Beste ist: Memcache bildet nur eine simple Zuweisung von <strong>Key-Value-Paaren</strong> ab. Unter einer Zeichenkette als Schlüssel, der maximal 250 Zeichen Länge betragen darf, werden einfache Zeichenketten als Werte hinterlegt. Die Werte dürfen maximal 1 MB Größe haben. Was man darin speichert, ist tatsächlich vollkommen egal. Man kann also gezielt eine Datenbank beschleunigen oder aber auch Template-Engines entlasten, also ganze HTML-Vorlagen ablegen, vielleicht auch schon komplett mit allen Inhalten gerendert. Ebenso lassen sich die Einträge unabhängig von deren Nutzung befüllen, zum Beispiel nachts, wenn die Last der Server geringer ist. Tagsüber stehen dann z.B. aggregierte Daten zum schnellen Abruf bereit.</p>
<p>Die <strong>PHP</strong>-Schnittstelle ist dazu mit einer <strong>automatischen Serialisierung</strong> implementiert. Man kann also einfach komplexere Objekte und Arrays einfach hineinwerfen &#8211; und so kommen sie auch heraus. Einzig und allein Pointer wie Dateizeiger oder auch (Datenbank-)Verbindungen sollte man nicht hineinwerfen, denn Ressourcen können im Allgemeinen nicht richtig wiederhergestellt werden. Jeder Eintrag kann dabei on-the-fly mit zlib komprimiert werden, sollte es sich um größere Datenmengen handeln, um im 1-MB-Bereich zu bleiben oder einfach aus Speichergründen. Der Dienst <strong>memcached</strong> wird außerdem mit einer Option -m für die Gesamtspeichergröße</p>
<pre>/usr/local/bin/memcached -d -m &lt;Anzahl der MB&gt; -p 11211 -l 127.0.0.1 -u nobody</pre>
<p>gestartet, sodass bei -m 2048 maximal 2 GB des Arbeitsspeichers belegt werden. Nicht zuletzt wird beim Zwischenspeichern ein Zeitlimit gesetzt, wie lange der Cache-Eintrag gültig sein soll.</p>
<h3>Anwendung am Beispiel www.ebrosia.de</h3>
<p>Zunächst habe ich ein Profiling unserer Startseite gemacht. Die sollte wohl sehr häufig aufgerufen werden. Insgesamt bietet allein diese Seite Anlass für über 100 Datenbank-Abfragen, wenn man alles streng nimmt. Also ist das mein erstes Optimierungsgebiet gewesen. Es gibt natürlich Abfragen, die man besser nicht zwischenspeichert, wie zum Beispiel den Warenkorb des Benutzers &#8211; der sollte immer auf die Sekunde aktuell sein. Die Zusammenfassung des Warenkorbs (x Artikel mit y Gesamtpreis) wandert also am besten in die Session: Verbraucht nicht viel Platz, ist nutzerabhängig und wird bei einer Änderung am Warenkorb neu errechnet, also eher selten.</p>
<p>Bei der Entscheidung, ob ganze <strong>Templates</strong> optimiert werden sollen, habe ich mich dagegen entschieden: Die Mehrzahl der Seiten bietet für eingeloggte Nutzer individualisierte Inhalte, dazu sind die Templates nicht wirklich die Zeitfresser, da reines HTML und PHP zum Einsatz kam, also keine Layout-Engine verwendet wird. Die Seiten leben eher von den kleinen Abfragen an die Datenbank. Hier und da geht es um Links zu einem Artikel, um einen Preis, einen Artikelnamen. Speziell zu Wein kamen noch das Land, ein Gebiet oder eine Rebsorte hinzu.</p>
<p>Wenn man einen <strong>Shop</strong> betreibt, dann sind die Produktdaten das A und O, gerade bei etwa 1000 Artikeln. In fast jeder Shop-Seite, besonders auf der Startseite, kommen mehrere, auch wechselnde Artikel vor. Jeder Artikel hat einen Namen, einen Preis, einen Rabatt und eine Lieferbarkeit. Diese Daten werden eventuell mehrfach pro Sekunde abgerufen, aber vielleicht nur einmal im Monat geändert, zum Beispiel bei einer Rabatt-Aktion. Setzt man hier ein <strong>Zeitlimit</strong> von 10-20 Minuten an, sollten also geschätzte 90% der Aufrufe mit Memcache abgefangen werden, ohne dass die Aktualität leidet. Ich habe mich für ein kurzes Zeitfenster von 10 Minuten entschieden, um im Fehlerfall flexibler zu bleiben.</p>
<p>Also habe ich dort angesetzt. Memcache bildete eine Art <strong>Zwischenschicht</strong> zwischen dem <strong>Shop</strong> als Gestaltung und dem <strong>Datenbank-Backend</strong>. Letztlich konnte ich so mit nur einer kleinen Memcache-Implementierung im Backend gleich sehr viele Shop-Seiten verbessern, die auf die optimierten Funktionen mit den <strong>SQL-Abfragen</strong> zugreifen. Mit der Zwischenspeicherung von Preisen, Rabatten, Namen von Artikeln und Kategorien habe ich die Startseite auf ein durchschnittliches Volumen von etwa 5 SQL-Anfragen gedrückt. Diese Anfragen gehen auf die stetige Erneuerung von Cache-Einträgen wie auch das Rotieren von Produkten in Teasern zurück. Letztlich betrug die Zeit der Auslieferung der Startseite nur noch 35-50 Millisekunden (PHP-Start bis PHP-Ende). Seiten ohne SQL-Inhalte bewegten sich gleichen Zeitrahmen. Man konnte demnach davon ausgehen, dass das <strong>Caching</strong> die Belastung des MySQL-Servers auf ein Minimum reduziert hatte.</p>
<p>Im zweiten Schritt ging es dann an Artikel- und Kategorie-Seiten. Diese waren schon weitgehend durch den vorigen Schritt entlastet. Es waren nur noch wenige Handgriffe zu tun, um auch hier an die gleichen Rendering-Zeiten zu gelangen. In Frieden ließ ich den individuellen Konto-Bereich der Kunden. Hier wäre auch das Optimierungspotenzial geringer. Mit dem investierten Aufwand bin ich bereits weit genug gekommen für eine erste Runde.</p>
<h3>Rollout auf dem Webserver</h3>
<p>Libevent installiert und memcache kurz kompiliert, schon konnte es losgehen. Der Dienst startete angenehm einfach. Das PHP-Modul ist mit einem einfach <strong>phpize</strong>, <strong>configure</strong> und <strong>make</strong> ebenso leicht aufgesetzt. Jetzt nur noch das <strong>Modul</strong> in der <strong>php.ini</strong> fest eingetragen und nach einem kurzen Neustart des Apache sagte mir ein Testskript &#8220;Ja, es ist angerichtet.&#8221; <img src='http://www.lieber-linux.de/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
<p>Der Shop wurde synchronisiert, und schwupps, kamen erste Ergebnisse im Cache an. Das Gefühl eines schnelleren Arbeitens war wirklich omnipräsent, das fühlte sich gleich gut an. Nach einigen Stunden Laufzeit hatte Memcache eine <strong>Trefferquote</strong> von etwa 87% zu verzeichnen, und die Last auf dem DB-Server war damit drastisch gefallen. An die Optimierung weiterer Bereiche kann nun Schritt für Schritt herangegangen werden. <strong>Nagios </strong>zeichnet flachere Kurven, und der Admin ist zufrieden. Jetzt kann Weihnachten kommen. <img src='http://www.lieber-linux.de/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.lieber-linux.de/2009/10/memcache-gib-mir-speed/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>SQL im Webshop: Suche nach Modified</title>
		<link>http://www.lieber-linux.de/2008/07/sql-im-webshop-suche-nach-modified/</link>
		<comments>http://www.lieber-linux.de/2008/07/sql-im-webshop-suche-nach-modified/#comments</comments>
		<pubDate>Thu, 31 Jul 2008 14:22:57 +0000</pubDate>
		<dc:creator>Nudge</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Cache]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Query]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[Webshop]]></category>

		<guid isPermaLink="false">http://www.lieber-linux.de/?p=24</guid>
		<description><![CDATA[Größere Netzwerke, Suchmaschinen oder auch mal eben der kleine Statusbalken arbeiten viel besser, wenn man einen Zeitstempel zur Verfügung stellen kann à la &#8220;Dieser Artikel oder dieses Produkt wurde zuletzt geändert am &#8230;&#8221;. Das ist allerdings nicht ganz so easy, wenn sich die Daten des Satzes in verschiedenen Tabellen befinden. Also muss man die sich [...]]]></description>
			<content:encoded><![CDATA[<p>Größere Netzwerke, Suchmaschinen oder auch mal eben der kleine Statusbalken arbeiten viel besser, wenn man einen Zeitstempel zur Verfügung stellen kann à la &#8220;Dieser Artikel oder dieses Produkt wurde zuletzt geändert am &#8230;&#8221;.</p>
<p>Das ist allerdings nicht ganz so easy, wenn sich die Daten des Satzes in verschiedenen Tabellen befinden. Also muss man die sich mühevoll zusammenklauben. Bei uns im Shop sind es eigentlich nur eine Handvoll Tabellen: Der Artikeleintrag in der Warenwirtschaft, die Varianten, die Preise, Rabatte, Shop-Eintrag, Shop-Texte und Shop-Fotos.</p>
<p><span id="more-24"></span>Die Einträge wurden aus eben diese Tabellen zusammengesucht. Aus Performance-Gründen bieten sich hier Stored Procedures an:<br />
<span style="color: #ff0000;">DELIMITER &#8216;$&#8217;;<br />
CREATE FUNCTION f_getModified(&#8230;xyz&#8230;) RETURNS Datetime READS SQL DATA<br />
BEGIN<br />
DECLARE last DATETIME;<br />
DECLARE cur DATETIME;<br />
SELECT Timestamp INTO last FROM Tbl_XYZ INTO last WHERE xyz;<br />
SELECT Timestamp INTO cur FROM Tbl_XYZ cur WHERE xyz;<br />
SET last = IF(cur &gt; last, cur, last);<br />
&#8230;<br />
RETURN last;<br />
END$<br />
DELIMITER &#8216;;&#8217;$</span></p>
<p>Natürlich schrumpft die Performance bei zunehmender Fragmentierung der Produktdaten erheblich, da die Berechnungskosten linear steigen. Aus diesem Grund habe ich überlegt, die Tabellen über INNER JOINS zu verknüpfen, und die Zeitstempel zunächst in einem Ritt zu holen:<br />
<span style="color: #ff0000;">DELIMITER &#8216;$&#8217;;<br />
CREATE FUNCTION f_getModified(&#8230;xyz&#8230;) RETURNS Datetime READS SQL DATA<br />
BEGIN<br />
DECLARE last DATETIME;<br />
DECLARE cur DATETIME;<br />
SELECT a.Timestamp, b.Timestamp INTO last,cur FROM Tbl_XYZ a INNER JOIN TblXYZ b ON a.x=b.x AND a.y=b.y WHERE xyz;<br />
SET last = IF(cur &gt; last, cur, last);<br />
RETURN last;<br />
END$<br />
DELIMITER &#8216;;&#8217;$</span></p>
<p>Auf meine 7 Tabellen angewendet, gibt das immerhin einen 10%-igen Performance-Schub. Nicht unerheblich, aber trotzdem nicht der Bringer. Anfangs waren es über 50% Geschwindigkeitsvorteil, als ich mit einfachen INNER JOINS über die ersten Tabellen arbeitete. Doch da es nicht immer entsprechende Einträge geben muss, ist man hier und da gezwungen, ein paar LEFT JOIN einzusetzen, welche als OUTER JOIN natürlich langsamer sind.</p>
<p>Ein weiteres Problem ist, dass nicht alle Tabellen einen Eintrag pro Produkt bieten, d.h. an dieser Stelle sind Sub-SELECTS vonnötigen, die den Zeitstempel über MAX(Timestamp) zunächst in der Ziel-Tabelle aggregieren, bevor der INNER JOIN einsetzt.</p>
<p>Letztendlich hatte ich bei einer entsprechend aufbereiteten Seite doch eher dazu tendiert, die bereits mit dem PHP-Script ausgelesenen Timestamps und die fehlenden Stempel per Hash-Scan zu ergänzen. An dieser Stelle konnte ich die Kosten für die Zeitstempel auf 20% der Procedure-Kosten drücken &#8211; allerdings ist diese Vorgehensweise nicht an allen Stellen die beste.</p>
<p>Doch bietet eine Seite nicht auch noch mehr? FAQs? Bewertungen? Mitkaufartikel? Hat sich die Seite nicht auch deswegen geändert, weil hier und da ein Eintrag dazugekommen ist? Was passiert, wenn sich eine Seite geändert hat, weil ein Eintrag <strong>wegfiel</strong>? Gar nicht so einfach, und ich möchte gern wissen, wie das &#8220;große&#8221; Shops so machen&#8230;Wer Ideen hat, ist zum Posten gerne eingeladen!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lieber-linux.de/2008/07/sql-im-webshop-suche-nach-modified/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL vs. Postgres &#8211; never ending story?</title>
		<link>http://www.lieber-linux.de/2008/07/mysql-vs-postgres-never-ending-story/</link>
		<comments>http://www.lieber-linux.de/2008/07/mysql-vs-postgres-never-ending-story/#comments</comments>
		<pubDate>Sun, 27 Jul 2008 09:57:09 +0000</pubDate>
		<dc:creator>Nudge</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Cache]]></category>
		<category><![CDATA[Chemitzer Linux-Tage]]></category>
		<category><![CDATA[DBMS]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Postgresql]]></category>
		<category><![CDATA[Query]]></category>
		<category><![CDATA[Tuning]]></category>

		<guid isPermaLink="false">http://www.lieber-linux.de/?p=23</guid>
		<description><![CDATA[Ich habe mal wieder darüber nachgedacht, ob es nicht Zeit wäre, von MySQL auf Postgres umzusteigen. Bei den diesjährigen Chemnitzer Linux-Tagen habe ich ein interessantes Gespräch mit einem der Postgres-Leute gehabt. Seitdem laufen beide Datenbanken auf meinem Laptop und warten darauf, eingehend verglichen zu werden.Allein die Aufstellung der Testaufgabe und Umgebung beeinflusst jedoch dermaßen die [...]]]></description>
			<content:encoded><![CDATA[<p>Ich habe mal wieder darüber nachgedacht, ob es nicht Zeit wäre, von <a href="http://www.mysql.com/" target="_blank">MySQL</a> auf <a href="http://www.postgresql.org/" target="_blank">Postgres</a> umzusteigen. Bei den diesjährigen <a href="http://chemnitzer.linux-tage.de/2008/info/" target="_blank">Chemnitzer Linux-Tagen</a> habe ich ein interessantes Gespräch mit einem der Postgres-Leute gehabt. Seitdem laufen beide Datenbanken auf meinem Laptop und warten darauf, eingehend verglichen zu werden.<span id="more-23"></span>Allein die Aufstellung der Testaufgabe und Umgebung beeinflusst jedoch dermaßen die Performance, dass ich davor zurückschrecke, die Geschwindigkeit mit ein paar naiven Query-Loops gegeneinanderzustellen. Aus Erfahrung mit MySQL weiß ich, dass Tuning der Datenbankparameter mehr als 50%Â  ausmachen können, geschweige denn das Tabellendesign und die richtige Wahl der Indizes &#8211; hier liegen einfach Welten drin.</p>
<p>Außerdem verwenden wir für 90% unserer Daten MyISAM-Tabellen, die sich jedoch nur schwer mit Postgres vergleichen lassen. Da sollte man schon InnoDB verwenden und auf dieselben Features zurückgreifen, zum Beispiel Transaktionen, Referentielle Integrität oder Row Level Locking, welches ich mal mit zeilenbasiertes Sperren übersetzen möchte.</p>
<p>Es gibt jedoch eine Menge <a href="http://www-css.fnal.gov/dsg/external/freeware/pgsql-vs-mysql.html" target="_blank">Sites</a>, die aus ihren <a href="http://2bits.com/articles/benchmarking-postgresql-vs-mysql-performance-using-drupal-5x.html" target="_blank">eigenen Tests</a> die eine oder andere Datenbank stets als schneller evaluiert haben, zB Drupal. Man sollte da einfach mal nach &#8220;MySQL Postgres&#8221; googlen. Man kann dabei aber schnell ins religiöse Halblicht reingezogen werden. <img src='http://www.lieber-linux.de/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' />  Die Tests werden meist von <a href="http://developers.slashdot.org/article.pl?sid=06/12/18/1152230&amp;from=rss" target="_blank">Advocacies und Flamewars</a> begleitet, die dann meistens die Tests selbst als ungenügend kritisieren oder alles für alte Banane abtun, da Version X.Y.4a ihres Lieblingsprodukts ja alle genannten Kritikpunkte wegsprenge.</p>
<p>Richtig ist: <a href="http://www-css.fnal.gov/dsg/external/freeware/pgsql-vs-mysql.html" target="_blank">Features und Konsistenz</a> bedürfen nun mal Rechenzeit. Je nachdem, wie gut das DBMS programmiert ist, wählt es für jede der Aufgaben den minimalen Mechanismus im Sinne der zugrundeliegenden Architektur. Daraus ergeben sich für die ein oder andere Funktion verschiedene Laufzeiten. Im großen und ganzen sollte die Summe jedoch recht ausgeglichen sein.</p>
<p>Beide Datenbanken haben in den letzten Jahren durch den Hype von Open Source Software im Business-Umfeld offensichtlich stark zugelegt. Nicht zuletzt, weil die Anzahl der Installation und damit auch Anforderungen an diese und das Bug-Reporting ordentlich anstiegen. Dass die Daten gut verpackt und fehlerfrei so rauskommen, wie man diese einmal reingeworfen hat, das kann man beiden System also ohne Zweifel voraussetzen.</p>
<p>Wichtig im Sinne eines stressfreien Einsatzes erscheinen mir daher eher solche Dinge wie Installationshandling, Rechteverwaltung, Unterstützung häufiger Datenmanipulationsaufgaben oder In- bzw. Export von Daten.Â  Für Profis eventuell noch wichtig: Replikation, Failover oder Backup. Am meisten interessiert sicherlich das Gros der Leute, ob die <a href="http://www.php.net/manual/de/ref.pgsql.php" target="_blank">PHP-Anbindung</a> sauber funktioniert und alle Aufgaben einwandfrei unterstützt werden.</p>
<p>Wenn man mehrere parallele Threads laufen hat, zB bei gleichzeitigen Zugriffen auf eine gut besuchte Webseite mit vielen Datenbankoperationen, zählen sicherlich auch nochÂ  andere Performance-Werte:</p>
<ul>
<li>Die Anzahl der konkurrierenden Zugriffe</li>
<li>der dabei entstehende Speicherbedarf (&#8220;memory footprint&#8221;)</li>
<li>das initiale Delay einer Abfrage.</li>
</ul>
<p>Allerdings sind das Sphären, in denen die meisten DBA zu Replikation greifen werden. Wenn ein Server so am Anschlag ist, dann gibt man ihm doch am besten einen Partner zur Hand.</p>
<p>Deshalb habe ich beschlossen, es vorerst bei MySQL zu belassen. Ich werde also erst einmal schauen, was InnoDB mit Row Level Locking und Referenzieller Integrität für mich noch an Vorteilen zu bieten hat. Hier allein lassen sich schon Performance-Vergleiche anstellen und deren Ergebnisse im Alltag effektiv nutzen.</p>
<p>Erst dann kommt dann Postgres dran &#8211; zunächst wird es mir ums Handling gehen und später um die Performance &#8211; und zwar genau an den auch für InnoDB geeigneten Stellen. An dieser Stelle wird davon zu lesen sein.</p>
<p>Stay tuned !</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lieber-linux.de/2008/07/mysql-vs-postgres-never-ending-story/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL 5: Functions oder Views vs. Indizes</title>
		<link>http://www.lieber-linux.de/2007/03/mysql-5-functions-oder-views-vs-indizes/</link>
		<comments>http://www.lieber-linux.de/2007/03/mysql-5-functions-oder-views-vs-indizes/#comments</comments>
		<pubDate>Fri, 23 Mar 2007 14:51:18 +0000</pubDate>
		<dc:creator>Nudge</dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Cache]]></category>
		<category><![CDATA[Indizes]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Profiling]]></category>
		<category><![CDATA[Sortierung]]></category>
		<category><![CDATA[Stored Procedure]]></category>
		<category><![CDATA[View]]></category>

		<guid isPermaLink="false">http://www.lieber-linux.de/?p=7</guid>
		<description><![CDATA[Mein Chef meinte zu unserem neuen Server, der sei &#8220;irgendwie lahm&#8221; . Ich hatte nach dem ersten Hochspielen unseres neuen Onlineshops eigentlich genau das Gegenteil als Erfahrung gemacht. Tatsächlich, die Seite war lahm. Vielleicht nur in seinem neuen IE7? Naja, im ewigen Streben nach mehr Geschwindigkeit habe ich mich diese Woche also ein wenig mit [...]]]></description>
			<content:encoded><![CDATA[<p>Mein Chef meinte zu unserem neuen Server, der sei &#8220;irgendwie lahm&#8221; . Ich hatte nach dem ersten Hochspielen unseres neuen Onlineshops eigentlich genau das Gegenteil als Erfahrung gemacht. Tatsächlich, die Seite war lahm. Vielleicht nur in seinem neuen IE7? Naja, im ewigen Streben nach mehr Geschwindigkeit habe ich mich diese Woche also ein wenig mit Indizes und Views sowie Funtionen (UDFs) in MySQL beschäftigt, um den Shop so gut wie möglich auf Trab zu bringen.</p>
<p><span id="more-7"></span>Zuerst suchte ich eine schöne handliche Funktion, die mir eine Seitenstruktur aufwärts traversiert, um meine eigene, recht grobgeschnitzte  Routine zur Anzeige  der  Navigation abzulösen.</p>
<p>Also erstmal nach CREATE FUNCTION gegoogelt, denn mein Buch <strong>PHP5/MySQL4</strong> umschifft gekonnt dieses technische Terroir. Das Google-Ergebnis war frustrierend: Der wichtigste Link war schließlich die offizielle MySQL-Doku, in den Groups gab es ein paar Beiträge auf polnisch, was ich leider gar nicht verstehe.</p>
<p>Die auf der <a title="MySQL-Doku" href="http://dev.mysql.com/doc/refman/5.1/de/create-procedure.html" target="_blank">MySQL-Doku</a> angegebene Syntax-Beschreibung waren abschreckend kompliziert dargestellt.  Da muss jemand denken &#8220;Eine EBNF sagt mehr als 1000 Worte&#8221;. Da hat er wohl recht &#8211; wenn man einmal in der Materie drin ist. Wenn mans nicht ist, siehts ganz düster aus.</p>
<p>Eine <a title="andere Seite" href="http://www.strassenprogrammierer.de/mysql-stored-procedures-trigger_tipp_469.html" target="_blank">andere Seite</a> geht dafür den Top-down-Approach, auch mein Traversal-Problem ist hier als Beispiel-Lösung angegeben (juhu! dachte ich erst). Super einfühlsam, hier bleibt man jedoch nach den Überschriften etwas stecken. Denn das Beispiel funktionierte nicht.</p>
<p>Nach etwas Fiddelei stellte sich bei mir heraus: Zunächst war die Syntax &#8220;DELIMITER ;&#8221; nicht korrekt, dann muss man der Funktion immerhin noch sagen, ob diese READ SQL DATA oder anderen Typs ist. Nach der Syntax dann die Anweisung: Die Abbruchbedingung der WHILE-Schleife haute gar nicht hin. Angeblich soll ja diese Routine in MySQL-Syntax zu beherrschen sein, aber eine WHILE (NOT ISNULL(variable)) scheints nicht zu geben, hier rennt man in eine Endlosschleife. Mit Ctrl-C schießt man dabei leider nur seinen Client ab, der Server läuft mit dem Prozess dann in aller Ruhe weiter. Andere Bedingungen wie &#8220;(variable&gt;)&#8221; stoppten bei NULL-Werten ebensowenig.</p>
<p>Nach ein paar Versuchen hatte der Server dann einen Load von 9.0 und MySQL eine Prozessorlast von konstant 200 Prozent erreicht. Mein vi hing, die Buchstaben waren eher per Fuß als per ssh auf dem Server. Mühsam schaffte ich es noch per top zu sehen, wer dran schuld war, so dass ich dann MySQL einfach stoppte und neu startete (die anderen in der Firma hatten hoffentlich gerade nix im Intranet zu tun:-)).</p>
<p>Also das war schon mal ein kleiner Reinfall. Meine Routine blieb.</p>
<p>Heute dann mehr Glück, wenn auch mit einem anderen Thema: Views und Indexes. Ich hatte zwischenzeitlich die Shop-Homepage mit einer Menge von Profiling-Anweisungen ausgestattet und konnte so die einzelnen Arbeitsschritte genau analysieren. Erstaunlich schnell konnte ich die &#8220;wunden Punkte&#8221; erkennen.</p>
<p>Mein erstes Fazit war: Lange Dateien per include in PHP-Code zu laden ist nicht schneller, als wenn es zwei kleinere Dateien sind. Meine Arbeitshypothese lautete bis dato noch: Weniger Dateien zu öffnen dauert nicht so lange wie ein sequentieller Lesevorgang einer Datei. Da lag ich wohl falsch, und bin nun froh, dass sinnvolles Strukturieren von include-Dateien &#8220;erlaubt&#8221; ist.</p>
<p>Zweites Fazit: Meine Arbeitshypothese, dass ich in Zukunft auf Apache&#8217;s mod_mem_cache setzen sollte, damit die ständig geladenen Seitenanteile nicht bei jedem Aufruf von der Platte gelesen werden müssen, ist hinfällig geworden: Nur 10% der gesamten Server-Bearbeitungszeit geht auf das Laden von Seiten zurück:</p>
<p>[PROFILING] =&gt; Array<br />
(<br />
[0] =&gt; Start<br />
[1] =&gt; 0.0000: session.inc.php geladen (0.0000)<br />
[2] =&gt; 0.0002: request.inc.php geladen (0.0002)<br />
[3] =&gt; 0.0004: ssl.inc.php geladen (0.0002)<br />
[4] =&gt; 0.0005: locale.inc.php geladen (0.0001)<br />
[5] =&gt; 0.0009: sage.inc.php geladen (0.0004)<br />
[6] =&gt; 0.0034: basic.inc.php geladen (0.0026)<br />
[7] =&gt; 0.0036: waehrungen.inc.php geladen (0.0002)<br />
[8] =&gt; 0.0040: db.inc.php geladen (0.0003)<br />
[9] =&gt; 0.0043: db verbunden (0.0004)<br />
[10] =&gt; 0.0043: Sortierung berechnet (0.0000)<br />
[11] =&gt; 0.0066: konto.inc.php geladen (0.0023)<br />
[12] =&gt; 0.0071: aktionen.inc.php geladen (0.0005)<br />
[13] =&gt; 0.0074: menue_js.inc.php geladen (0.0003)<br />
[14] =&gt; 0.0084: left.php geladen (0.0010)<br />
[15] =&gt; 0.0084: goInsite(); (0.0000)<br />
[16] =&gt; 0.0091: kategorie.inc.php geladen (0.0008)<br />
[17] =&gt; 0.0096: Kopf ausgegeben (0.0005)<br />
<strong> [18] =&gt; 0.4466: Artikel gefunden (0.4369)</strong><br />
[19] =&gt; 0.4471: Sortieren/Blaettern ausgegeben (0.0005)<br />
[20] =&gt; 0.4521: Artikel ausgegeben (0.0050)<br />
[21] =&gt; 0.4523: Blaettern ausgegeben (0.0001)<br />
[22] =&gt; 0.4524: Entering _post.php (0.0001)<br />
[23] =&gt; 0.4525: footer.inc.php geladen (0.0001)<br />
[24] =&gt; 0.4584: right.inc.php geladen (0.0059)<br />
[25] =&gt; 0.4600: menue.inc.php geladen (0.0016)<br />
[26] =&gt; 0.4605: Seitenende (0.0005)<br />
)</p>
<p>Hier sieht man schnell: von den 460 Millisekunden gehen 436 für die Artikelsuche in der Datenbank drauf.</p>
<p>Nach der Einführung von Indizes für einige Bereiche der Website und der Einführung eines Views für die Darstellung aller zur Zeit aktiven Artikel sieht das Ergebnis gleich ganz anders aus:</p>
<p>[PROFILING] =&gt; Array<br />
(<br />
[0] =&gt; Start<br />
[1] =&gt; 0.0000: session.inc.php geladen (0.0000)<br />
[2] =&gt; 0.0002: request.inc.php geladen (0.0002)<br />
[3] =&gt; 0.0004: ssl.inc.php geladen (0.0002)<br />
[4] =&gt; 0.0005: locale.inc.php geladen (0.0001)<br />
[5] =&gt; 0.0009: sage.inc.php geladen (0.0004)<br />
[6] =&gt; 0.0035: basic.inc.php geladen (0.0026)<br />
[7] =&gt; 0.0037: waehrungen.inc.php geladen (0.0002)<br />
[8] =&gt; 0.0040: db.inc.php geladen (0.0003)<br />
[9] =&gt; 0.0044: db verbunden (0.0004)<br />
[10] =&gt; 0.0044: Sortierung berechnet (0.0000)<br />
[11] =&gt; 0.0067: konto.inc.php geladen (0.0023)<br />
[12] =&gt; 0.0072: aktionen.inc.php geladen (0.0005)<br />
[13] =&gt; 0.0074: menue_js.inc.php geladen (0.0003)<br />
[14] =&gt; 0.0084: left.php geladen (0.0009)<br />
[15] =&gt; 0.0088: goInsite(); (0.0004)<br />
[16] =&gt; 0.0096: kategorie.inc.php geladen (0.0008)<br />
[17] =&gt; 0.0102: Kopf ausgegeben (0.0006)<br />
<strong> [18] =&gt; 0.0106: Artikel gefunden (0.0004)</strong><br />
[19] =&gt; 0.0111: Sortieren/Blaettern ausgegeben (0.0005)<br />
[20] =&gt; 0.0142: Artikel ausgegeben (0.0031)<br />
[21] =&gt; 0.0144: Blaettern ausgegeben (0.0001)<br />
[22] =&gt; 0.0145: Entering _post.php (0.0001)<br />
[23] =&gt; 0.0146: footer.inc.php geladen (0.0001)<br />
[24] =&gt; 0.0205: right.inc.php geladen (0.0059)<br />
[25] =&gt; 0.0236: menue.inc.php geladen (0.0031)<br />
[26] =&gt; 0.0236: Seitenende (0.0000)<br />
[max] =&gt; Array<br />
(<br />
[0] =&gt; 0.00587892532349<br />
[1] =&gt; right.inc.php geladen<br />
)<br />
)</p>
<p>Und so wurden aus 461 ms 24 ms Gesamtladezeit. <img src='http://www.lieber-linux.de/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
<p>Ich muss aber dazu sagen, dass die Einführung eines Views selbst zu keiner nennenswerten Performance-Steigerung führte. Die dabei gemessenen Ergebnisse sahen genauso, wenn nicht noch schlechter als die Abfrage der Originaltabelle aus. Erst die Indizes bringen die Performance, im obigen Beispiel ist dies immerhin eine Reduktion auf 5% der Original-Antwortzeit. Das eigentliche Problem, nämlich 1-2 Sekunden Rendering der Seite im IE7, ist damit (natürlich rein statistisch) auf 0.6-1.4 Sekunden geschrumpft. Dennoch werden wir hier nochmal ran, und diesmal den CSS-Code studieren müssen.</p>
<p>PS: Ich habe das View schließlich doch benutzt, damit mein Code einfacher und leichter wartbar wird. Eventuell werde ich den View noch auf die zur Artikelsuche relevanten Felder begrenzen, und die Messung damit wiederholen. Die großen Unterschiede wird es jedoch nicht mehr machen können. Und die Abweichungen jeder Messung sind augenscheinlich. Zweimal nacheinander eine ähnlich Abfrage bringt allein durch das Caching der Datenbank alle Ergebnisse durcheinander.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lieber-linux.de/2007/03/mysql-5-functions-oder-views-vs-indizes/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

