Version de développement
Cette documentation décrit la version « next » (develop), non encore publiée. Pour la version stable, basculez sur « v5 » dans l'en-tête.
Références techniques

Verrous transactionnels dans PostgreSQL

Utilisation des verrous transactionnels advisory pour PostgreSQL dans Dokos

Verrous transactionnels dans PostgreSQL

Dokos supporte désormais les verrous transactionnels advisory pour PostgreSQL, permettant de sécuriser des sections critiques de validation sans verrouiller des lignes de données. Cette fonctionnalité est particulièrement utile pour les développeurs qui doivent implémenter des logiques de validation complexes tout en maintenant la cohérence des données.

Fonctionnement

La fonction frappe.db.transaction_advisory_lock(key) utilise le verrou pg_advisory_xact_lock de PostgreSQL, qui présente les caractéristiques suivantes :

  • Portée transactionnelle : Le verrou est automatiquement libéré lorsque la transaction est validée ou annulée.
  • Détection des interblocages : Participe à la détection des interblocages au même titre que les verrous de lignes.
  • Réentrance : Un même verrou peut être acquis plusieurs fois au sein d'une même transaction sans blocage.
  • Héritage des transactions : Le verrou est partagé avec les transactions imbriquées.

Utilisation

Pour utiliser un verrou transactionnel advisory dans votre code :

# Acquérir un verrou transactionnel
if frappe.db.db_type == "postgres":
    frappe.db.transaction_advisory_lock("ma_cle_unique")

# Effectuer des opérations critiques
try:
    # Validation des données
    # ...
    frappe.db.commit()  # Le verrou est libéré ici

except Exception:
    frappe.db.rollback()  # Le verrou est libéré ici

Cas d'usage

Exemple : Validation de stock en batch

Lors de la valorisation des stocks en batch, il est crucial d'éviter les conflits entre plusieurs mouvements simultanés sur le même article. Plutôt que de verrouiller toutes les lignes d'historique des mouvements de stock (SLE), vous pouvez utiliser un verrou transactionnel advisory :

if frappe.db.db_type == "postgres":
    frappe.db.transaction_advisory_lock(f"stock_valuation_{item_code}")

# Calculer la valorisation du stock
# ...

Avantages par rapport aux verrous traditionnels

  1. Performance : Évite le verrouillage de nombreuses lignes de données.
  2. Simplicité : Un seul verrou en mémoire remplace des centaines de verrous de lignes.
  3. Sécurité : Intégré au mécanisme de détection des interblocages de PostgreSQL.

Limitations

  • Spécifique à PostgreSQL : Cette fonctionnalité n'est disponible que pour les bases de données PostgreSQL. Pour MariaDB, vous devez utiliser des mécanismes alternatifs comme GET_LOCK (qui est de portée session et non transactionnelle).
  • Nécessite une gestion explicite : Les développeurs doivent vérifier le type de base de données avant d'utiliser cette fonction.

Bonnes pratiques

  1. Choix des clés : Utilisez des clés uniques et descriptives pour éviter les collisions.
  2. Durée minimale : Gardez les sections verrouillées aussi courtes que possible.
  3. Gestion des erreurs : Assurez-vous que les transactions sont correctement validées ou annulées.
  4. Documentation : Documentez clairement l'utilisation des verrous dans votre code.

Vérification du verrou

Vous pouvez vérifier les verrous actifs dans PostgreSQL avec la requête suivante :

SELECT locktype, objid, transactionid, mode, granted
FROM pg_locks
WHERE locktype = 'advisory';

Exemple complet

Voici un exemple complet d'utilisation dans un contexte de valorisation de stock :

def calculate_batch_valuation(item_code, warehouse):
    if frappe.db.db_type == "postgres":
        frappe.db.transaction_advisory_lock(f"valuation_{item_code}_{warehouse}")

    try:
        # Récupérer les mouvements de stock
        sle = frappe.get_all(
            "Stock Ledger Entry",
            filters={
                "item_code": item_code,
                "warehouse": warehouse
            },
            fields=["*"],
            order_by="posting_date"
        )

        # Calculer la valorisation
        valuation = 0
        for entry in sle:
            valuation += entry.stock_value_difference

        # Mettre à jour l'article
        frappe.db.set_value(
            "Item",
            item_code,
            "standard_rate",
            valuation / frappe.db.get_value("Item", item_code, "actual_qty")
        )

        frappe.db.commit()

    except Exception as e:
        frappe.db.rollback()
        frappe.throw(f"Erreur lors de la valorisation : {str(e)}")

Compatibilité

Base de donnéesSupportAlternative
PostgreSQL✅ Oui-
MariaDB❌ NonGET_LOCK

Cette fonctionnalité est particulièrement utile pour les applications personnalisées qui nécessitent une synchronisation fine des opérations sans impact sur les performances globales du système.