Mein erster Versuch (abgeguckt von Redis) war, per `os.fork` ein Kind abzuspalten, das das `dict` auf Platte "pickelt". Leider klont `os.fork` auch alle offenen Datei-Handles, insbesondere das Socket meines HTTP-Servers und leider fängt das benutzte Web-Rahmenwerk (bottle) auch `SystemExit`, sodass sich mein abgespaltenes Kind nicht beenden kann, sondern versucht, einen Fehler HTTP 500 in das Socket zu schreiben.
Fork ist praktisch, da es atomar ist und Unix eine copy-on-write-Semantik für den Speicher hat. Ich konserviere somit den Stand des `dict` genau zu dem Zeitpunkt, zu dem ich `os.fork` aufrufe und es gibt keinen inkonsistenten Zustand. Außerdem ist es speichereffizient und passt prima zu Python mit seinem GIL.
Leider kann ich bei einer WSGI-Architektur nicht explizit den geklonten Datei-Handle schließen, wenn ich das richtig sehe. Ich wollte aber gerne einfach HTTP als Kommunikationsprotokoll benutzen.
Welche Alternativen habe ich?
Ich könnte einen Thread abspalten und zum Sichern des `dict` benutzen. (Jedenfalls, wenn ich weiß, dass ich in keiner CGI-artigen Run-Once-Umgebung laufe.)
Nun kann es aber zu Änderungen am `dict` kommen während ich es speichern will.
Ich bräuchte eine "persistente" Datenstruktur, wie sie Clojure hat. Persistent meint hier nicht wie bei vielen objektreationalen Mappern, das man sie irgendwie in eine DB geschrieben hat, sondern, dass sie nach dem Anlegen unveränderbar ist. Ein persistentes `dict` ändert sich nie. Operationen um dort neue Schlüssel und Werte einzutragen erzeugen neue Datenstrukturen, die sich in der Regel so viel Speicher wie möglich mit der alten Struktur teilen.
Gibt es so etwas für Python? Die Clojure-Implementierung ist nämlich alles andere als trivial. Ich glaube, sie basiert für "PersistentMap" auf einem Red-Black-Tree.
Hier ist eine Arme-Leute-Implementierung:
Code: Alles auswählen
class PersistentDict(object):
def __init__(self, dct):
self.dct = dct
def get(self, key, default=None):
return dct.get(key, default)
def put(self, key, val):
old = self.get(key)
if old is val:
return self
return PersistentDict(dict(self.dct, **{key: val}))
Speziell für meinen Key-Value-Store könnte ich das optimieren, indem ich, solange ich das `dict` auf Platte schreibe, ein Flag setze, das dafür sorgt, dass ich bei Änderungen einmalig ein neues `dict` klone:
Code: Alles auswählen
def put(key, val):
with dict_lock.acquire():
if need_to_clone:
store = dict(store)
need_to_clone = False
store[key] = val
def background_save():
with dict_lock.acquire():
store_ref = store
need_to_clone = True
try:
with open("...") as f:
pickle.dump(store_ref, f)
finally:
with dict_lock.acquire():
need_to_clone = False
Oder ich benutzen bsddb oder sqlite statt alles im Hauptspeicher zu halten. Aber wo ist denn dann noch der Spaß bei der Sache? Außerdem finde ich das einfache `dict`-Programmiermodell durchaus attraktiv.
Wir würdet ihr es machen?
Stefan