Serie High Performance Websites – Teil 5: letztes Feintuning

Dieser vorerst finale Teil befasst sich mit den restlichen Serverseitigen Themen Redirects und Keep-Alive. Zusätzlich wird auf DNS und das DNS Cacheverhalten des Browsers eingegangen.
Für die Performance von Webseiten hat DNS ausschließlich beim erstmaligen Aufruf Einfluss. Hier ist zu beachten das der DNS Server ihres ISP typischerweise 10-200 Millisekunden braucht um eine Namensauflösung durchzuführen. Nach der Erstanfrage wird der DNS Eintrag sowohl im Betriebssystem als auch im Browser gecached um erneute Anfragen zu vermeiden. Wie lange der bereits erfragte Eintrag zwischengespeichert wird gibt in erster Linie die TTL (Time-To-Live) in der DNS Zone an, desweiteren ist in den Browsern hinterlegt wie lange der DNS Cache zusätzlich zur TTL Einträge speichern soll. Dies kann bedeuten das trotz bereits abgelaufener TTL ein DNS Eintrag im Browser noch durch den Cache vorgehalten wird.
In Summe kann festgehalten werden das es sinnvoll ist die Verwendung unterschiedlicher Hostnamen auf ein minimum zu reduzieren um die durch DNS Anfragen entstehende Latenz beim erstmaligen Laden der Seite zu reduzieren.
In den vorherigen Kapiteln wurde vieles unternommen um die Ladezeit für den Benutzer so gering wie möglich zu halten. Genau das Gegenteil verursachen leider verwendete Redirects, wird bei der ersten HTTP Anfrage ein Redirect vom Server erwidert folgt daraus ein erneuter (also zusätzlicher) HTTP Request. Dieses Verhalten verzögert das Laden der eigentlichen Seite und sollte vermieden werden.
Ziel ist also Redirects wenn möglich zu verhindern oder sehr eingeschränkt zu verwenden.
Zur weiteren Optimierung sollte Keep-Alive serverseitig aktiviert werden, hier werden vom Browser bereits offene TCP Verbindungen für einen definierten Zeitraum wiederverwendet. Dieser Mechanismus spart zusätzlich Zeit für den eigentlichen TCP Verbindungsaufbau. Der Server und Browser signalisieren beim initiieren der Verbindung im “Connection” Header ob Keep-Alive Verbindungen erlaubt sind. Zusätzlich zu Keep-Alive wird unter HTTP/1.1 Pipelining zur Verfügung gestellt um die Verbindungsgeschwindigkeit zu erhöhen. Hier werden mehrere Anfragen über einen Socket übermittelt ohne vorher auf eine Antwort des Servers warten zu müssen.
In Apache wird Keep-Alive durch die folgenden Direktiven aktiviert.

Keep-Alive on

Serie High Performance Websites – Teil 4: Weniger ist Mehr !

Ein weiterer wichtiger Punkt bei der Optimierung ist das Thema Kompression, hierbei werden sämtliche noch komprimierbaren Inhalte (HTML, CSS, JavaScript, …) geschrumpft und so in einer verkleinerten Version ausgeliefert.
Die durchschnittliche Einsparung durch Komprimierung beträgt bis zu 70% was bedeutet das nur 30% des ursprünglichen Dateiinhaltes übertragen werden müssen. In den meisten Setups wird “gzip” für die Verkleinerung der ܜbertragungmenge verwendet, gzip bietet hier eine bessere Komprimierungsrate als “deflate”.
Um die Komprimierung in Apache zu aktivieren muss mod_deflate geladen werden, zusätzlich zum laden des Moduls muss in der VirtualHost (oder globalen) Konfiguration die Kompression aktiviert werden.

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/x-javascript

Zusätzlich lässt sich mit dem Parameter DeflateCompressionLevel der Komprimierungsgrad festlegen, die Grundeinstellung ist hier meist ausreichend.
Einige ältere Browserversionen unterstützen leider keine Kompression, deswegen sollten zusätzlich folgende Einstellungen hinterlegt werden um auch diesen “Exoten” Zugang zur Webpräsenz zu ermöglichen.

BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

Durch die nun gesetzten Einstellungen werden sämtliche “textuellen” Inhalten wie HTML/CSS oder JS ab jetzt komprimiert übertragen. Im nächsten Teil wird nun noch angeführt wie mittels DNS, Keepalive und der vermeidung von Redirects noch zusätzlich Ladezeit verkürzt werden kann.
Analog hierzu ist die Konfiguration unter Lighttpd, das “deflate” Modul ist vollständig unter http://redmine.lighttpd.net/projects/lighttpd/wiki/Mod_Deflate dokumentiert. Die Einrichtung ist hier denkbar einfach sofern Lighttpd mit Kompressionsunterstützung übersetzt wurde (sollte in allen Distributionspaketen Standard sein).

Serie High Performance Websites – Teil 3: Cachen erlaubt !

Nachdem im vorherigen Teil die HTTP-Requests auf ein minimum reduziert wurden ist es nun an der Zeit den Client das zwischenspeichern von Inhalten auf eine besitmmte Zeit hin zu erlauben.
Hierfür werden sog. Expires-Header serverseitig konfiguriert. Die dadurch in den HTTP-Response gesetzten  “Expires” und “Cache-Control” Header werden bei der Auslieferung jedes Contentelements übertragen. Definiert wird der Expires-Header auf Basis von Dateinamen oder Pfaden in der abgerufenen URL. Um die Expires in Apache nutzen zu können muss mod_expires aktiviert sein.
Für Lighttpd ist eine passende Anleitung unter http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:ModExpire zu finden.
Zusätzlich ist es notwendig in der Konfiguration des entsprechenden VirtualHost’s folgende definitionen vorzunehmen:

<IfModule mod_expires.c>
ExpiresActive on
ExpiresByType image/gif "access plus 10 years"
ExpiresByType image/jpeg "access plus 10 years"
ExpiresByType image/png "access plus 10 years"
ExpiresByType text/css "access plus 2 years"
ExpiresByType text/js "access plus 2 years"
ExpiresByType text/javascript "access plus 2 years"
ExpiresByType application/javascript "access plus 2 years"
ExpiresByType application/x-javascript "access plus 2 years"
</IfModule>

Hier wird für die genannten Elemente jeweils eine Ablaufzeit von 10 bzw. 2 Jahren definiert, was den anfragenden Browser dazu bringt das geladene Element für 10 bzw. 2 Jahre im Browsercache zu halten und nicht erneut abzufragen.
Zusätzlich kann noch ein genereller Expire mittels
ExpiresDefault ”access plus 1 days”

gesetzt werden.
Durch dieses Verhalten ergibt sich allerdings ein neues Problem, beispielsweise kann es sein das sich innerhalb dieser 2 Jahre das Stylesheet verändert. In diesem Fall würde der Browser das veränderte CSS durch die ihm Bekannte “Cache-Halbwertszeit” von 2 Jahren nicht neu laden und die Änderung würde nicht angezeigt. Um dieses Problem zu verhindert sollte eine Versionierung der “halbstatischen” Elemente durchgeführt werden, aus “style.css” wird so “style-20090101-001.css”.
Ändert sich nun das Stylesheet auf “style-20090101-002.css” wird der Sourcecode der HTML Seite um den Namen des CSS angepasst und der Browser läd automatisiert die neue Version da ihm das “neue” Inhaltselement nicht im Cache bekannt ist.

Serie High Performance Websites – Teil 2: Minimieren von HTTP-Requests

Im ersten technischen Teil der High Performance Websites Serie dreht sich alles um die Reduzierung von HTTP-Requests.
Interessant zu wissen ist, dass bei den meisten Webseiten (bei leerem Browser Cache) weniger als 10-20% der Antwortzeit für den eigentlichen Abruf der HTML Dokumente verwendet wird. Im Umkehrschluss werden dann 80-90% der Ladezeit durch HTTP-Requests verbraucht. Durch dieses Verhältnis wird deutlich das die Anzahl der HTTP-Anfragen zur Ladezeit der gesamten Webseite und damit auch zur Gesamtperformance bedeutend beiträgt.
Um unnötige Requests zu vermeiden gibt es mehrere Möglichkeiten, eine davon ist mehrere vereinzelte Bilder zu einem Ganzen zusammenzufassen und mittels Imagemap oder CSS-Sprite in einzelne Links zu unterteilen.
Genauer beleuchtet kann z.B. die Navigationsleiste einer Webseite aus vielen einzelnen Bildern bestehen wodurch für jeden Abruf eines Bildes ein eingener HTTP-Request erzeugt wird. Werden nun diese einzelnen Bilder zusammengefasst entsteht nur noch eine einzige HTTP-Anfrage.
Eine weitere Möglichkeit ist die direkte Einbettung von Bildern in eine HTML oder PHP Seite mittels sog. Inline-Images. Dabei wird direkt im Sourcecode der Seite das “data:” URI Schema wie folgt verwendet:
HTML URI Schema:
<img src="data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP
C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA
AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J
REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq
ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0
vr4MkhoXe0rZigAAAABJRU5ErkJggg==" alt="Red dot" />

Siehe auch: http://en.wikipedia.org/wiki/Data_URI_scheme
Bei der Verwendung von Inline-Images ist hervorzuheben das diese nicht Seitenübergreifend gecached werden was bedeutet das jede Seite die das Bild enthält auch um dessen Größe im Cache anwächst.
Leider werden Inline-Images nicht von allen Browsern verarbeitet, dazu gehört unter anderem der Internet Explorer, daher wird diese Variante eher selten benutzt. Imagemaps bzw. CSS-Sprites sind also den Inline-Images vorzuziehen.
Abschließend soll auch noch auf die Kombination von einzelnen JavaScript und Stylesheets hingewiesen werden. Hier ist es empfehlenswert die jeweils voneinander getrennten einzelnen JavaScript und Stylesheet includes zu vereinheitlichen sodass noch jeweils ein gesamtes Script geladen werden muss. Auch diese Maßnahme verringert HTTP-Anfragen.

Serie High Performance Websites – Teil 1: Vorarbeiten

Nachdem die MySQL Performance Serie ein großer Erfolg war, haben wir uns dazu entschlossen eine weitere Reihe mit dem Titel “High Performance Website” aufzulegen.
In dieser Blogserie versuchen wir Anregungen zu liefern auf welche Einstellungen zurückgegriffen werden kann um lahmenden Webseiten Beine zu machen oder zu erwartende Lastspitzen besser abzuarbeiten. Ziel ist es dem Anwender in einem möglichst kleinem Zeitfenster die gewünschte Website auszuliefern. Hierbei ist wichtig dem Gast möglichst schnell eine Darstellung im Browser zur Verfügung zu stellen. Ein Ladebalken in der Statusleiste des Browsers ist hierbei die schlechteste der vorhandenen Möglichkeiten.
Um mögliche Veränderungen wahrnehmen zu können ist wichtig diese auch zu protokollieren. Für die technischen Messwerte wie Ladezeit oder HTTP-Header können Tools wie Firebug oder YSlow unter Firefox sowie die integrierten Developmenttools von Apple`s Safari unterstützen. Zum anderen sollten zusätzlich zu den gemessenen Werten auch Konfigurationsänderungen und subjektive Wahrnehmungen beim Aufruf der Seite wie z.B. verlangsamt ladende Bilder oder ein verzögerter Aufbau des Seiteninhaltes dokumentiert werden.
In den nächsten Artikeln geben wir einen Einblick in diverse Möglichkeiten um den Aufruf zu beschleunigen. Hier werden sowohl Server- als auch Codeseitige Verbesserungen dargestellt und beschrieben, im Detail werden folgende Themen behandelt:

  • Teil 1: Vorarbeiten und Grundlagen
  • Teil 2: Minimieren von HTTP-Requests
  • Teil 3: Einbinden von Expires-Header
  • Teil 4: Komprimierung der Seiten
  • Teil 5: DNS Lookups und Redirects
Wir hoffen die aufgezeigten Lösungen helfen Ihnen weiter. Für Rückfragen zu den genannten Themen stehen wir selbstverständlich gerne zur Verfügung.

MySQL Performance Serie – Addon: Analyze Table

Im MySQL-Umfeld gibt es, wie in unsere Performance-Serie ausführlich geschildert, eine Vielzahl an Stellschrauben zur Optimierung.
Ein ganz wichtiger Punkte zur Optimierung von Joined-Selects ist die regelmässige Pflege der der internen Datenbankstatistiken. Hierfür gibt es den Befehl Analyze Table, welcher die Tabelleninhalte analysiert und entsprechende Statistiken über die Schlüsselverteilung speichert.
Der Befehl “show index from table” ermöglicht einen Blick auf die aktuellen Statistiken und die entsprechende Index-Kardinalität. Kardinalität ist quasi der “Grad” der Eindeutigkeit eines bestimmten Tabellenattributs. So sollte z. B. die Kardinalität eines Unique-Key-Attributes nach Aktualisierung durch Analyse Table der Menge der vorhandenen Rows entsprechen.
Diese Statistiken sind für die Datenbank extrem wichtig, um bei verknüpften Abfragen den “richtigen Weg” zur Verringerung der zu verarbeitenden Daten einzuschlagen. Auch wenn der Explain-Befehl vermuten lässt, dass ein angelegter Index nicht verwendet, kann ein Analyze hier Abhilfe schaffen.
Wichtig ist, dass ein Analyze Table einen entsprechenden Lock der Tabelle zur Folge hat und über das Binlog auch in Replikationsumgebungen verteilt wird.

Bernd Erk
Bernd Erk
CEO

Bernd ist Geschäftsführer der NETWAYS Gruppe und verantwortet die Strategie und das Tagesgeschäft. Bei NETWAYS kümmert er sich eigentlich um alles, was andere nicht machen wollen oder können (meistens eher wollen). Darüber hinaus startet er das wöchentliche Lexware-Backup und investiert seine ganze Energie in den Rest der Truppe und versucht für kollektives Glück zu sorgen. In seiner Freizeit macht er mit sinnlosen Ideen seine Frau verrückt und verbündet sich dafür mit seinem Sohn.

MySQL Performance Serie – Teil 8: Replikation

MySQL verfügt über einen guten Replikationsmechanismus, der bei korrekter Konfiguration sehr fehlerunanfällig und stabil seinen Dienst verrichtet.
Die Replikation einer Datenbank hat in der Regel eines der folgenden Motive:

  • Lastverteilung: Entweder über Multi-Master-Replikation oder Master-Slave durch gezielte Select-Zuweisung auf die Slave-Datenbanken
  • Ausfallsicherheit: Multi-Master-Replikation als Failover-Datenbank ohne manuellen Zugriff
  • Verwendung einer Slave-DB als Aggregations- und/oder Analysedatenbank.

Neu in der Version 5.1 ist die Mixed-Based Replikation (kurz MBR). Hier erfolgt die Replikation normalerweise Statement basierend und wird nur bei bestimmten Ausnahmen auf Row-Level Replikation umgestellt. In vielen Szenarien resultiert die Verwendung von MBR in einer erheblichen Performancesteigerung.
Ein bekanntes Problem bei der Multi-Master-Replikation ist die automatische Schlüsselvergabe. Die MySQL Parameter auto_increment_increment und auto_increment_offset vermeiden hier doppelte Schlüsselvergabe indem Sie eine Art Schlüsselband pro Replikationsknoten erzeugen.
(mehr …)

Bernd Erk
Bernd Erk
CEO

Bernd ist Geschäftsführer der NETWAYS Gruppe und verantwortet die Strategie und das Tagesgeschäft. Bei NETWAYS kümmert er sich eigentlich um alles, was andere nicht machen wollen oder können (meistens eher wollen). Darüber hinaus startet er das wöchentliche Lexware-Backup und investiert seine ganze Energie in den Rest der Truppe und versucht für kollektives Glück zu sorgen. In seiner Freizeit macht er mit sinnlosen Ideen seine Frau verrückt und verbündet sich dafür mit seinem Sohn.

MySQL Performance Serie – Teil 7: Table-Partitioning

Mit der Version 5.1 hält Table-Partitioning Einzug in MySQL. Kommerzielle Datenbanken wie z.B. Oracle haben schon seit Jahren dieses Feature implementiert und es wird wirklich Zeit dass MySQL hier nachzieht.
Partitioning zerlegt nach definierbaren Regeln eine physikalische Tabelle in einzelne Teile. Für den Anwender ist dies transparent und er bekommt bei normalen DDL nichts von den vorhandenen Partitionen mit. Die “Zerlegungsregel” kann bei Anlage der Tabelle mitgegeben und einzelne Partionen danach entfernt oder hinzugefügt werden.
Version 5.1 unterstützt MySQL folgende Partitionierungsstrategien:

  • List-Partitioning
  • Range-Partitioning
  • Hash-Partitioning
  • Key-Partitioning

Die Dokumentation gibt detailierten Aufschluss über die vorhandenen Optionen und das passende Einsatzszenario.
Besonders interessant ist noch das sogenannte Partition-Pruning. Hier kann die Datenbank einzelne Partitionen bei einer selektiven Abfrage mit Where-Klausel ausschliessen und somit die zu verarbeitende Menge minimieren.
Replikation ist Inhalt des nächsten Teils.

Bernd Erk
Bernd Erk
CEO

Bernd ist Geschäftsführer der NETWAYS Gruppe und verantwortet die Strategie und das Tagesgeschäft. Bei NETWAYS kümmert er sich eigentlich um alles, was andere nicht machen wollen oder können (meistens eher wollen). Darüber hinaus startet er das wöchentliche Lexware-Backup und investiert seine ganze Energie in den Rest der Truppe und versucht für kollektives Glück zu sorgen. In seiner Freizeit macht er mit sinnlosen Ideen seine Frau verrückt und verbündet sich dafür mit seinem Sohn.

MySQL Performance Serie – Teil 6: Slow-Query-Log

Vielen MySQL-Benutzern ist der Slow-Query-Log natürlich bekannt und meistens die erste Anlaufstelle um langsame SQL-Queries im System aufzuspüren und in der Einzelanalyse zu optimieren. Interessant sind jedoch die Möglichkeiten die long_query_time, also die Zeit bis zur Ausführung des Statements, zu parametrisieren und die Statements auszugeben, die nicht von einem Index gebrauch machen. Wichtig ist jedoch immer eine längerfristige Beobachtung und Analyse des Systems um Unschärfen oder einmalige “Ausrutscher” zu eliminieren und keine vorzeitigen Schlüsse zu ziehen.
Beim Start des MySQL-Daemons mit dem Parameter “–log-queries-not-using-indexes” werden Statements ohne Verwendung von Indizes ebenfalls protokolliert. Die Anlage des richtigen Indizes ist neben der Aktivierung des Query-Caches meist die einfachste Möglichkeit die Selektionsperformance deutlich zu verbessern.
Zusätzlich kann der Datenbank-Administrator noch seine eigenen Langläufer mit dem Parameter “–log-slow-admin-statements” ausgeben.
Inhalt des nächsten Serienteils ist das Table-Partioning.

Bernd Erk
Bernd Erk
CEO

Bernd ist Geschäftsführer der NETWAYS Gruppe und verantwortet die Strategie und das Tagesgeschäft. Bei NETWAYS kümmert er sich eigentlich um alles, was andere nicht machen wollen oder können (meistens eher wollen). Darüber hinaus startet er das wöchentliche Lexware-Backup und investiert seine ganze Energie in den Rest der Truppe und versucht für kollektives Glück zu sorgen. In seiner Freizeit macht er mit sinnlosen Ideen seine Frau verrückt und verbündet sich dafür mit seinem Sohn.

MySQL Performance Serie – Teil 5: Key-Buffer

Bei Verwendung der MyISAM-Storage Engine macht die Datenbank vom sogenannten Key-Buffer-Cache gebrauch. In diesem Cache werden die meist frequentierten Index-Blöcke, ähnlich wie beim Query-Cache, abgelegt um den langsameren Zugriff auf das Plattensubsystem zu vermeiden. Bei Nutzung von MyISAM ist die Verwendung dieses Speicherbereichs ein absolutus muss.
Desto näher die Key-Hit-Ratio an 100% ist, desto besser die Effektivität und daraus folgend die Performance. Mit folgender Formel lässt sich die Key-Hit-Ratio ermitteln:

  • 100 – (key_reads * 100 / key_read_requests)

Die Werte können mit dem Befehl “show global status” ermittelt werden. Besonders interessant ist noch die Möglichkeit hochfrequentierte Blöcke manuell in den Key-Buffer zu laden. Dieses sogenannte Index Preloading gibt dem Administrator eine gute Möglichkeit die Wirksamkeit zu ermitteln.
Schwerkpunkt des nächsten Teils ist das Thema Slow-Queries.

Bernd Erk
Bernd Erk
CEO

Bernd ist Geschäftsführer der NETWAYS Gruppe und verantwortet die Strategie und das Tagesgeschäft. Bei NETWAYS kümmert er sich eigentlich um alles, was andere nicht machen wollen oder können (meistens eher wollen). Darüber hinaus startet er das wöchentliche Lexware-Backup und investiert seine ganze Energie in den Rest der Truppe und versucht für kollektives Glück zu sorgen. In seiner Freizeit macht er mit sinnlosen Ideen seine Frau verrückt und verbündet sich dafür mit seinem Sohn.