DDIA Chapitre 7 : Transactions

Tony Duong

Tony Duong

avr. 13, 2026 · 5 min

Aussi disponible en:🇬🇧🇯🇵
#ddia#databases#transactions#acid#isolation#distributed-systems#mvcc
DDIA Chapitre 7 : Transactions

Vue d'ensemble

Le chapitre 7 de Designing Data-Intensive Applications traite des transactions : regrouper lectures et ecritures en une unite logique afin que la base fournisse des garanties de surete (souvent resumees par ACID) malgre les crashes et l'acces concurrent. Une grande partie du chapitre porte sur l'isolation : quelles anomalies peuvent apparaitre quand des transactions s'executent en parallele, et quels niveaux d'isolation echangent performance contre un resultat ressemblant plus ou moins a une execution serie.

Pourquoi les transactions comptent

  • Tout ou rien : soit toute la transaction est commit, soit elle n'a aucun effet (atomicite), ce qui simplifie la gestion d'erreurs quand une ecriture touche plusieurs lignes ou index.
  • Recuperation apres crash : la base utilise des logs (par ex. WAL) pour que le travail commit survive aux redemarrages ; les transactions abort sont rollback.
  • Acces concurrent : sans regles claires, les lectures/ecritures entrelacees produisent des race conditions difficiles a raisonner dans le code applicatif.

ACID est une etiquette utile mais non standardisee : la "consistency" melange souvent contraintes de base et invariants metier.

Anomalies et niveaux d'isolation

L'isolation faible existe parce qu'une execution vraiment serie (une transaction a la fois) est souvent trop lente. Les niveaux plus faibles autorisent des ordonnancements qui ne sont equivalents a aucun ordre serie des memes transactions. Problemes courants :

Phenomenon Idea
Dirty read Lire des donnees ecrites par une autre transaction mais non commit.
Dirty write Ecraser des donnees non commit d'une autre transaction.
Read skew (non-repeatable read) Dans une transaction, la meme requete executee deux fois voit des snapshots commit differents car une autre transaction a commit entre-temps.
Lost update Deux transactions font read-modify-write sur le meme etat ; une mise a jour ecrase l'autre.
Write skew Deux transactions lisent des lignes qui se recouvrent et prennent des decisions localement coherentes, mais ensemble elles violent un invariant (exemple classique : deux medecins d'astreinte qui se mettent tous deux hors service).
Phantom read Une transaction relance une requete de plage et voit de nouvelles lignes correspondantes (inserees par une autre transaction).

La snapshot isolation donne a chaque transaction un snapshot coherent de la base au debut de la transaction (ou a la premiere lecture), evitant de nombreux read-skew pour les lectures, mais n'empeche pas automatiquement le write skew — il peut falloir des contraintes explicites ou un comportement serializable.

Read Committed

Niveau par defaut courant : on ne lit que des donnees commit, et des verrous d'ecriture courts evitent les dirty writes. Il ne garantit pas un snapshot stable sur toute la transaction — les non-repeatable reads restent possibles.

Snapshot Isolation et Multi-Version Concurrency Control (MVCC)

La snapshot isolation est souvent implementee via MVCC : la base conserve plusieurs versions de lignes ; une transaction voit les versions visibles a son timestamp de snapshot. Les writers creent de nouvelles versions ; readers et writers ne se bloquent en general pas mutuellement, selon des regles de type first-writer-wins ou abort on conflict.

Isolation Serializable

Une execution serializable signifie que le resultat est comme si les transactions s'etaient executees l'une apres l'autre dans un certain ordre — pas de lost updates, write skew ni phantoms (dans les garanties annoncees par le systeme).

Strategies d'implementation discutees dans le chapitre :

  • Execution serie reelle : executer les transactions sur un seul thread (ou une partition) quand la charge le permet ; peut etre tres rapide pour des systemes in-memory avec transactions courtes.
  • Two-phase locking (2PL) : verrouillage fort classique ; des predicate / index-range locks traitent les phantoms en verrouillant les chemins d'acces, pas seulement les lignes existantes.
  • Serializable snapshot isolation (SSI) : approche optimiste au-dessus de la snapshot isolation — suivre les dependances entre transactions concurrentes et abort en cas de motif dangereux (ex. cycles dans le graphe de serialisation). L'objectif est d'obtenir des performances proches du snapshot avec une semantique serializable.

Exemple generique Rails/RSpec : tester un lost update

Voici un modele simplifie pour tester des ecritures concurrentes dans une application Rails. Deux workers mettent a jour des cles differentes dans le meme etat JSON, et le test verifie que les deux mises a jour persistent.

# spec/models/job_concurrency_spec.rb
require "rails_helper"

RSpec.describe "concurrent updates", type: :model do
  it "keeps both updates without lost update" do
    job = Job.create!(
      state: {
        "node_a" => { "status" => "pending" },
        "node_b" => { "status" => "pending" }
      }
    )

    ready = Queue.new
    go = Queue.new

    threads = %w[node_a node_b].map do |node_key|
      Thread.new do
        ActiveRecord::Base.connection_pool.with_connection do
          local = Job.find(job.id)
          ready << true
          go.pop
          local.update_node_status!(node_key, "succeeded")
        end
      end
    end

    2.times { ready.pop } # both workers prepared
    2.times { go << true } # release at nearly same time
    threads.each(&:join)

    reloaded = Job.find(job.id)
    expect(reloaded.state.dig("node_a", "status")).to eq("succeeded")
    expect(reloaded.state.dig("node_b", "status")).to eq("succeeded")
  end
end
# app/models/job.rb
class Job < ApplicationRecord
  # Example column: state :jsonb
  def update_node_status!(node_key, new_status)
    with_lock do
      data = state.deep_dup
      data[node_key] ||= {}
      data[node_key]["status"] = new_status
      update!(state: data)
    end
  end
end

with_lock rend le read-modify-write atomique au niveau de la ligne, afin qu'un worker n'ecrase pas accidentellement la modification de l'autre.

Points cles

  • Les transactions regroupent les operations pour l'atomicite et des semantiques d'echec plus claires ; l'isolation definit quelles interleavings concurrentes sont autorisees.
  • Une isolation plus faible ameliore le debit mais introduit des anomalies specifiques ; comprendre read skew, lost updates, write skew et phantoms est essentiel pour choisir un niveau.
  • Snapshot isolation + MVCC est tres utilisee pour les charges a forte lecture ; ce n'est pas equivalent a une serializabilite complete pour tous les motifs d'ecriture.
  • Un comportement serializable peut etre obtenu par execution serie, 2PL (avec predicate locking pour les phantoms) ou SSI — chaque approche ayant des compromis operationnels differents.

Traduit par Claude

Tony Duong

Par Tony Duong

Un journal intime numérique. Pensées, expériences et réflexions.