expert fridays - Сергей Укустов: "crdt"
TRANSCRIPT
Conflict-free ReplicatedData Type
Функциональный подход к распределённым системам
Сергей Укустов
«Умные структуры данных и тупой код работают куда лучше, чем наоборот»
Эрик Раймонд
Распределённые системы
• Много общающихся машин
• Общее состояние• Бывает разное
• CAP теорема• Consistency, Availability, Partitioning – выберите любые два
• CALM теорема• Consistency as logical monotonicity – система может быть распределённой,
если она не удаляет исторические факты
CRDT
• Тип структур данных для eventually consistent систем
• Формально доказано: состояние системы сходится
• CAP теорема: выкидываем строгую consistency
• CALM теорема: строгая гарантия eventual consistency
CRDT: State-based
• Множество <A, combine>• a combine (b combine c) == (a combine b) combine c
• a combine b == b combine a
• a combine a == a
• Необходимо есть нулевой элемент• Ограниченная полурешетка AKA коммутативный идемпотентный моноид
• Частичный порядок
G-Counter
• Можно только увеличивать счётчик
case class GCounter[R, E](state: Map[R, E])(implicit num: Numeric[E])
import com.machinomy.crdt.state._import cats.syntax.all._import cats._
val counter = Monoid[GCounter[Int, Int]].empty // empty G-Counterval firstReplica = counter + (1 -> 1) // increment replica 1val secondReplica = counter + (2 -> 2) // increment replica 2val firstReplicacombined = firstReplica |+| secondReplica // combineval secondReplicacombined = secondReplica |+| firstReplica // combine
firstReplicacombined == secondReplicacombined // the result is independent of combine order
PN-Counter
• Можно делать инкремент и декремент
class PNCounter[K, E: Numeric](increments: GCounter[K, E], decrements: GCounter[K, E])
import com.machinomy.crdt.state._import cats.syntax.all._import cats._
val counter = Monoid[PNCounter[Int, Int]].emptyval firstReplica = counter + (1 -> 1) // increment replica 1val secondReplica = counter + (2 -> -2) // decrement replica 2val firstReplicacombined = firstReplica |+| secondReplica // combineval secondReplicacombined = secondReplica |+| firstReplica // combine
firstReplicacombined == secondReplicacombined // the result is independent of combine order
G-Set
• Только добавление, `combine` объединяет множества
• class GSet[E](val state: Set[E])
import com.machinomy.crdt.state._import cats.syntax.all._import cats._
val counter = Monoid[GSet[Int]].empty // empty G-Setval firstReplica = counter + 1 // add elementval secondReplica = counter + 2 // add elementval firstReplicacombined = firstReplica |+| secondReplica // combineval secondReplicacombined = secondReplica |+| firstReplica // combine
firstReplicacombined == secondReplicacombined // the result is independent ofcombine order
И ещё…
• GT-Set
• MC-Set
• OR-Set
• TP-Set
• LWW-Element-Set
• LWW-Register
• …
CRDT: Op-based
• Передаём операции по сети, а не состояниеS · f · g = S · g · f
• Можно эмулировать поверх state-based, и наоборот
Op-based Counter
case class Counter[E: Numeric](value: E)
def update[E: Numeric](counter: Counter[E], update: Update[E]): Counter[E] = update match {
case Counter.Increment(i) => Counter(num.plus(counter.value, i))case Counter.Decrement(i) => Counter(num.minus(counter.value, i))
}
Op-based ORSet
case class ORSet[E, T: TombStone](state: Map[E, Set[T]])
2P2P-Graph
case class TPTPGraph[A, V <: VertexLike[A], E <: EdgeLike[A, V]]
(va: Set[V], vr: Set[V], ea: Set[E], er: Set[E])
• Можно добавлять и удалять элементы
• Но никаких гарантий!
Monotonic DAG
Monotonic DAG
case class MonotonicDag[V, E[X] <: DiEdgeLikeIn[X], G <: Graph[V, E]](graph: G)
type UpdateResult[V, E[X] <: DiEdgeLikeIn[X], G <: Graph[V, E]] = (MonotonicDag[V, E, G], Option[Update[V, E, G]])
def add(e: E[V] with OuterEdge[V, E]): MonotonicDag.UpdateResult[V, E, G] = {val from = graphLike.fromVertex(graph, e)val to = graphLike.toVertex(value, e)val containsSource = graphLike.contains(value, from)val containsDestination = graphLike.contains(value, to)val effective = graphLike.addEdge(value, e)if (containsSource && containsDestination && graphLike.existsPath(effective, from, to)) {(MonotonicDag[V, E, G](effective), Some(MonotonicDag.AddEdge[V, E, G](e)))
} else {(this, None)
}}
Pro et Contra
• Простая логика
• Стабильное теоретическое обоснование
• Лучше, чем OT
• Социальное доказательство
• Непонятно, что делать с BFT
• Необходимо обеспечить стабильный канал
• Сложно найти подходящую реализацию
https://github.com/machinomy/crdthttps://www.mendeley.com/groups/9275881/crdt/papers/