Key-Value-Store: Wie mach ich's thread-safe/process-safe?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Hier ist ein Beispiel, wie ich mit einem einfachen Key-Value-Store arbeiten können möchte:

Code: Alles auswählen

    wiki = KVStore("/tmp/wiki.kvstore")
    with wiki.write_transaction:
        wiki["home1"] = "This is the home page 1"
        wiki["home2"] = "This is the home page 2"

    with wiki.read_transaction:
        for name in wiki:
            print name, "=", wiki[name]
Nun möchte ich sicherstellen, dass verschiedene Prozesse auf die Datei "/tmp/wiki.kvstore" zugreifen können, ohne das sie diese gegenseitig kaputtmachen.

Die Theorie ist einfach: Lesen dürfen mehrere Prozesse, doch am Ende der "write_transaction" darf nur ein Prozess exklusiv schreiben und auch nur dann, wenn sich seit seiner Leseoperation die Datei nicht verändert hat.

Mit normalen Datei-Operationen (`open`, `file`) komme ich nicht weit. Die low-level-Operationen aus `os` können (optional) locking und können zumindest sicherstellen, dass ich nicht parallel schreibe, doch ich zu einem Punkt, wo ich zwar eine neue Datei schreiben kann, dann aber diese irgendwie die alte ersetzen lassen muss und das ist keine atomare Operation. Was tun?

Hier ist die ganze Geschichte: http://gist.github.com/173209

Hat jemand Tipps? (Mir geht's darum, zu lernen, wie's geht. Nicht einfach eine vorhandene Implementierung genannt zu bekommen.)

Stefan
lunar

Wie kann man denn mit "os.open" direkt (also ohne zweite Lock-Datei) sicherstellen, dass man nicht parallel auf eine Datei schreibt?

Wie dem auch sei, das Umbenennen über "os.rename" ist atomar. Falls Du komplexere Operationen auf der Datei ausführen willst, würde man dafür wohl eine zweite Lock-Datei verwenden (e.g. "/tmp/wiki.kvstore.lock").

Bevor ein Prozess eine exklusive Operation durchführen will, öffnet er die Datei mit "os.open('/tmp/wiki.kvstore.lock', os.O_CREAT | os.O_EXCL)". Existiert die Datei nicht, ist dieser Aufruf erfolgreich und der Prozess kann seine Operationen durchführen. Danach schließt und löscht der Prozess die Lock-Datei. Schlägt der "os.open()"-Aufruf dagegen fehl, existiert die Datei schon, und der Prozess muss irgendwie darauf warten, dass sie gelöscht wird.
farid
User
Beiträge: 95
Registriert: Mittwoch 8. Oktober 2008, 15:37

Was spricht gegen bsddb3?
apollo13
User
Beiträge: 827
Registriert: Samstag 5. Februar 2005, 17:53

farid hat geschrieben:Was spricht gegen bsddb3?
was von
Hat jemand Tipps? (Mir geht's darum, zu lernen, wie's geht. Nicht einfach eine vorhandene Implementierung genannt zu bekommen.)
hast du nicht verstanden?
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

farid hat geschrieben:Was spricht gegen bsddb3?
Dies:
sma hat geschrieben:Mir geht's darum, zu lernen, wie's geht. Nicht einfach eine vorhandene Implementierung genannt zu bekommen.
lunar hat geschrieben:Wie kann man denn mit "os.open" direkt (also ohne zweite Lock-Datei) sicherstellen, dass man nicht parallel auf eine Datei schreibt?
Mit `os.O_EXLOCK`. Muss aber leider nicht von jedem Betriebssystem und Dateisystem unterstützt werden.

Das Umbenennen atomar ist, reicht leider nicht (zumal trifft das unter Windows erst ab Vista durch eine neue `MoveFileTransacted`-Funktion zu).

Durch einen exklusiven Lock auf der ursprünglichen Datei kann ich andere schreibende Prozesse stoppen. Ich kann währenddessen eine neue Datei schreiben. So weit so gut. Doch nun müssen alte und neue Datei die Plätze tauschen. Um die alte zu löschen, muss ich (wahrscheinlich) den Lock lösen. Nun kann ein anderer Prozess ebenfalls schreiben, während ich noch die neue Datei zurück in die alte umbenennen will. Selbst wenn das geht, habe ich noch ein anderes Problem: Was, wenn mein Prozess abstürzt, nachdem er gelöscht, aber bevor er umbenannt hat?

Mir fällt da nur folgende Variante ein:

Meine Datei enthält am Anfang einen 8-Byte-Index. Hash und Daten hänge ich jetzt immer nur an und überschreibe niemals andere Daten. Will ich lesen, hole ich mir einen nicht exklusiven Lock auf die Datei, lese den Index, springe an diese Position und lese Hash und Daten. Dann gebe ich den Lock wieder frei. Will ich schreiben, hole ich mir einen exklusiven Lock, hänge jetzt Hash und Daten an die Datei an und falls das geklappt hat, schreibe atomar den neuen Index. Optional kann ich mir vorher noch die Dateilänge merken und versuchen, diese wieder auf diesen Wert zu "truncaten".

Es ist wohl schlau, auch den Hash an den Anfang der Datei zu schreiben, in der Hoffnung, dass 28 Bytes immer in einem Block atomar geschrieben werden.

Leider habe ich jetzt noch das Problem, dass meine Datei grenzenlos wächst und ab und zu verkleinert werden müsste...

Zusammenfassend: Windows und NFS sind doof. Der eine hat kein erreichbares atomares Rename, der andere keine Locks.

Vielleicht schaue ich mir später noch mal den Quelltext von BSDDB an.

Stefan
Zuletzt geändert von sma am Sonntag 30. August 2009, 10:55, insgesamt 1-mal geändert.
lunar

Wieso nimmst Du nicht einfach eine separate Lock-Datei? Die kannst Du atomar und exklusiv öffnen, so dass alle anderen Deiner Prozesse am Schreiben auf die ursprüngliche Datei gehindert werden.

Dann kannst Du alles mögliche mit der Originaldatei anstellen, und anschließend die Lock-Datei wieder schließen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Das Problem ist nicht der Lock, sondern das Ersetzen einer alten Datei durch eine neue, ohne dass man die gesamte DB schrottet. Unter Unix (Posix?) kann man mit `os.rename` löschen und unbenennen atomar machen, nicht aber unter Windows.

Ich habe gerade noch mal nachgelesen, und es sieht so aus, als wenn [aus der IEEE-Open Group-Spec] "If one or more processes have the file open when the last link is removed, the link shall be removed before rename() returns, but the removal of the file contents shall be postponed until all references to the file are closed." dafür sorgt, dass ich die Datei, die ich ersetzen will, problemlos mit einem exklusiven Lock offen halten kann, dann umbenennen und dann die Datei schließen und damit löschen kann.

Unter Windows (vor Vista) muss man aber explizit die Datei schließen, löschen und kann dann umbenennen. Unbefriedigend, weil nicht atomar.

Stefan
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Schau dir mal folgendes Modul an, das macht genau das, was du dir wünschst.
http://pypi.python.org/pypi/lockfile/0.8
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

ice2k3 hat geschrieben:Schau dir mal folgendes Modul an, das macht genau das, was du dir wünschst.
http://pypi.python.org/pypi/lockfile/0.8
Da treibt jemand viel Aufwand, um ein Feature zu implementieren, was zumindest mein Python bereits hat und kann dennoch kein korrektes Verhalten zusichern: Wenn das Programm vor dem `release` abstürzt, bleibt die Datei bzw. das Verzeichnis stehen. Ein "echter" Lock sollte vom Betriebssystem bei Prozessende wieder weggeräumt werden.

Ich bin inzwischen der Meinung, dass ich keinen Windows-Support brauche und werde demnächst mal meinen Artikel aktualisieren.

Stefan
Antworten