3 votes

Comment FoundationDB gère-t-elle les transactions conflictuelles ?

Je suis curieux de savoir comment FoundationDB gère une situation dans laquelle plusieurs transactions tentent de mettre à jour la même clé ?

Si un seul client exécute cette transaction :

db.run((Transaction tr) -> {
  tr.set(Tuple.from("key").pack(), Tuple.from("valueA").pack());
  return null;
});

Pendant qu'un autre client exécute une transaction conflictuelle :

db.run((Transaction tr) -> {
  tr.set(Tuple.from("key").pack(), Tuple.from("valueB").pack());
  return null;
});

Que se passera-t-il dans FoundationDB pour résoudre ce conflit ?

1voto

Pavel S. Points 855

Récemment, j'ai exploré et testé FoundationDB (je suppose que tout le monde joue avec en ce moment) et dans le cadre de mes explorations, j'ai fait quelques tests simples. L'un d'eux devrait répondre à vos questions :

Voici donc un exemple (en espérant que Scala ne vous dérange pas) :

import com.apple.foundationdb._
import com.apple.foundationdb.tuple._
import resource.managed

import scala.collection.mutable
import scala.util.Random

object Example {

  val THREAD_COUNT = 1

  @volatile var v0: Long = 0
  @volatile var v1: Long = 0
  @volatile var v2: Long = 0
  @volatile var v3: Long = 0
  @volatile var v4: Long = 0

  def doJob(db: Database, x: Int): Unit = {
    db.run((tr) => {
      val key = Tuple.from("OBJ", Long.box(100)).pack()

      val current = Tuple.fromBytes(tr.get(key).join())
      if (Random.nextInt(100) < 2) {
        out(current)
      }

      val next = mutable.ArrayBuffer(current.getLong(0), current.getLong(1), current.getLong(2), current.getLong(3), current.getLong(4))

      if (x == 1 && v1 == next(1)) { println(s"again: $v1, v0=$v0, 0=${next(0)}")}
      if (x == 0 && v0 > next(0)) { out(current); ??? } else { v0 = next(0)}
      if (x == 1 && v1 > next(1)) { out(current); ??? } else { v1 = next(1)}
      if (x == 2 && v2 > next(2)) { out(current); ??? } else { v2 = next(2)}
      if (x == 3 && v3 > next(3)) { out(current); ??? } else { v3 = next(3)}
      if (x == 4 && v4 > next(4)) { out(current); ??? } else { v4 = next(4)}

      next.update(x, next(x) + 1)
      val nv = Tuple.from(next.map(v => Long.box(v)) :_*)

      tr.set(key, nv.pack())
    })

  }

  def main(args: Array[String]): Unit = {
    if (THREAD_COUNT > 5) {
      throw new IllegalArgumentException("")
    }

    val fdb: FDB = FDB.selectAPIVersion(510)
    for (db <- managed(fdb.open())) {
      // Run an operation on the database
      db.run((tr) => {
        for (x <- 0 to 10000) {
          val k = Tuple.from(s"OBJ", x.toLong.underlying()).pack()
          val v = Tuple.from(Long.box(0), Long.box(0), Long.box(0), Long.box(0), Long.box(0)).pack()
          tr.set(k, v)
          null
        }
      })

      val threads = (0 to THREAD_COUNT).map { x =>
        new Thread(new Runnable {
          override def run(): Unit = {
            while (true) {
              try {
                doJob(db, x)
              } catch {
                case t: Throwable =>
                  t.printStackTrace()
              }
            }
          }
        })
      }

      threads.foreach(_.start())
      threads.foreach(_.join())

    }
  }

  private def out(current: Tuple) = {
    println("===")
    println((v0, v1, v2, v3, v4))
    println((Thread.currentThread().getId, current))
  }
}

Ainsi, cette chose vous permet de lancer plusieurs threads écrivant dans le même objet. Il reste du code non requis provenant d'autres expériences, ignorez-le (ou utilisez-le pour vos propres expériences).

Ce code génère vos threads, puis chaque thread lit un tuple de cinq longs, comme suit (0,1,0,0,0) de la clé ("OBJ", 100) puis incrémente correspondant au numéro du fil, puis le réécrit et incrémente l'un des compteurs volatils.

Et ce sont mes observations :

  1. Lorsque vous exécutez cet exemple configuré avec un seul thread, vous verrez qu'il écrit très rapidement,
  2. Lorsque vous augmentez la concurrence, vous remarquerez que vos écritures ralentissent (attendu)...
  3. ...Et vous verrez que ce code est exécuté de temps en temps : println(s"again: $v1, v0=$v0, 0=${next(0)}")

Donc, essentiellement, lorsqu'un conflit se produit, les clients FoundationDB essaient de valider les transactions jusqu'à ce qu'elles aboutissent. Vous pouvez trouver plus de détails dans ce chapitre des docs. Ensuite, regardez les schéma d'ensemble de l'architecture.

Il ne faut pas non plus que vos transactions ne soient que des fonctions. Espérons que - fonctions idempotentes .

Et vous devez savoir que dans de nombreux cas, vous pouvez éviter les conflits en utilisant opérations atomiques sur votre valeur.

J'espère que cela répond à votre question.

Je vous conseille de lire tous les documents officiels par conséquent, vous pouvez trouver beaucoup choses intéressantes, y compris comment les développeurs de bases de données considérer le théorème CAP de beaux exemples de structures de données distribuées cool et bien d'autres détails techniques et choses intéressantes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X