SWick

Sysadmin-by-Nature

Entries tagged "db".

Python script for Nagios to monitor SQL Server 2005 database mirroring
16th March 2009

Python Port of a Perl script I uploaded to NagiosExchange last year.

The script should be run on the PRINCIPAL with a read-only user. If you want to run it on the MIRROR, the user must have the Sysadmin role on it (ask Microsoft for the reason). Otherwise you get NULL.

You have to install the module pymssql manually if it's not shipped with your distro.

#!/usr/bin/python

import optparse
import pymssql
import sys

def main():

    #Connect to MSSQL Server
    try:
        con = pymssql.connect(host=host, user=user, password=password, database=database)
        cur = con.cursor()

    except TypeError:
        print 
        print "Could not connect to SQL Server"
        print 
        sys.exit(1)

    # Execute Query which checks if database is mirrored
    query="""SELECT d.name, m.mirroring_role_desc, m.mirroring_state_desc
             FROM sys.database_mirroring m
             JOIN sys.databases d ON m.database_id = d.database_id
             WHERE mirroring_state_desc IS NOT NULL AND name = """ + "'" + database + "'"

    cur.execute(query)

    results = cur.fetchall()

    for row in results:
        name  = row[0]
        role  = row[1]
        state = row[2]

    exit_val = 2

    if cur.rowcount > 0:
        if (role == "PRINCIPAL") and (state == "SYNCHRONIZED"):
            exit_val = 0

    if exit_val == 0:
        print "OK", "-", name, "-", role, "-", state
    else:
        print "CRITICAL - Check the mirrored database"

    con.close()


if __name__ == "__main__":

    # Command line Options
    parser = optparse.OptionParser()

    parser.add_option("-H", "--host",     dest="host",     metavar="HOST", help="IP or hostname with the mirrored database")
    parser.add_option("-d", "--database", dest="database", metavar="DB",   help="Name of the mirrored database")
    parser.add_option("-u", "--user",     dest="user",     metavar="USER", help="User to login")
    parser.add_option("-p", "--password", dest="password", metavar="PW",   help="Password of the user")

    if (len(sys.argv) < 2):
        args=["-h"]
        (options, args) = parser.parse_args(args)

    (options, args) = parser.parse_args()

    host     = options.host
    user     = options.user
    password = options.password
    database = options.database

    # Main function 
    main()
Tags: db, monitoring, mssql, python.
Automatisches Backup von gespiegelten MS-SQL 2005 Datenbanken
15th April 2009

Problem

In einem MS-SQL 2005 Mirroring Setup können Datenbanken auf dem MIRROR nicht angesprochen werden und somit kann ein Backup nur auf dem PRINCIPAL stattfinden. Ein Job im SQL-Server-Agent würde bei einem Failover nicht mehr funktionieren.

Lösung A

If Exists (Select * from sys.database_mirroring where database_id=yourdbid and mirroring_role=1)
Backup database dbname to disk='Path\filename.bak' With INIT

Lösung B

Macht ein Backup aller Datenbanken außer den Systemdatenbanken und Datenbanken, die nicht angesprochen werden können.

DECLARE @DBName varchar(255)

DECLARE @DATABASES_Fetch int

DECLARE DATABASES_CURSOR CURSOR FOR
    select
        DATABASE_NAME   = db_name(s_mf.database_id)
    from
        sys.master_files s_mf
    where
       -- ONLINE
        s_mf.state = 0 

       -- Only look at databases to which we have access
    and has_dbaccess(db_name(s_mf.database_id)) = 1 

        -- Not master, tempdb or model
    and db_name(s_mf.database_id) not in ('Master','tempdb','model')
    group by s_mf.database_id
    order by 1

OPEN DATABASES_CURSOR

FETCH NEXT FROM DATABASES_CURSOR INTO @DBName

WHILE @@FETCH_STATUS = 0
BEGIN
    declare @DBFileName varchar(256)    
    set @DBFileName = datename(dw, getdate()) + ' - ' + 
                       replace(replace(@DBName,':','_'),'\','_')

    exec ('BACKUP DATABASE [' + @DBName + '] TO  DISK = N''c:\db backup\' + 
        @DBFileName + ''' WITH NOFORMAT, INIT,  NAME = N''' + 
        @DBName + '-Full Database Backup'', SKIP, NOREWIND, NOUNLOAD,  STATS = 100')

    FETCH NEXT FROM DATABASES_CURSOR INTO @DBName
END

CLOSE DATABASES_CURSOR
DEALLOCATE DATABASES_CURSOR

Hier macht es vor allem die Funktion has_dbaccess möglich, dass der Job auch auf dem MIRROR laufen kann und das Backup nach einem Failover weiter funktioniert.

Tags: backup, db, mssql, tsql.
Dokumente in CouchDB wieder herstellen
2nd January 2010

Das Dateiformat bei CouchDB ist append-only, d.h. Veränderungen an der Datenbank ändern niemals bestehende Daten, sondern es wird immer am Ende angehängt. Das hat den Vorteil, dass bei einem Absturz o.ä., Daten nicht korrupt gehen können, keine langen Konsistenz-Checks beim Starten durchgeführt werden müssen und kein Locking stattfindet und dadurch extrem skalierbar ist.

Aber wie werden dann Daten in CouchDB gelöscht?

Beim Löschen eines Dokuments (Datensatz) wird dieses mit dem zusätzlichen Attribut _deleted und dem Wert true an die Datenbank angehängt. Da bei jeder Änderung eines Dokuments sich die sogenannte Revision ID ändert, erhält man auch beim Löschen eines Dokuments eine neue Revision ID. Dieses neue (angehängte) Dokument ist jedoch nur noch ein stub (Stumpf). D.h. es beinhaltet (fast) nichts mehr von seinen ursprünglichen Daten.

Einzig die Attribute ID, Revision ID und _deleted sind erhalten. Endgültig gelöscht werden Daten erst, wenn man eine Datenbank in CouchDB compacted. Da wird dann altes Zeug ausgemistet.

Das bedeutet, man kann auf gelöschte Dokumente weiterhin zugreifen, wenn man vorherige Revision IDs verwendet und kann damit z.B. Datensätze wieder herstellen. Da die letzte Revision ja nur noch ein Stumpf ist, muss also eine vorherige verwendet verwenden und es darf vorher solange NICHT compacted werden. Erst nachdem der Un-Delete durchgeführt wurde, darf man compacten. Ansonsten sind die Daten weg...

Details auf der Mailing Liste

Hier mal ein Beispiel, wie man das mit dem Python Modul für CouchDB macht. Hilfe dazu bekam ich auf der Mailingliste. Das Anzeigen von Dokumenten hab ich hier noch mit curl in einer Shell gemacht, damit es anschaulicher ist. Lässt sich aber auch direkt mit dem Python Modul machen. Testumgebung ist eine Ubuntu Karmic Installation, die mit CouchDB 0.10 und CouchDB-Python 0.6 daherkommt...

Anlegen einer Datenbank und Dokument erstellen

import couchdb
server = couchdb.Server()
db = server.create('testdb')
db['somedoc'] = {'type': 'Subtitle'}

Dokument anzeigen

curl -X GET 127.0.0.1:5984/testdb/somedoc
{"_id":"somedoc","_rev":"1-1d0d2e2f53eebed9dc22b38e05f7b585","type":"Subtitle"}

Dokument löschen

curl -X DELETE 127.0.0.1:5984/testdb/somedoc?rev="1-1d0d2e2f53eebed9dc22b38e05f7b585"
{"ok":true,"id":"somedoc","rev":"2-3369b4ffb9474a2ae9c22beb238a53a4"}

Wie man sieht, sagt CouchDB mit "ok":true, dass das Dokument gelöscht wurde und auch eine neue Revision ID bekommen hat.

Alte Revisionen anschauen

curl -X GET 127.0.0.1:5984/testdb/somedoc?rev=2-3369b4ffb9474a2ae9c22beb238a53a4
{"_id":"somedoc","_rev":"2-3369b4ffb9474a2ae9c22beb238a53a4","_deleted":true}

Na, etwas aufgefallen? Unser Attribut "type":"Subtitle" fehlt. Das ist der Stumpf, den ich zuvor angesprochen habe. Um eine vorherige Version mit allen Daten zu erhalten, dürfen wir nicht die letzte Revision ID nehmen, sondern eine davor.

curl -X GET 127.0.0.1:5984/testdb/somedoc?rev=1-1d0d2e2f53eebed9dc22b38e05f7b585
{"_id":"somedoc","_rev":"1-1d0d2e2f53eebed9dc22b38e05f7b585","type":"Subtitle"}

Tadaa. Unser altes Dokument. Um es wieder herzustellen, müssen wir diese Revision ID nutzen.

Dokument wieder herstellen

doc = db.get('somedoc', rev='1-1d0d2e2f53eebed9dc22b38e05f7b585')
db[doc.id] = doc

Mal sehen, was passiert ist:

curl -X GET 127.0.0.1:5984/testdb/somedoc
{"_id":"somedoc","_rev":"2-489230fd027dd8b7a1f96d1a0bd3928a","type":"Subtitle"}

Und siehe da. Unser Dokument ist wieder da. Und zwar erneut mit einer neuen Revision ID...

Tags: couchdb, db, nosql, python.
Scaling
12th February 2011

Sehenswertes Video zum Thema Skalierung.

Zu Beginn ist es eigentlich eine Einführung zu memcached, jedoch gehen sie zwischendrin sehr ausführlich auf NoSQL sowie die Probleme relationaler Datenbanken ein.

Very nice video for quick but comprehensible introduction to NoSQL

Tags: db, nosql.
recutils im LinuxUser Magazin
26th December 2011

Im LinuxUser Magazin ist ein Artikel zu recutils erschienen:

Die Beispiele stammen aus dem Debian Paket.

Im Artikel gehen sie leider nicht weiter auf das Encryption Feature ein, das nach einem Feature Request in die 1.4 eingeflossen ist.

Die 1.4 ist mittlerweile in Debian Testing und für Arch gibt es auch schon ein PKGBUILD im AUR.

Die 1.5 ist für Januar angekündigt...

Tags: db, nosql, recutils.
MongoDB - UND-Verknüpfung bei Arrays
24th May 2012

Queries in MongoDB machen default eine UND-Operation. Wendet man dies jedoch auf Array Elemente an, kann dies falsche Ergebnisse liefern, wenn man den Query nicht richtig anwendet.

$ mongo
> db.people.insert({_id: "rms", first_name: "richard", last_name: "stallman"})
> db.people.insert({_id: "steve", first_name: "steve", last_name: "ballmer"})
>
> db.people.find()
{ "_id" : "rms", "first_name" : "richard", "last_name" : "stallman" }
{ "_id" : "steve", "first_name" : "steve", "last_name" : "ballmer" }
>
> db.people.find({first_name: "richard", last_name: "ballmer"})
>

Der letzte Query liefert keine Ausgabe, da es keinen Datensatz gibt, der als Vornamen richard und als Nachnamen ballmer hat.

Das Gleiche könnte man erwarten bei folgender Situation:

> db.tags.insert({_id: "foo", tags: ["tag1", "tag2", "tag3"]})
> db.tags.insert({_id: "bar", tags: ["tag4", "tag5", "tag6"]})
>
> db.tags.find()
{ "_id" : "foo", "tags" : [ "tag1", "tag2", "tag3" ] }
{ "_id" : "bar", "tags" : [ "tag4", "tag5", "tag6" ] }
>
> db.tags.find({tags: "tag1", tags: "tag4"}, {_id: true} )
{ "_id" : "bar" }
>
> db.tags.find({tags: "tag4", tags: "tag1"}, {_id: true} )
{ "_id" : "foo" }

Wie man sieht, erhält man ein Resultat, obwohl es keinen Datensatz gibt, in dem das Tag tag1 und tag4 vorkommt.

Es scheint, als überschreibt bei mehrmaliger Angabe des gleichen Keys, der letzte davon alle vorhergehenden:

Ein

db.tags.find({tags: "tag1", tags: "tag4"}, {_id: true} )

endet also als

db.tags.find({tags: "tag4"}, {_id: true} )

bzw.

db.tags.find({tags: "tag4", tags: "tag1"}, {_id: true} )

als

db.tags.find({tags: "tag1"}, {_id: true} )

Eine UND-Verknüpfung bei Array Elementen muss daher anders angeben werden:

> db.tags.find({ $and: [ {tags: "tag1" }, {tags: "tag4"} ] }, {_id: true} )
> 
> db.tags.find({ $and: [ {tags: "tag4" }, {tags: "tag1"} ] }, {_id: true} )
> 
> db.tags.find({ $and: [ {tags: "tag1" }, {tags: "tag3"} ] }, {_id: true} )
{ "_id" : "foo" }

Oder etwas eleganter:

> db.tags.find({ tags: { $all: ["tag1", "tag4"] } } , {_id: true} )
> 
> db.tags.find({ tags: { $all: ["tag1", "tag3"] } } , {_id: true} )
{ "_id" : "foo" }
Tags: db, mongodb, nosql.
Speichern von Untertiteln - bzip2 vs zlib vs lzma
29th July 2012

Bisher speichere ich Text-Untertitel (meist im SubRip Format) als Base64-codierten String, der vorher mit zlib komprimiert ist, in einer MongoDB Datenbank. Auf der Debconf12 wurde vor kurzem diskutiert, ob man von gzip (zlib) auf xz (lzma/lzma2) umsteigen soll, um das Debian Archiv einzudampfen (derzeit 615 GB).

Dabei kam mir der Gedanke, vielleicht auch umzusteigen und zukünftig Untertitel vor dem Speichern in MongoDB mit xz zu komprimieren damit die Datenbank nicht explodiert. Aber nur weil das für Debian vielleicht von Vorteil ist, muss das in meinem Fall nicht auch so sein. Es gilt also selbst Tests durchzuführen.

Ein handvoll Untertitel zum Testen vorbereiten

$ find /media/master/Movies/ -name *.srt -exec cp "{}" /srv/subtitles/ \;

$ du -ch /srv/subtitles/*.srt

31M total

$ ls -l /srv/subtitles/ | wc -l

508

$ ls -l /srv/subtitles/ | awk '{s+=$5} END {print "Average file size: " s/NR/1024 "k"}'

Average file size: 58.1365k

Zum Testen sind also 508 Untertiteldateien vorhanden, die insgesamt eine Größe von 31 MB haben. SubRip Dateien sind in der Regel zwischen 40 und 100 KB groß, wie man auch unter "Average file size" sieht.

Mit xz komprimieren

$ time xz -k /srv/subtitles/*.srt

real    0m29.131s
user    0m26.670s
sys 0m0.216s

$ du -ch /srv/subtitles/*.xz

9.7M    total

Mit bzip2 komprimieren

$ time bzip2 -k /srv/subtitles/*.srt

real    0m7.469s
user    0m6.536s
sys 0m0.308s

$ du -ch /srv/subtitles/*.bz2

9.2M    total

Mit gzip komprimieren

$ time gzip /srv/subtitles/*.srt

real    0m4.660s
user    0m4.308s
sys 0m0.148s

$ du -ch /srv/subtitles/*.gz

12M total

xz soll angeblich sehr effektiv im Eindampfen sein, das, wie man hier sieht, nicht der Fall ist. Bei bereits nur 508 Untertiteln, spart bzip2 gegenüber gzip 2,8 MB ein und gegenüber xz 0,5 MB und braucht zeitlich nur gering länger als gzip. Bei xz dauert das schon sehr viel länger.

Da Untertitel meist nur einzeln und nicht in Bulk in MongoDB gespeichert werden, kann der Zeitverlust vernachlässigt werden:

Einzelnen Untertitel komprimieren

$ ls -lh subtitle.srt

65K Jul 29 18:28 subtitle.srt

$ time xz -k subtitle.srt

real    0m0.093s
user    0m0.064s
sys 0m0.020s

$ time bzip2 -k subtitle.srt

real    0m0.021s
user    0m0.004s
sys 0m0.012s

$ time gzip subtitle.srt

real    0m0.016s
user    0m0.008s
sys 0m0.004s

$ ls -alhS --reverse

21K Jul 29 18:28 subtitle.srt.bz2
22K Jul 29 18:28 subtitle.srt.xz
27K Jul 29 18:28 subtitle.srt.gz

Dauert alles weniger als 200 ms, ab wann es für den Menschen spürbar wird, was Latenz betrifft....

Versuch mit größeren Dateien

$ wget 'http://cachefly.cachefly.net/100mb.test' -O 100M.tar

Das Testfile von Cachefly beinhaltet sehr viele Textdateien.

$ tar tfv 100M.tar

Mit xz komprimieren

$ time xz -k 100M.tar

real    0m53.034s
user    0m51.811s
sys 0m0.296s

Mit bzip2 komprimieren

$ time bzip2 -k 100M.tar

real    0m21.024s
user    0m20.761s
sys 0m0.092s

Mit gzip komprimieren

$ time gzip 100M.tar

real    0m2.762s
user    0m2.488s
sys 0m0.136s

Resultat

$ ls -alhS --reverse

 44K Feb 28  2005 100M.tar.xz
1.5M Feb 28  2005 100M.tar.bz2
4.5M Feb 28  2005 100M.tar.gz

In diesem Fall hat xz also ganze Arbeit geleistet und 100 MB in nur 44 KB eingestampft. Bei Kompression kommt es natürlich immer darauf an, welche Art von Daten man schrumpfen will. So wie es aussieht, ist für kleine Textdateien < 900 KB bzip2 die beste Wahl. Bei sehr viel größeren Dateien wie z.B. Logfiles oder auch Binary Files zeigt xz seine Stärken.

D.h. zum Speichern der Untertitel ist in diesem Anwendungsfall bzip2 der geeignetere Kandidat. Den Code umzustricken dürfte eine Kleinigkeit sein, da die API der Python Module gleich aufgebaut ist:

Ein

$ sed -i 's/zlib\.compress/bz2\.compress/g' *.py
$ sed -i 's/zlib\.decompress/bz2\.decompress/g' *.py

wäre warscheinlich schon alles...

Links

Tags: db, mongodb, nosql, subtitle.
recutils JSON output
8th June 2014

Quick Python one-liner to get JSON output from recutils

data.rec

%rec: movies

Id: 4
Title: Banana Joe
Country: Italy  |  West Germany
Date: 1982
Director: Steno
Genre: Comedy
Length: 93
Add_date: 05/06/2008
Audio: German (MP3)
Identifier: 141
Location: Box 1
Media: DVD
Amount_of_Media: 1
Rating: 6
Video_file: /media/cdrom0/Banana-Joe.avi
Video_format: DX50
Viewed: 1
Borrower: none
Favourite: 0

Id: 2
Title: Baraka
Country: USA
Date: 1992
Director: Ron Fricke
Genre: Documentary
Length: 96 min
Add_date: 04/06/2008
Identifier: 858
Location: Box 1
Media: CD
Amount_of_Media: 1
Rating: 0
Video_file: /media/cdrom0/Baraka.avi
Video_format: divx
Viewed: 1
Borrower: none
Favourite: 0

Python one-liner

$ rec2csv data.rec | python -c 'import csv,json,sys; print(json.dumps(list(csv.DictReader(sys.stdin)), indent=4))'

Output

[
    {
        "Rating": "6", 
        "Identifier": "141", 
        "Title": "Banana Joe", 
        "Add_date": "05/06/2008", 
        "Country": "Italy  |  West Germany", 
        "Favourite": "0", 
        "Video_file": "/media/cdrom0/Banana-Joe.avi", 
        "Amount_of_Media": "1", 
        "Director": "Steno", 
        "Genre": "Comedy", 
        "Length": "93", 
        "Location": "Box 1", 
        "Borrower": "none", 
        "Media": "DVD", 
        "Date": "1982", 
        "Video_format": "DX50", 
        "Audio": "German (MP3)", 
        "Id": "4", 
        "Viewed": "1"
    }, 
    {
        "Rating": "0", 
        "Identifier": "858", 
        "Title": "Baraka", 
        "Add_date": "04/06/2008", 
        "Country": "USA", 
        "Favourite": "0", 
        "Video_file": "/media/cdrom0/Baraka.avi", 
        "Amount_of_Media": "1", 
        "Director": "Ron Fricke", 
        "Genre": "Documentary", 
        "Length": "96 min", 
        "Location": "Box 1", 
        "Borrower": "none", 
        "Media": "CD", 
        "Date": "1992", 
        "Video_format": "divx", 
        "Audio": "", 
        "Id": "2", 
        "Viewed": "1"
    }
]
Tags: db, nosql, python, recutils.

RSS Feed

"People said I should accept the world. Bullshit! I don't accept the world." -- Stallman