bcarlin.net/content/blog/004-locking-buzhug/index.fr.md

178 lines
5.1 KiB
Markdown
Raw Normal View History

2025-06-20 02:33:45 +02:00
---
slug: 4-verrouiller-buzhug
title: Verrouiller de Buzhug
date: "2012-02-07T00:00:00+01:00"
categories: [Dev]
tags:
- Python
- Buzhug
summary: >
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.
[Buzhug]: http://buzhug.sourceforge.net
## 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](http://blog.vmfarms.com/2011/03/cross-process-locking-and.html) convient
parfaitement. Voici une version légèrement modifiée pour en faire un gestionnaire de contexte :
```python
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`) :
```python
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
```python
database = PS_Base( ... )
```
Et toutes les erreurs ont disparu.