(* Victor B. F. Gomes, University of Cambridge MartinKleppmann,UniversityofCambridge DominicP.Mulligan,UniversityofCambridge AlastairR.Beresford,UniversityofCambridge
*)
section‹Strong Eventual Consistency›
text‹In this section we formalise the notion of strong eventual consistency.
We do not make any assumptions about networks or data structures; instead,
we use an abstract model of operations that may be reordered, and we reason about
the properties that those operations must satisfy.
We then provide concrete implementations of that abstract model in later sections.›
theory
Convergence imports
Util begin
text‹The \emph{happens-before} relation, as introduced by cite‹"Lamport:1978jq"›, captures
causal dependencies between operations. It can be defined in terms of sending and receiving
messages on a network.
However, for now, we keep it abstract, our only restriction on the happens-before relation is
that it must be a \emph{strict partial order},
that is, it must be irreflexive and transitive, which implies that it is also antisymmetric.
We describe the state of a node using an abstract type variable.
To model state changes, we assume the existence of an \emph{interpretation} function \isa{interp}
which lifts an operation into a \emph{state transformer}---a function that either maps
an old state to a new state, or fails.›
locale happens_before = preorder hb_weak hb for hb_weak :: "'a ==> 'a ==> bool" (infix‹⪯›50) and hb :: "'a ==> 'a ==> bool" (infix‹≺›50) + fixes interp :: "'a ==> 'b ⇀ 'b" (‹⟨_⟩› [0] 1000) begin
text‹We say that two operations $x$ and $y$ are \emph{concurrent}, written
$\isa{x} \mathbin{\isasymparallel} \isa{y}$, whenever one does not happen before the other:
$\neg (\isa{x} \prec\isa{y})$ and $\neg (\isa{y} \prec\isa{x})$.›
definition concurrent_set :: "'a ==> 'a list ==> bool"where "concurrent_set x xs ≡∀y ∈ set xs. x ∥ y"
lemma concurrent_set_empty [simp, intro!]: "concurrent_set x []" by (auto simp: concurrent_set_def)
lemma concurrent_set_ConsE [elim!]: assumes"concurrent_set a (x#xs)" and"concurrent_set a xs ==> concurrent x a ==> G" shows"G" using assms by (auto simp: concurrent_set_def)
lemma concurrent_set_ConsI [intro!]: "concurrent_set a xs ==> concurrent a x ==> concurrent_set a (x#xs)" by (auto simp: concurrent_set_def)
lemma concurrent_set_appendI [intro!]: "concurrent_set a xs ==> concurrent_set a ys ==> concurrent_set a (xs@ys)" by (auto simp: concurrent_set_def)
lemma concurrent_set_Cons_Snoc [simp]: "concurrent_set a (xs@[x]) = concurrent_set a (x#xs)" by (auto simp: concurrent_set_def)
text‹The purpose of the happens-before relation is to require that some operations must be applied
in a particular order, while allowing concurrent operations to be reordered with respect to each other.
We assume that each node applies operations in some sequential order (a standard assumption
for distributed algorithms), and so we can model the execution history of a node as a list of operations.›
inductive hb_consistent :: "'a list ==> bool"where
[intro!]: "hb_consistent []" |
[intro!]: "[ hb_consistent xs; ∀x ∈ set xs. ¬ y ≺ x ]==> hb_consistent (xs @ [y])"
text‹As a result, whenever two operations $\isa{x}$ and $\isa{y}$ appear in a hb-consistent list,
and $\isa{x}\prec\isa{y}$, then $\isa{x}$ must appear before $\isa{y}$ in the list.
However, if $\isa{x}\mathbin{\isasymparallel}\isa{y}$, the operations can appear in the list
in either order.›
lemma"(x ≺ y ∨ concurrent x y) = (¬ y ≺ x)" using less_asym by blast
lemma consistentI [intro!]: assumes"hb_consistent (xs @ ys)" and"∀x ∈ set (xs @ ys). ¬ z ≺ x" shows"hb_consistent (xs @ ys @ [z])" using assms hb_consistent.intros append_assoc by metis
lemma hb_consistent_remove1 [intro]: assumes"hb_consistent xs" shows"hb_consistent (remove1 x xs)" using assms by (induction rule: hb_consistent.induct) (auto simp: remove1_append)
lemma hb_consistent_singleton [intro!]: shows"hb_consistent [x]" using hb_consistent.introsby fastforce
lemma hb_consistent_prefix_suffix_exists: assumes"hb_consistent ys" "hb_consistent (xs @ [x])" "{x} ∪ set xs = set ys" "distinct (x#xs)" "distinct ys" shows"∃prefix suffix. ys = prefix @ x # suffix ∧ concurrent_set x suffix" using assms proof (induction arbitrary: xs rule: hb_consistent.induct, simp) fix xs y ys assume IH: "(∧xs. hb_consistent (xs @ [x]) ==> {x} ∪ set xs = set ys ==> distinct (x # xs) ==> distinct ys ==> ∃prefix suffix. ys = prefix @ x # suffix ∧ concurrent_set x suffix) " assume assms: "hb_consistent ys""∀x∈set ys. ¬ hb y x" "hb_consistent (xs @ [x])" "{x} ∪ set xs = set (ys @ [y])" "distinct (x # xs)""distinct (ys @ [y])" hence"x = y ∨ y ∈ set xs" using assms by auto moreover { assume"x = y" hence"∃prefix suffix. ys @ [y] = prefix @ x # suffix ∧ concurrent_set x suffix" by force
} moreover { assume y_in_xs: "y ∈ set xs" hence"{x} ∪ (set xs - {y}) = set ys" using assms by (auto intro: set_equality_technical) hence remove_y_in_xs: "{x} ∪ set (remove1 y xs) = set ys" using assms by auto moreoverhave"hb_consistent ((remove1 y xs) @ [x])" using assms hb_consistent_remove1 by force moreoverhave"distinct (x # (remove1 y xs))" using assms by simp moreoverhave"distinct ys" using assms by simp ultimatelyobtain prefix suffix where ys_split: "ys = prefix @ x # suffix ∧ concurrent_set x suffix" using IH by force moreover { have"concurrent x y" using assms y_in_xs remove_y_in_xs concurrent_def by blast hence"concurrent_set x (suffix@[y])" using ys_split by clarsimp
} ultimatelyhave"∃prefix suffix. ys @ [y] = prefix @ x # suffix ∧ concurrent_set x suffix" by force
} ultimatelyshow"∃prefix suffix. ys @ [y] = prefix @ x # suffix ∧ concurrent_set x suffix" by auto qed
lemma hb_consistent_append [intro!]: assumes"hb_consistent suffix" "hb_consistent prefix" "∧s p. s ∈ set suffix ==> p ∈ set prefix ==>¬ s ≺ p" shows"hb_consistent (prefix @ suffix)" using assms by (induction rule: hb_consistent.induct) force+
lemma hb_consistent_append_porder: assumes"hb_consistent (xs @ ys)" "x ∈ set xs" "y ∈ set ys" shows"¬ y ≺ x" using assms by (induction ys arbitrary: xs rule: rev_induct) force+
text‹We can now define a function \isa{apply-operations} that composes an arbitrary list of operations
into a state transformer. We first map \isa{interp} across the list to obtain a state transformer
for each operation, and then collectively compose them using the Kleisli arrow composition combinator.›
definition apply_operations :: "'a list ==> 'b ⇀ 'b"where "apply_operations es ≡ foldl (⊳) Some (map interp es)"
lemma apply_operations_empty [simp]: "apply_operations [] s = Some s" by(auto simp: apply_operations_def)
text‹We say that two operations $\isa{x}$ and $\isa{y}$ \emph{commute} whenever
$\langle\isa{x}\rangle\mathbin{\isasymrhd} \langle\isa{y}\rangle = \langle\isa{y}\rangle\mathbin{\isasymrhd} \langle\isa{x}\rangle$,
i.e. when we can swap the order of the composition of their interpretations without changing
the resulting state transformer. For our purposes, requiring that this property holds for \emph{all} pairs of operations is too strong. Rather, the commutation property is only required
to hold for operations that are concurrent.›
definition concurrent_ops_commute :: "'a list ==> bool"where "concurrent_ops_commute xs ≡ ∀x y. {x, y} ⊆ set xs ⟶ concurrent x y ⟶⟨x⟩⊳⟨y⟩ = ⟨y⟩⊳⟨x⟩"
text‹We can now state and prove our main theorem, $\isa{convergence}$.
This theorem states that two hb-consistent lists of distinct operations, which are permutations
of each other and in which concurrent operations commute, have the same interpretation.›
theorem convergence: assumes"set xs = set ys" "concurrent_ops_commute xs" "concurrent_ops_commute ys" "distinct xs" "distinct ys" "hb_consistent xs" "hb_consistent ys" shows"apply_operations xs = apply_operations ys" using assms proof(induction xs arbitrary: ys rule: rev_induct, simp) case assms: (snoc x xs) thenobtain prefix suffix where ys_split: "ys = prefix @ x # suffix ∧ concurrent_set x suffix" using hb_consistent_prefix_suffix_exists by fastforce moreoverhence *: "distinct (prefix @ suffix)""hb_consistent xs" using assms by auto moreover { have"hb_consistent prefix""hb_consistent suffix" using ys_split assms hb_consistent_append_D2 hb_consistent_append_elim_ConsD by blast+ hence"hb_consistent (prefix @ suffix)" by (metis assms(8) hb_consistent_append hb_consistent_append_porder list.set_intros(2) ys_split)
} moreoverhave **: "concurrent_ops_commute (prefix @ suffix @ [x])" using assms ys_split by (clarsimp simp: concurrent_ops_commute_def) moreoverhence"concurrent_ops_commute (prefix @ suffix)" by (force simp del: append_assoc simp add: append_assoc[symmetric]) ultimatelyhave"apply_operations xs = apply_operations (prefix@suffix)" using assms by simp (metis Diff_insert_absorb Un_iff * concurrent_ops_commute_appendD set_append) moreoverhave"apply_operations (prefix@suffix @ [x]) = apply_operations (prefix@x # suffix)" using ys_split assms ** concurrent_ops_commute_concurrent_set by force ultimatelyshow ?case using ys_split by (force simp: append_assoc[symmetric] simp del: append_assoc) qed
corollary convergence_ext: assumes"set xs = set ys" "concurrent_ops_commute xs" "concurrent_ops_commute ys" "distinct xs" "distinct ys" "hb_consistent xs" "hb_consistent ys" shows"apply_operations xs s = apply_operations ys s" using convergence assms by metis end
subsection‹Convergence and progress›
text‹Besides convergence, another required property of SEC is \emph{progress}: if a valid operation
was issued on one node, then applying that operation on other nodes must also succeed---that is,
the execution must not become stuck in an error state.
Although the type signature of the interpretation function allows operations to fail, we need to
prove that in all $\isa{hb-consistent}$ network behaviours such failure never actually occurs.
We capture the combined requirements in the $\isa{strong-eventual-consistency}$ locale,
which extends $\isa{happens-before}$.›
locale strong_eventual_consistency = happens_before + fixes op_history :: "'a list ==> bool" and initial_state :: "'b" assumes causality: "op_history xs ==> hb_consistent xs" assumes distinctness: "op_history xs ==> distinct xs" assumes commutativity: "op_history xs ==> concurrent_ops_commute xs" assumes no_failure: "op_history(xs@[x]) ==> apply_operations xs initial_state = Some state ==>⟨x⟩ state ≠ None" assumes trunc_history: "op_history(xs@[x]) ==> op_history xs" begin
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.