Les transactions de base de données dans Rails : ce que signifie vraiment ACID pour votre code
ActiveRecord rend les transactions très faciles à utiliser. Ce que promet réellement chacune des quatre garanties ACID, et où les développeurs Rails se trompent sur l'isolation.
Publié le 17 octobre 2021 · 5 min de lecture
ActiveRecord rend les transactions si faciles à utiliser qu'il vaut la peine de prendre du recul et d'être précis sur ce qu'une transaction garantit réellement, et sur ce qu'elle ne garantit très délibérément pas.
Quatre lettres, quatre promesses distinctes
Où les développeurs Rails se trompent sur l'isolation
Le niveau d'isolation par défaut de Postgres, read committed, est plus faible que ce que la plupart des ingénieurs supposent. Deux transactions peuvent lire la même ligne, décider toutes les deux qu'il est sûr de la mettre à jour, et valider toutes les deux, la seconde écriture écrasant silencieusement l'intention de la première. Cela se manifeste comme une race condition dans des cas comme la décrémentation d'un stock ou le crédit du solde d'un portefeuille.
# Racy: two requests can both pass the check before either updates
if account.balance_cents >= amount_cents
account.update!(balance_cents: account.balance_cents - amount_cents)
end
# Safe: the row lock serializes concurrent updates
ActiveRecord::Base.transaction do
account = Account.lock.find(account.id)
raise InsufficientFunds if account.balance_cents < amount_cents
account.update!(balance_cents: account.balance_cents - amount_cents)
end
La cohérence est aussi le travail de vos callbacks, pas seulement celui de la base de données
Le « C » de ACID renvoie aux contraintes au niveau de la base de données, pas aux règles métier de votre application. Une validation numérique contournée par une mise à jour brute d'une colonne laisse vos données techniquement validées et pratiquement fausses. Appuyez-vous sur les contraintes de la base, NOT NULL, clés étrangères, check constraints, pour tout ce qui ne doit jamais être violé, et considérez les validations de modèle comme une seconde couche, pas comme la seule.
La durabilité a un coût qu'il vaut mieux connaître
Chaque commit qui retourne avec succès à votre processus Rails a, par défaut, été écrit sur disque de manière à survivre à un crash. Cette garantie est ce qui rend relativement coûteux le fait de valider à chaque écriture à fort volume, et c'est pourquoi regrouper les écritures en moins de transactions, plus grosses, est souvent la première vraie amélioration de performance sur une table très sollicitée, bien avant d'ajouter du matériel.
Articles liés
Comment rattraper des millions de lignes dans une base Rails sans interruption de service
Rattraper une table de quarante millions de lignes n'est pas le même problème que d'en rattraper une de quarante mille. Ce qui change, et comment nous le régulons en toute sécurité.
Migrations de base de données sans interruption avec Rails : un guide pratique
Pourquoi les migrations qui paraissent anodines sont celles qui cassent la production, et la séquence que nous suivons pour modifier un schéma sans verrouiller une table très sollicitée.