Posts Tagged ‘MySQL’

Donnerstag, 14. Februar 2013

Data Mining mit Ubuntu, MySQL, PHP und Python

Beruflich setze ich mich derzeit mit der Analyse von Inhalten von Web-Sites auseinander. Nachfolgend habe ich einige Erfahrungen aufgelistet, welche ich dabei gemacht habe.

Wir verarbeiten in diesem Projekt Web-Sites, welche wir mit entsprechenden Tools aus dem Web auf den lokalen Rechner gespiegelt haben. Die Web-Site Assets liegen im Dateisystem. Zur Weiterverarbeitung der Daten wurden die HTML-, JS-, CSS- und XML-Rohdaten in eine MySQL-Tabelle gespitzt (12GB) und anschliessend mit Meta-Daten ergänzt.

Ich persönlich bin nicht sicher, ob die Ablage von HTML-Code in der Datenbank die sinnvollste und performanteste Lösung ist, aber dies war nunmal der Stand des Projektes als ich dazu gestossen bin — und daran liess sich nichts mehr rütteln.

Da wir unter anderem auch Volltextsuchen auf die Grunddaten anwenden, hätte ich mir Apache Solr genauer angeschaut und darauf mittels PHP und JSON zugegriffen.

Kommandozeile

Da der Ubuntu-Server aus mir unerfindlichen Gründen mit LAMPP aufgesetzt wurde, befinden sich die Binaries wie php nicht in den Standardpfaden und werden von bash ohne absolute Pfadangabe nicht gefunden. Da man den Interpreter täglich dutzende, wenn nicht gar hunderte Male aufruft, ist es ratsam, das Verzeichnis sofort in die $PATH-Variable des Shells aufzunehmen.

Hier findet man sich in der Shell Startup File Hell wieder. Je nachdem, ob man lokal arbeitet oder sich per SSH einloggt muss der Befehl an einem anderen Ort stehen. Schlussendlich habe ich die nachfolgende Zeile …

...
PATH="$PATH:/opt/lampp/bin"

… ans Ende folgender zwei Dateien angefügt:

  • ~/.profile
  • ~/.bashrc

Datenbank (MySQL)

Cache aktivieren

Wir führen PHP-Scripts über die Linux-Shell aus. Gerade bei der Entwicklung neuer Scripts sind verschiedene Anläufe nötig, bis alle Bugs und nicht beabsichtigten Funktionen ausgemerzt sind. Da wir oftmals an einem Datenset von 60’000+ Seiten arbeiten, ist es unabdingbar, dass wir eine Cacheing-Lösung anwenden, um Datenbankabfragen im Kurzspeicher zwischenzulagern.

Folgende Parameter in my.cnf aktivieren den in MySQL vorhandenen Cache:

...
[mysqld]
...
query_cache_type = 1
query_cache_size = 512M
query_cache_limit = 32M

Der Geschwindigkeitsgewinn ist immens — nachdem ein Script mit SELECT-Statements zum ersten Mal ausgeführt wurde und dafür mehrere Minuten benötigte, rauscht es in den folgenden Malen innert Sekunden durch.

Programmierung (PHP und Python)

Helper-Funktionen und -Klassen

Wie aus der Web-Entwicklung gewohnt sollte man bei jedem der mit der Zeit entstehenden Scripts zu Beginn Klassen, allgemeine Einstellungen und Funktionen einbinden.

In der Funktionsbibliothek setze ich beispielsweise folgende wichtigen Parameter in globaler Form:

...
error_reporting(E_ALL);
date_default_timezone_set('Europe/Zurich');

Auch die Datenbankverbindung wird mittels meiner MySQL-Klasse hier erstellt und an alle Scripts weitergegeben, welche die Library einbinden.

Aufbau der Scripts

Als ich zum Projekt stiess herrschten Spaghetti-Code in teils monolithischen Scripts vor. Ich habe meine eigenen Scripts dann aber so entwickelt, dass sie dem Unix-Gedanken folgend normalerweise nur eine bestimmte Funktion ausführen, diese dafür aber ausgezeichnet und in sich abgeschlossen. So steht im Normalfall in jedem Script, welches Daten manipuliert, zuoberst ein SQL-Query, welches die zu verändernden Datenbankdaten auswählt.

Bei der Entwicklung wählt man hierbei ein Query, das einen oder nur wenige Werte aus der Datenbank ausliest und verarbeitet — in dieser Phase hat man keine Zeit, möglicherweise fehlerhafte Manipulationen an 60’000+ Seiten durchzuführen.

HTML mit regulären Ausdrücken parsen?

Nein, besonders nicht dann, wenn man konkret an Eigenschaften des SGML-Markup interessiert ist (bspw. Wohin zeigen Links?). Hierzu verwendet man die in PHP standardmässig enthaltene DOMDocument-Bibliothek.

Wichtig ist, dass man bei der Verwendung von E_ALL die loadHTML()-Funktion mittels @ stummschaltet, weil sonst das Terminal mit Warnungen über fehlerhaften HTML-Code (leider an der Tagesordnung) vollgespamt wird:

...
$dom = new DOMDocument();
@$dom->loadHTML($html);
$elements = $dom->getElementsByTagName('a');

HTML manipulieren — und Fallstricke

Für jede HTML-Seite erstellen wir eine Nur-Text-Version. Hierzu verwenden wir html2text.py von Aaron Swartz selig. Bevor das HTML aber umgewandelt wird, säubern wir die HTML-Datei auf eigene Faust. Auch hier kommt DOMDocument zum Zug.

Wir suchen dabei zuerst einmal Elemente jeglicher Art, deren ID oder Klasse den String nav, menu und breadcrumb enthält. Die Navigation interessiert uns nämlich nicht, und noch schlimmer: Sie verfälscht teilweise die Resultate, weil in der Navigation gesuchte Begriffe vorkommen.

Hierzu lade ich den HTML-Code wieder in eine DOMDocument und iteriere danach über alle Elemente auf der Suche nach den besagten IDs und Klassennamen:

function cleanNavMenuElements($html = null) {
		$dom = new DOMDocument();
		@$dom->loadHTML($html);
		
		$changesMade = false;
		
		$elements = $dom->getElementsByTagName('*');
		foreach($elements as $element) {
			if(preg_match('/^(html|body)$/',$element->nodeName)) {
				// Otherwise we might delete the whole DOM!
				continue;
			}
			
	        if($element->hasAttribute('class') && preg_match('/(nav|menu|breadcrumb)/i',$element->getAttribute('class')) > 0) {
				status('Found element with class ' . $element->getAttribute('class'));
				$element->parentNode->removeChild($element);
				
				$changesMade = true;
				
				// Don't go further if we removed this node already
				continue;
			}
			
			if($element->hasAttribute('id') && preg_match('/(nav|menu|breadcrumb)/i',$element->getAttribute('id')) > 0) {
				status('Found element with id ' . $element->getAttribute('id'));
				$element->parentNode->removeChild($element);
				
				$changesMade = true;
			}
	    }
		
		if(!$changesMade) {
			return $html;
		}
		
		return $dom->saveHTML();
	}

Ein Problem manifestiert sich bei der Manipulation aber: Jegliche Anpassungen erfolgen live, was verwirrende Folgen für Schleifen haben kann.

Da die Suche nach obigen ID- und Klassennamen nicht alle Navigationselemente eliminiert, suche ich in einem zweiten Anlauf Tabellen und Listen, deren Elemente ausschliesslich Links enthalten. Dies ist ein guter Indikator, ein Navigationsblock gefunden zu haben.

Hier ist das Problem des sich bei jeder Iteration veränderndem DOM aber sehr ausgeprägt. Wenn ich deshalb durch td-Elemente und li-Elemente iteriere, verwende ich nicht foreach() sondern eine for()-Schleife, deren Counter $i ich immer dann zurücksetze, wenn ich ein Element entferne. Ansonsten wird aus Erfahrung in der Folge eines (oder mehrere Elemente) übersprungen. Damit dies klappt, arbeite ich den DOM Tabellen- respektive Listenweise ab:

$containers = $dom->getElementsByTagName($containerTag);
	
foreach($containers as $container) {
	$items = $container->getElementsByTagName($itemTag);
	
	for($i = 0; $i < $items->length; $i++) {
		$item = $items->item($i);
		
		if($item === null) {
			continue;
		}
		
		$otherTagsPresent = false;
		foreach($item->childNodes as $child) {
			$tag = $child->nodeName;

			if($tag == '#text') {
				$text = trim($child->nodeValue);
				$len = strlen($text);
				if($len < 1) {
					//#text is empty, thus not relevant
					continue;
				}
			}

			if($tag != 'a') {
				$otherTagsPresent = true;
				continue;
			}
		}

		if(!$otherTagsPresent) {
			$item->parentNode->removeChild($item);
			$i = $i-1;
		}
	}

Weiterverarbeitung der Daten durch Nicht-IT-Profis

Zur Weiterverarbeitung der Auswertungen durch andere, in IT nicht versierte Mitarbeiter habe ich eine Funktion geschrieben, welche eine Pfadangabe sowie CSV-Daten als Argumente übertragen erhält. Die Datei wird geschrieben und gleich anschliessend mittels eines kleinen Python-Scripts (Stichwort: openpyxl) in das bei uns hauptsächlich verwendete XLSX-Format konvertiert.

Tags: , , , , , , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Montag, 29. Oktober 2012

MySQL, das Windows unter den Datenbanken

Obwohl ich seit 12 Jahren mit dieser Datenbank arbeite, habe ich mir noch nie Gedanken darüber gemacht, wie unkonform MySQL mit Daten umgeht:

Via: PostgreSQL Rising

Trotz all seinen Fehlern: MySQL reicht für die Bedürfnisse von mindestens 80 Prozent seiner Benutzer. Normalerweise verfrachte ich die Prüfhandlungen und die Logik in PHP-Scripte und verwende MySQL als “dummen” Datenspeicher. Doch in einigen Fällen habe ich mir doch einige MySQL-Funktionen und Feld-Attribute zu nutzen gemacht, um Fehleingaben auch auf Datenbankebene zu verhindern. Denn die Datenbank ist nun mal das letzte Bollwerk vor dem Datensalat.

PS: Wenn man schon den unangefochtenen Platzhirschen im Linux Shared Hosting-Bereich madig machen will, sollte man auf Comic Sans als Schrift verzichten. Helvetica oder Helvetica Neue gäbe dem Video mehr Seriosität.

Tags: , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 11. September 2011

MySQL meldet “Got error 28 from storage engine”

Tritt dieser Fehler in den Error-Logs auf, handelt es sich höchstwahrscheinlich um eine volle /tmp-Partition:

...
/dev/sda8             361M  359M     0 100% /tmp
...

Quelle: database error “Got error 28 from storage engine query: SELECT DISTINCT(p.perm)”

Tags:
Labels: Linux

Keine Kommentare | neuen Kommentar verfassen

Dienstag, 26. Oktober 2010

Darf ein Web-Entwickler seine geliebte Scripting-Sprache aufgeben?

How do you hire a programmer if you’re not one yourself? Some things to look for …

1. How opinionated are they?

Ask them about a juicy programming topic (e.g. Ruby or Python?). The tone and reasoning of the answer will reveal a lot. In our recent podcast on programming, Jeff said, “When people have strong opinions about things — when they can talk at length about something — it’s a good indication that they’re passionate about it.”

Quelle: How to hire a programmer when you’re not a programmer – (37signals)

Genau dies habe ich letzte Woche erlebt. Ich auf der Seite des Programmierers, auf der anderen Seite ein Headhunter, der für ein “internationales” Unternehmen in Zürich einen Web-Entwickler suchte. Er war über Xing an meine Kontaktangaben gelangt.

Auf die Frage, ob ich Erfahrung in ASP.NET hätte, erwiderte ich ein klares Nein, um anzufügen, dass ich das letzte Mal im Jahr 2000 ASP programmiert hätte. ASP war damals mein erster Einstieg in webbasierte Scriptingsprachen. Innert weniger Monate wurde ich dann aber äusserst rasch auf die gute Seite der Macht gezogen — und entwickelte fortan auf den LAMP-Stack aufbauend.

Der Headhunter hakte nach: Ob ich es mir denn vorstellen könne, ASP.NET zu erlernen? Darauf erwirderte ich ein klares und dezidiertes “Nein”. Ich, der Mac OS X/Linux-Fan, der plötzlich in Visual Studio rumeiert? Das wäre wie wenn ein Kommunist zur SD überlaufen würde. Oder ein Wechselstromverfechter ins Camp der Gleichstromfreaks übertreten würde.

Ich habe mich noch ein/zwei Male gefragt, ob ich wirklich die richtige Antwort gegeben habe — doch mit obiger Bemerkung von Seiten der Web-Entwicklerprofis bin ich ein für allemal sicher, dass ich mich richtig entschieden habe.

Tags: , , , , , , ,
Labels: IT, Linux, Web

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 22. September 2010

Höhere Datenbankkunde mit Facebook

“We need some way of applying the changes that we missed after the copy was started,” he said. OSC does so using database triggers. “When the copy finishing, we replay all of the changes that were logged by the triggers, and then we briefly – for a fraction of a second – block access to the table and then we switch the original table with the copy.”

Quelle: Facebook open sources live MySQL makeover • The Register

Jede populäre Web-Applikation kann an einen Punkt heranwachsen, bei dessen Überschreitung das mit den Datenbanken kein Spass mehr ist, sondern eine grosse, grosse Bürde. Inkrementelle Backups und Schema-Upgrades — halleluja. Aber meist gibt es halt keinen Weg drumherum.

Tags: , , ,
Labels: IT, Web

Keine Kommentare | neuen Kommentar verfassen

Dienstag, 18. Mai 2010

Thing und Data Tables

Instead, they keep a Thing Table and a Data Table. Everything in Reddit is a Thing: users, links, comments, subreddits, awards, etc. Things keep common attribute like up/down votes, a type, and creation date. The Data table has three columns: thing id, key, value. There’s a row for every attribute. There’s a row for title, url, author, spam votes, etc. When they add new features they didn’t have to worry about the database anymore. They didn’t have to add new tables for new things or worry about upgrades. Easier for development, deployment, maintenance.  The price is you can’t use cool relational features. There are no joins in the database and you must manually enforce consistency. No joins means it’s really easy to distribute data to different machines. You don’t have to worry about foreign keys are doing joins or how to split the data up. Worked out really well. Worries of using a relational database are a thing of the past.

Quelle: High Scalability – High Scalability – 7 Lessons Learned While Building Reddit to 270 Million Page Views a Month

Tags: , ,
Labels: IT, Web

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 11. Oktober 2009

Ist MySQL ein RDBMS?

Die Antwort eines Vollprofis – sowohl auf dem Gebiet von Datenbanken als auch der englischen Sprache:

My sql is a DBMS(database management system) that is basically in the tabular form .for example: MS excess, we use tables.
but RDBMS is in non tabular form …the general example is google i.e when we search something in google the data is displayed in non tabular form …so my sql is a DBMs consist of creating tables for making a database

Quelle: WikiAnswers – Is MySQL DBMS or RDBMS

Microsoft Excess? Das würde ich einfach schon nur kaufen, um es in mein Software-Regal zu stellen.

Tags: , ,
Labels: Funny, IT

Keine Kommentare | neuen Kommentar verfassen

Freitag, 7. November 2008

Datenbankdump von MySQL 5 nach MySQL 4

Den ursprünglichen Dump stellte ich mit phpMyAdmin her. Leider erlaubt die Web-Applikation nicht, den Charset des Dumps von UTF-8 nach LATIN1 (aka ISO-8859-1) anzupassen.

Deshalb ging es auf die Kommandozeile des Servers:

mysqldump --default-character-set=latin1 --compatible=mysql40 -u {user} -p {db} > dump.sql

Einerseits konnte ich so das Charset des Dumps festlegen (--default-character-set=latin1), andererseits musste ich aber auch auf die Kompatibilität achten (--compatible=mysql40). Leider war es mit letzterem Versprechen nicht wirklich weit her …

Auf Grund eines seit Monaten bestehenden Bugs in MySQL 5 enthielt der mysqldump-Dump Befehle, die MySQL 4.0.x nicht versteht:

SET @saved_cs_client     = @@character_set_client;
SET character_set_client = @saved_cs_client;

… was MySQL 4 folgende Fehlermeldung ausgeben liess:

#1193 - Unknown system variable 'character_set_client'  

Mittels vim (die GUI-Editoren unter Mac OS X mit Syntax-Highlighting kapitulierten vor 4000+ Zeilen) konnte ich den Dump dann doch noch derart zurechtbiegen, dass MySQL 4 die Datei schlussendlich schluckte. Der Befehl dazu lautete:

:g/^SET/d

Via: Vim: Delete every line in the file that does not match a pattern

Dieser vim-Befehl löscht kurzerhand alle Zeilen, die mit SET ... beginnen. Scheint dem Import nicht geschadet zu haben …

Tags:
Labels: IT, Linux

Keine Kommentare | neuen Kommentar verfassen

Freitag, 22. August 2008

Wenn MySQL unter Mac OS X nicht automatisch startet

Ich habe seit längerem das offizielle MySQL 5.0.45 auf meinem MacBook (Intel mit Mac OS X 10.4.11) installiert. Alles wunderbar – doch bis zum heutigen Tage wurde MySQL bei einem (Re-)Boot nicht automatisch gestartet. Ich musste mich dann immer mühsam zum Preference Pane durchhangeln und dort auf “Start MySQL Server” klicken (für einmal per GUI, nicht per CLI).

Heute habe ich mir nun zur Aufgabe gesetzt, dieses Problem zu beheben und kann folgenden Lösungsweg aufzeichnen:

  1. Download der neuesten MySQL-Version bei SWITCH: mysql-5.0.67-osx10.4-i686.dmg
  2. Mounten des Disk Images
  3. Doppelklick auf MySQLStartupItem.pkg
  4. # rm -rf /Library/PreferencePanes/MySQL.prefPane
  5. Download des Ersatzes von MySQL.prefPane-leopardfix.zip
  6. Installation des neuen Preference Panes mittels Doppelklick auf MySQL.prefPane

Seither läuft MySQL bei jedem (Neu-)Start.

Via: Bug-Report mit Erläuterungen zum Problem

Tags: , ,
Labels: Allgemein

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 2. Juli 2008

Hostpoint-Problem des Monats: Zeichensalat

Kein Monat vergeht, in dem Hostpoint nicht eine Überraschung parat hat. Während ich im Juni das erste Mal seit langem etwas Positives berichten durfte, war klar, dass im Juli garantiert wieder etwas kaputt gehen musste.

Und tatsächlich: Heute erhalte ich ein Mail eines Kunden, der über komische dargestellte Sonderzeichen flucht. Nach dem ich die Homepage angesurft habe, kann ich das Problem bestätigen: Irgendwie scheinen da UTF-8 und ISO-8859-1 durcheinander gekommen zu sein. Seit sechs Jahren hat die Web-Site keine Probleme mit Zeichensätzen aufgewiesen, doch nun ist über Nacht wohl etwas “kaputt” gegangen.

Soweit ich erkennen konnte, liegt das Problem darin begründet, dass mysql_query() neu nicht mehr ISO-8859-1-kodierte Zeichensätze zurückliefert, sondern UTF-8. Das HTML-Dokument sagt von sich aber, dass es in ISO-8859-1 kodiert ist – und htmlentities() erwartet auch ISO-8859-1. Ah, und die Tabellen-Spalten weisen ebenfalls latin1_german1_ci als Kodierung auf (jedenfalls sagt mir das phpMyAdmin so).

Temporärer Workaround

mysql_query("SET NAMES latin1");

… zuoberst in der index.php (natürlich nach dem Initialisieren der Datenbankverbindung!)

Jetzt klappt es wieder mit den Zeichensätzen.

Mal schauen, was sich Hostpoint für den kommenden Monat einfallen lässt.

Tags: , , ,
Labels: Schweiz

Keine Kommentare | neuen Kommentar verfassen