Ich wollte nur [...] und dann ist das Universum explodiert.

Projects
Books
Archive
About









    Permalink
  1. MySQL | Table Migration zu InnoDB

    Ich durfte letztens einer schönen Schulung zum Thema MySQL lauschen. Dabei kam viel herrum. Ein Teil davon hat sich mit verschiedenen Storage Engines beschäftigt.

    Ich habe mich entschieden den Großteil meiner Datenbanken zu InnoDB umzuwandeln. Welche Vor- und Nachteile das hatt sollte sich jeder vorher klarmachen. Stichwort: ACID und Fulltext-Search. Natürlich könnte man einfach mit ALTER arbeiten. Aber ich wollte nicht umbedingt die alten MyISAM Tables noch im FS liegen haben. Also alle Datenbanken droppen und den geänderten Dump wieder einspielen, sodass die Tables neu (mit InnoDB) angelegt werden.

    Zuersteinmal eine Liste mit den generieren mit den Datenbanken die man Bearbeiten möchte. Es ist zu beachten, dass man den mysql Table selbst nicht auf InnoDB umstellen möchte.

    mysql -u root -p -e "show databases;" -N --batch | grep -v ^information_schema$ | grep -v ^mysql$

    Die Liste der Datenbanken wird nachher noch hilfreich sein. Danach will man wahrscheinlich erstmal alle Dienste beenden, die auf dem MySQL zugreifen (Apache, Tomcat, whatever). In der my.cnf habe ich dann folgende Optionen für die InnoDB spezifiziert.

    defaulot-storage-engine = InnoDB
    innodb_buffer_pool_size = 16M
    innodb_additional_mem_pool_size = 2M
    innodb_log_file_size = 5M
    innodb_log_buffer_size = 8M
    innodb_flush_log_at_trx_commit = 1
    innodb_lock_wait_timeout = 50

    Anschliessend den Dump erstellen und alle ENGINE=MyISAM durch InnoDB ersetzen:

    mysqldump -u root -p > all-databases.sql
    sed -i -e 's#ENGINE=MyISAM#ENGINE=InnoDB#g' all-databases.sql

    Vorsicht. Hier ist mysql als Datenbank mit gedumped! Mir ist dabei keine wirklich einfache Zeile eingefallen die mit Suche/Ersetze Spielchen mysql ausschliesst. Es gibt bei mysqldump die Option “–ignore-table=” aber auch hier hätte ich jeden mysql Table einzeln nennen müssen. Ich hab die Datenbank dann einfach per hand aus dem Dump herausgelöscht.

    Außerdem sollte man seinen Datenbank Dump nach FULLTEXT durchsuchen, da dieser von InnoDB nicht unterstützt wird. In meinem Fall hat es nur ein altes Forum das niemand mehr benutzt betroffen, weshalb ich die Zeile einfach löschen konnte.

    grep "FULLTEXT" all-databases.sql

    Um jetzt alle Datenbanken zu droppen hab ich mir folgende Line gebastelt:

    for x in $(mysql -u root -phierstehteinpasswort -e "show databases;" -N --batch | grep -v ^information_schema | grep -v ^mysql$) ; do mysql -u root -phierstehteinpasswort -e "drop database $x ; " --batch ; done

    Nach der Bearbeitung kann man den Dump mit der neuen Engine für die Tables wieder einspielen:

    mysql -u root -p < all-databases.sql


  2. Permalink
  3. Statistiken | Einfache Graphen mit R und MySQL Anbindung

    Immer mal wieder reizt mich die Programmiersprache für Statistiken R. Um diesen Reiz dann auszuleben hab ich vor ein paar Monaten angefangen kleine Graphen für den zufallsbasierten Simulator ZRE zu bauen. Das Spiel “läuft” einfach 24/7 und schreibt für jedes geschehene Event Einträge in die Datenbank. Diese Einträge werte ich dann mit Hilfe von R aus.

    Dazu gibt es ein Skript. Nämlich zre.R (Ob das die Konvention bei R-Skriptnamen ist, kann ich nicht sagen ;) )

    #!/usr/bin/env Rscript
    
    ### General R-Script
    # MySQL
    library(RMySQL)
    con <- dbConnect(MySQL(), user="", password="", host="", client.flag=CLIENT_MULTI_RESULTS)
    # Style
    zre_colors <- colors()[grep("green",colors())]
    zre_mint <- colors()[c(48,86,50)]

    Im Klartext wird aus dem CRAN Library Verzeichnis die Library RMySQL includiert und die Verbindung in der Variable con abgelegt. Ähnlich wie bei PHP. Für alle Debian / Ubuntu Benutzer empfiehlt sich aber, die Library einfach über das Paketsystem nachzuinstallieren.

    $ aptitude install r-cran-rmysql

    Standardmäßig sehen Graphen die mit R erstellt werden ziemlich mau aus. Die weiteren Variablen unter Style habe ich gewählt um mir die Colorierung der Graphen etwas zu erleichtern. Diese werden später einfach als Attribute in den Plots/Barplots gesetzt und ausgewertet. Ich fange einfach mal der Reihe nach an:

    Die Abfolge ist immer ziemlich ähnlich. Zu aller Erst wird der Query für die Datenbank an die Variable sql übergeben. Diese Variable wiederrum wird zusammen mit der Connection an die Funktion dbGetQuery übergeben und das Ergebnis dessen schliesslich in zre_wins gespeichert. Anschliessend ein paar kleine Informationen an das Dateiformat übergeben und den Graphen bauen.

    Die Funktion par lässt sich erstmal als eine Art Environment Funktion für Graphen verstehen. Hier werden Eigenschaften wie Schriftfarbe, Hintergrund, Axenfarbe, und Liniendicke definiert. Danach kommt (wie ich finde) der schwierigste Teil. Bauen des Graphen. Je nach Art des Graphen (Balken, Linien, Torte u.ä.) werden logischerweise verschiedene Parameter erwartet. Die Daten werden hierbei jetzt als Matrix an ein Balkendiagram übergeben. Weitere Informationen wie die Überschrift (main) und die Beschreibung der Balken (names.arg) werden einfach angefügt.

    # Graph: Wins

    sql <- paste("SELECT COUNT(id) AS sum, side FROM zombies.zre_wins GROUP BY side;")
    zre_wins <- dbGetQuery(con, sql)
    png(file="wins.png", width=400, height=400)
    par(col="white", bg="transparent", col.axis="white", col.lab="white", col.main="white", lwd=2)
    barplot(as.matrix(zre_wins$sum), main="Game Summary", names.arg=c(zre_wins$side), beside=TRUE, col=zre_mint)

    Selbes Spiel wieder, nur mit mehr Balken und anderem Use-Case. Diesmal werden die 25 Konflikte mit den meisten Opfern visualisiert.

    # Graph: Highest Kills

    sql <- paste("SELECT id, kills FROM zombies.zre_kills ORDER BY kills DESC LIMIT 25;")
    zre_matches_highest <- dbGetQuery(con,sql)
    png(file="highestkills.png", width = 400, height = 400, bg="transparent")
    par(col="white", bg="transparent", col.axis="white", col.lab="white", col.main="white", lwd=4)
    barplot(zre_matches_highest$kills, zre_matches_highest$id, main="25 Highest Kills", beside = TRUE, ylab="Kills", col=zre_colors)

    Aber da Balkendiagramme auch irgendwann Langweilig werden geht das natürlich auch anders. Die 25 letzten Konflikte werden im “Opferverlauf” wie folgt dargestellt:

    # Graph: Kills

    sql <- paste("SELECT kills FROM zombies.zre_kills ORDER BY id DESC limit 25;")
    zre_kills <- dbGetQuery(con,sql)
    yrange <- range(zre_kills$kills)
    xrange <- length(zre_kills$kills)
    png(file="kills.png", width=400, height=400, bg="transparent")
    par(col="white", bg="transparent", col.axis="white", col.lab="white", col.main="white", lwd=4)
    plot(zre_kills$kills, xlab="Games", type="b", ylab="Kills", main="Kills from last 25 Attacks", col=zre_mint)

    Damit es nicht immer nur um Tote geht, auch mal was erfreuliches. Die Geburtenrate in ZRE steigt! :)

    # Graph: BirthRate

    sql <- paste("SELECT Month(date) AS month, count(id) AS born FROM (SELECT *, Month(date) AS M FROM zombies.zre_born) t Group by M; ")
    zre_birthrate <- dbGetQuery(con,sql)
    png(file="birthrate.png", width=400, height=400)
    par(col="white", bg="transparent", col.axis="white", col.lab="white", col.main="white", lwd=3)
    barplot(zre_birthrate$born, xlab="Month", ylab="Born Humans/Zombies", names.arg=c(zre_birthrate$month),main="BirthRate per Month", col=zre_colors)

    Und auch das Wetter soll bei der ganzen Sache nicht zu kurz kommen. Hierbei bitte besonderes Augenmerk auf die Legende rechts oben. Eine direkte Zuordnung der Werte und Farben ist nicht nötig, da die Farben in der selben Reihenfolge von zre_colors befüllt werden wie die Balken. Die erschreckend hohe Zahl an Naturkatastrophen erklärt das aber trotzdem nicht :)

    # Graph: Weather
    sql <- paste("SELECT COUNT(id) AS count, weather FROM zombies.zre_weather GROUP BY weather ORDER BY count DESC;")
    zre_weather <- dbGetQuery(con,sql)
    png(file="weather.png", width=400, height=400)
    par(col="white", bg="transparent", col.axis="white", col.lab="white", col.main="white", lwd=2)
    barplot(zre_weather[,1], main="Weather in ZRE", beside = TRUE, col=zre_colors)
    legend( 5, 40000, zre_weather$weather, cex=0.9, fill=zre_colors, col="white")

    Das volle zre.R Skript befindet sich wie das meiste auf Github: https://gist.github.com/1031260


  4. Permalink
  5. PostgreSQL | 1000 und 1 Query

    Zur Zeit spiele und bastle ich nebenher mit PostgreSQL rum. Überlege ob ich mal eine alternative DB für das Zombie-Revolution-Environment an den Start bringe…

    Für meinen Use-Case scheint das allerdings nur begrenzt von Nutzen zu sein. Ich mache vielleicht etwas falsch, aber wenn ich 1000 Queries in MySQL kippe, dauert nur einen Bruchteil so lange wie in postgreSQL. Um das zu veranschaulichen:

    $ time for x in $(seq 1 1000) ; do mysql -u root -ppw -e "insert into foobar.foo values ($x, now());" ; done
    real 0m7.349s
    user 0m0.060s
    sys 0m0.380s

    $ time for x in $(seq 1 1000) ; do psql --quiet -d foobar -c "insert into foobar values ($x, now());" ; done
    real 1m28.363s
    user 0m37.450s
    sys 0m13.020s

    Kann mir jemand sagen woran das liegt? Ich kann mir nur schwer vorstellen das PostgreSQL so hinterher hinkt.


  6. Permalink
  7. Shell-Zauberei | Namensliste in MySQL Datenbank einspeisen

    Code
    for x in $(mysql --batch -u root --password=passw0rd -e "USE rtdb; SELECT DISTINCT id FROM Users;" | grep -v ^id); do mysql -u root --password=passw0rd -e "USE rtdb; UPDATE Users SET Name=\"$(sed -n $(($RANDOM % $(cat Names | wc -l) +1 ))p Names)\" WHERE id=\"$x\" ;" ; done

    Hintergrund
    Wie auch schon beim letzten mal dreht es sich wieder um die Anonymisierung der RequestTracker Datenbank für die ich zur Zeit an einem Statistik Tool arbeite. Diesmal will ich aber nicht die EmailAdressen ändern, sondern die Namen der Benutzer. Da ich diese nicht so einfach generieren lassen kann, habe ich mir aus dem Interweb eine Liste mit Namen besorgt und mit diesen Namen die eingetragenen überschrieben. Jetzt kann ich endlich den Post über das Statistik Tool schreiben und mit Beispielen versehen :)

    Funktion
    Das Ganze läuft wie folgt ab: Für jede ID die ich mittels Datenbank-Verbindung in die For-Schleife einbette, setze ich einen UPDATE Befehl ab, der die Tabelle “Users” und das Feld “Name” aktualisiert. Der Aktualisierungsvorgang passiert aber generisch. Das heisst ich setze den Namen des Users auf einen zufällig ausgewählten neuen Namen aus der Datei “Names”. Das habe ich mit sed -n p FILE gelöst, was sicher auch schöner geht, aber für meine Zwecke hat es ausgereicht.

    About: “Ein Byte der Shellzauberei” – Kategorie: http://noqqe.de/archives/category/shell-zauberei


  8. Permalink
  9. ZRE | SQL Statistik Modul

    Ich möchte kurz ein Wort über das SQL Modul verlieren, welches die Statistiken des permanent laufenden Zombie Environments aufzeichnet. Wie mit Sicherheit schon überall auf dem Planeten Erde bekannt ist, passieren in ZRE Events. Diese Hand voll Events lösen (wie der Name vielleicht schon suggeriert) Geschehnisse in innerhalb des Environments aus, welche es gilt statistisch auszuwerten und darzustellen.

    Han Solo & Chewbacca

    Wie aber erfasse ich solche Informationen am Besten. Ich hatte ehrlich gesagt keine große Lust, in jedes der Events einzeln eine SQL-Verbindung aufzubauen und den gewünschten Query abzusetzen. Ganz zu schweigen von der Problemfallbehandlung (MySQL nicht installiert, Modul in Config nicht aktiviert, MySQL nicht erreichbar usw.) die ich in jedem Event hätte extra behandeln müssen.

    events/
    ├── attack.humans.event
    ├── attack.zombies.event
    ├── born.humans.event
    ├── born.zombies.event
    ├── building.humans.event
    ├── collecting.zombies.event
    ├── die.humans.event
    ├── die.zombies.event
    ├── infos.event
    ├── stats.humans.event
    ├── stats.zombies.event
    ├── support.humans.event
    ├── support.zombies.event
    └── weather.event

    Stattdessen entschied ich mich ein kleines Modul in Bash zu schreiben, welches eine Hand voll übergebener Daten annimmt und dann in die konfigurierte Datenbank schreibt. Hier am Beispiel des weather.event

    case $nature_msg in
    1) echo "A volcano explodes." ; zresql weather vulcano ;;
    2) echo "An earthquake hits the city." ; zresql weather earthquake ;;
    3) echo "A hurricane hits the city." ; zresql weather hurricane ;;
    esac

    Als erstes übergeben wird immer der gewünschte Modus, in den zresql den Datensatz einordnen soll, danach alle weiteren Informationen die der Struktur nach erforderlich sind.

    # Hurricane
    $ zresql weather hurricane
    # Humans gewinnen einen Kampf und vernichten 643 Zombies
    $ zresql kill human 643

    Das zresql Modul nimmt die Informationen dann entgegen und leitet es unter den gegebenen Umständen via sqlsend an die Datenbank weiter.

    [...]
    weather) sqlsend "INSERT INTO zre_weather VALUES (NULL, \"$2\", CURRENT_TIMESTAMP);" ;;
    kill) sqlsend "INSERT INTO zre_kills VALUES (NULL , \"$2\", \"$3\", CURRENT_TIMESTAMP);" ;;
    [...]

    sqlsend ist im Grunde nur eine weitere kleine Funktion die direkt über Kommandozeile den SQL Query abschickt.

    sqlsend() {
    mysql -e "use $sqldb; $1" --user=$sqluser --password=$sqlpw --host=$sqlhost
    }

    Mit dieser Lösung bin ich eigentlich relativ zufrieden. Über Verbesserungsvorschläge und Kritik freue ich mich natürlich wie immer. Das ganze Modul ist zusehen auf Github: https://github.com/noqqe/zombie-revolution-environment/blob/master/lib/sqlstats.library.bash


  10. Permalink
  11. Charset | UTF8 für Apache, PHP, MySQL, Debian und WordPress

    Nachdem ich die Migration meines Blogs auf meinen neues Stück Blech größtenteils abgeschlossen hatte, wurde ich wieder an den Charset Wirr-Warr von IT-Systemen erinnert. Um meinem Blog seine Umlaute wieder zu beschaffen habe ich folgende Änderungen an verschiedenen Stellen eingespielt. Vorzugsweise immer in den entsprechenden conf.d/ Verzeichnissen, da die Änderungen evtl. beim nächsten Upgrade überschrieben werden könnten.



    Apache2 Charset
    vim /etc/apache2/conf.d/charset
    AddDefaultCharset UTF-8

    PHP5 Charset
    $ vim /etc/php5/apache2/conf.d/charset.ini
    [PHP]
    default_charset = "utf-8"
    [mbstring]
    mbstring.language = utf-8
    mbstring.internal_encoding = utf-8
    mbstring.http_input = utf-8
    mbstring.http_output = utf-8

    MySQL Charset
    $ vim /etc/mysql/conf.d/character
    [client]
    default-character-set = utf8
    [mysqld]
    default-character-set = utf8
    character-set-server = utf8
    collation-server= utf8_general_ci
    init_connect = ‘SET collation_connection = utf8_general_ci’
    init_connect = ‘SET NAMES utf8′
    [mysqldump]
    default-character-set = utf8
    [mysqlimport]
    default-character-set = utf8
    [mysql]
    default-character-set = utf8

    Debian WordPress Config
    $ vim /etc/wordpress/config-blog.url.php
    define('DB_CHARSET', 'utf8');
    define('DB_COLLATE', '');
    define('WPLANG', 'de_DE.UTF-8');

    Debian Locales
    $ dpkg-reconfigure locales

    Sollte ich es mal wieder brauchen, les ich hier nach.


  12. Permalink
  13. MySQL | Datenbanken einzeln sichern

    Bis vor kurzem reichte mir ein volles MySQL-Backup der alle DB’s komplett in ein File gesichert hat. Eine Zeile CronJob.
    mysqldump -u root --password=x --all-databases > /pfad/$(date +%Y-%m-%d).sql

    Das funktioniert so lange, bis einmal recovered werden muss. Alle DB’s neu einspielen ist dann doch irgendwie kein Spass. Weder von der Dauer noch vom Datenverlust. 25 MB sind jetzt zwar nicht die Welt, aber trotzdem doof wenn etwas verloren geht. Deshalb kombinierte ich mein Voll-Backup mit einem File pro DB. Folgendes Script dient dazu:

    #!/bin/bash
    pass=
    backuppath=/var/cache/mysqlbackups
    
    mysqldump --password=$pass --all-databases > ${backuppath}/$(date +%Y-%m-%d).sql
    return1=$?
    
    for x in $(mysql --password=$pass -Bse 'show databases'); do
    mysqldump --password=$pass $x > ${backuppath}/$(date +%Y-%m-%d)-${x}.sql
    done
    return2=$?
    
    if [ $return1 -eq 0 ] && [ $return2 -eq 0 ]; then
    logger -p local0.info -t MYSQLBACKUP MySQL Backup successful
    else
    logger -p local0.err -t MYSQLBACKUP MySQL Backup failed
    fi
    

    Ausschlaggebender Teil ist die for-Schleife. Für jede Zeile Output von “show databases” wird ein seperates .sql File erstellt. Versehen mit Datum und DB-Name. Nebenbei wird auch noch via logger in /var/log/syslog geloggt.


  14. Permalink
  15. Mail | Postfix-Aliases mit MySQL-Backend erstellen

    Ich registrierte mich vor kurzem wiedermal bei einem etwas zwielichtigem Portal. Keine begründete Behauptung, es schien mir aber trotz allem so vorzukommen. Wie üblich loggte ich mich in meinen PHPMyAdmin ein und erstellte (um SpamEmails vorzubeugen) mithilfe meines MySQL-Backends von Postfix einen Alias. In einer Tabelle gesammelt liegen sämtliche aliase und deren Empfänger-Postfach.

    address | goto
    ubuntu@zwetschge.org | mail@zwetschge.org
    spam2@zwetschge.org | mail@zwetschge.org
    spam3@zwetschge.org | mail@zwetschge.org
    spam4@zwetschge.org | mail@zwetschge.org

    Es mag jetzt mit Sicherheit User geben die MySQL mit Postfix für unnötig halten, da die Steuerung über ConfigFiles ausreicht. Auf kurz oder lang gefällt mir die MySQL einfach besser. Einfach zuhandhaben. Flexibel. Schön. Das ständige eingelogge in HTpasswd, phpmyadmin-login und herumgeklicke war mir grad nur etwas zu blöd. Ich wollte ein kleines Skript basteln das mir das adden von Aliasen per CLI ermöglicht. In etwa so:

    aliasadd <alias> <recepient>

    Via echo lässt sich mysql (nach Authentifizierung) einen Befehl übergeben:

    echo "use maildb; insert into aliases values ('$1', '$2');" | mysql -u <user> --password=<pass>

    Damit wäre auch schon das gröbste geschafft. Zumindest das Einfügen. Ein Skript zeichnet allerdings mehr aus als nur die Aufgabe die es erledigen soll. Ein Skript muss zuverlässlich sicherstellen das die Aufgabe ausgeführt wurde und dies dem Benutzer nach Möglichkeit auch noch mitteilen.

    echo "use maildb; select * from aliases where address = '$1';" | mysql -u <user> --password=<pass>

    Die vorherige Zeile sieht eigentlich nur nach ob der eingegeben Alias wirklich in der Datenbank vorkommt. Freilich(wer findet ‘freilich’ eigentlich noch seltsam in Sätzen?) könnte ich jetzt noch nach Rückgabewerten mit $? Abfragen und ähnliche if-Vorraussetzungen einbauen. Aber für die 4-5 mal im Monat in denen ich es benutze wäre das übertrieben.

    Fertig sieht das ganze dann wie folgt aus:

    #!/bin/bash
    echo "use maildb; insert into aliases values ('$1', '$2');" | mysql -u <user> --password=<pass>
    echo "use maildb; select * from aliases where address = '$1';" | mysql -u <user> --password=<pass>

    Bildschirmfoto 2010-01-09 um 12.12.35

    nochmal als Plaintext:
    http://zwetschge.org/paste/6


  16. Permalink
  17. A-byte-of-MySQL. Eine Kurzreferenz

    Da diese Referenzen-Sache beim letzten mal mit IPv6 schon so gut geklappt hat, hab ich für die morgige Software-Arbeit über MySQL wieder eine Kurzzusammenfassung geschrieben und in Publications veröffentlicht. Sozusagen ein kurzes how to für mysql. Viel Spaß beim lesen.

    lernend,
    Flo