5.1 KiB
slug | title | date | categories | tags | summary | |||
---|---|---|---|---|---|---|---|---|
4-verrouiller-buzhug | Verrouiller de Buzhug | 2012-02-07T00:00:00+01:00 |
|
|
Comment implémenter un verrou inter-processus et système pour Buzhug |
J'ai récemment décidé d'utiliser Buzhug pour un projet. À ce que je puisse en juger, il s'est avéré efficace, rapide, facile à utiliser et à maintenir. Cependant, j'ai rencontré quelques problèmes.
Les solutions simples sont souvent les meilleures
J'en suis venu à utiliser Buzhug pour les raisons suivantes :
- J'avais besoin d'une seule table
- Je ne voulais pas ajouter de dépendances supplémentaires au projet
- La taille de la table serait en moyenne de 5K entrées (sans dépasser 10k entrées en pic)
Et une raison supplémentaire (personnelle) :
- Je ne voulais pas me soucier de SQL. Vraiment pas. Pas question !
Cela ne me laissait qu'une option : une base de données embarquée en pur Python.
Après avoir considéré quelques bibliothèques, j'ai été séduit par la manière dont l'interface de Buzhug est proche de la manipulation d'objets Python. Et les benchmarks semblaient montrer qu'il est assez performant pour ce projet.
Après un rapide prototypage (1 jour), le choix était fait.
Puis vinrent quelques semaines de développement et les premiers tests de charge...
Et la réalité est revenue rapidement
Plusieurs fois par jour, l'application soutenue par cette base de données est intensément utilisée :
- Elle peut être exécutée jusqu'à 50 fois simultanément dans des processus Python séparés
- Chaque exécution effectue une opération de lecture et une opération d'écriture/suppression
Cela provoque une condition de course sur les fichiers utilisés pour stocker les données, et les écritures concurrentes corrompent la base de données.
L'utilisation de buzhug.TS_Base
au lieu de buzhug.Base
n'a rien résolu, car
le problème n'est pas lié aux threads, mais aux processus. Ce dont j'ai besoin
est un verrou inter-processus.
Voici la solution
La première étape a été de trouver comment implémenter un verrou inter-processus et système.
Comme cela doit seulement fonctionner sur Linux, la classe Lock donnée par Chris de Vmfarms convient parfaitement. Voici une version légèrement modifiée pour en faire un gestionnaire de contexte :
import fcntl
class PsLock:
"""
Adapté de :
http://blog.vmfarms.com/2011/03/cross-process-locking-and.html
"""
def __init__(self, filename):
self.filename = filename
self.handle = open(filename, 'w')
# Faites un OR bit à bit avec fcntl.LOCK_NB si vous avez besoin d'un verrou non bloquant
def acquire(self):
fcntl.flock(self.handle, fcntl.LOCK_EX)
def release(self):
fcntl.flock(self.handle, fcntl.LOCK_UN)
def __del__(self):
self.handle.close()
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
pass
self.release()
def __enter__(self):
self.acquire()
La deuxième étape consiste à définir une nouvelle classe qui hérite de
buzhug.Base
et qui utilise PsLock
(inspiré de TS_Base
) :
import buzhug
_lock = PsLock("/tmp/buzhug.lck")
class PS_Base(buzhug.Base):
def create(self, *args, **kw):
with _lock:
res = buzhug.Base.create(self, *args, **kw)
return res
def open(self, *args, **kw):
with _lock:
res = buzhug.Base.open(self, *args, **kw)
return res
def close(self, *args, **kw):
with _lock:
res = buzhug.Base.close(self, *args, **kw)
return res
def destroy(self, *args, **kw):
with _lock:
res = buzhug.Base.destroy(self, *args, **kw)
return res
def set_default(self, *args, **kw):
with _lock:
res = buzhug.Base.set_default(self, *args, **kw)
return res
def insert(self, *args, **kw):
with _lock:
res = buzhug.Base.insert(self, *args, **kw)
return res
def update(self, *args, **kw):
with _lock:
res = buzhug.Base.update(self, *args, **kw)
return res
def delete(self, *args, **kw):
with _lock:
res = buzhug.Base.delete(self, *args, **kw)
return res
def cleanup(self, *args, **kw):
with _lock:
res = buzhug.Base.cleanup(self, *args, **kw)
return res
def commit(self, *args, **kw):
with _lock:
res = buzhug.Base.commit(self, *args, **kw)
return res
def add_field(self, *args, **kw):
with _lock:
res = buzhug.Base.add_field(self, *args, **kw)
return res
def drop_field(self, *args, **kw):
with _lock:
res = buzhug.Base.drop_field(self, *args, **kw)
return res
Maintenant, j'utilise simplement
database = PS_Base( ... )
Et toutes les erreurs ont disparu.