Slide 1

Slide 1 text

#JTF2019 #JTF2019_A Proof. #JTF2019 #JTF2019_A テスト駆動開発から証明駆動開発へ チェシャ猫 (@y_taka_23) July Tech Festa 2019 (2019/12/08)

Slide 2

Slide 2 text

#JTF2019 #JTF2019_A 本日のアジェンダ ● 分散システムとテスト ● プログラムと証明の間には ● 定理証明と数学的帰納法 ● 分散システムの証明

Slide 3

Slide 3 text

#JTF2019 #JTF2019_A 分散システムとテスト Lemma 1.

Slide 4

Slide 4 text

#JTF2019 #JTF2019_A Kubernetes https://github.com/kubernetes/kubernetes

Slide 5

Slide 5 text

#JTF2019 #JTF2019_A etcd https://github.com/etcd-io/etcd

Slide 6

Slide 6 text

#JTF2019 #JTF2019_A CloudNative の立役者 etcd ● Kubernetes のバックエンド KVS ○ システム全体の Source of Truth ○ 壊れると Kubernetes が動作不能に ● 分散合意アルゴリズム Raft を利用 ○ クラスタの過半数が生存していれば大丈夫 ○ Paxos と比較して理解可能性を重視

Slide 7

Slide 7 text

#JTF2019 #JTF2019_A 枯れたクラスタリング技術?

Slide 8

Slide 8 text

#JTF2019 #JTF2019_A etcd v3.4 (2019/08) での修正点 ● 新ノードの Join 時に問題が発生 ○ データコピーの際、Leader が高負荷で死ぬ ○ 起動失敗の場合でもメンバとして認識してしまう ○ 1-Node クラスタへの Join に失敗すると即崩壊 ● 新しい状態 Learner の導入 ○ Join 直後は「過半数」にカウントされない

Slide 9

Slide 9 text

#JTF2019 #JTF2019_A 意外と問題があったりする

Slide 10

Slide 10 text

#JTF2019 #JTF2019_A 考えるべき事象が多すぎる ● 通信パケットが入れ替わったら? ● 通信パケットが重複して送信されたら? ● 通信パケットが欠落したら? ● 変なタイミングでノードが死んだら? ● 実は生きていて通信が遅れて届いたら?

Slide 11

Slide 11 text

#JTF2019 #JTF2019_A 分散システムの検証困難性 ● 各ノードが非同期に動作する ○ 考えるべきパターンが多すぎる ● ノードやネットワークは信頼できない ○ 独立にランダムなタイミングで故障が発生 ● 仕様そのものが複雑になりがち ○ 特定の瞬間ではなく一連の動作が問題に

Slide 12

Slide 12 text

#JTF2019 #JTF2019_A これテスト書くの辛くない?

Slide 13

Slide 13 text

#JTF2019 #JTF2019_A 古典的なテストは「未知」に弱い ● 人間がテストケースを与える ○ 明示した有限個のケースしか保証できない ○ 思いつかない異常系はそもそも考慮外 ● 再現性がある問題しか扱えない ○ 「たまに落ちるテスト」はあまり役に立たない ○ 特にタイミングに依存する問題は難しい

Slide 14

Slide 14 text

#JTF2019 #JTF2019_A 「未知」に対するふたつのアプローチ

Slide 15

Slide 15 text

#JTF2019 #JTF2019_A 1. 未知に素早く反応する

Slide 16

Slide 16 text

#JTF2019 #JTF2019_A Chaos Engineering ● 運用環境であえて障害を発生させる ○ サービス不能状態に陥らないか ○ 障害状態から正しく復旧できるか ● 隠れていた問題点を炙り出せる ○ 意図して発生させることが難しいバグを発見 ○ いわば「未知に素早く反応する」アプローチ

Slide 17

Slide 17 text

#JTF2019 #JTF2019_A Section 1 のポイント ● 流行りの分散システムは留意点が多い ○ タイミング・組み合わせ・ランダム性 ● 古典的なテストの限界 ○ 既知の問題から外れたケースの保証は苦手 ● 未知の事象にどう対処するか ○ Chaos Engineering は「未知に素早く反応」する

Slide 18

Slide 18 text

#JTF2019 #JTF2019_A 2. 未知を既知にする

Slide 19

Slide 19 text

#JTF2019 #JTF2019_A #JTF2019 #JTF2019_A 形式手法 Formal Methods

Slide 20

Slide 20 text

#JTF2019 #JTF2019_A プログラムと証明の間には Lemma 2.

Slide 21

Slide 21 text

#JTF2019 #JTF2019_A 形式手法とは ● システムを数学的対象により表現 ○ その対象の性質に基づいて検証を行う ○ 対象として何を選ぶかでツールの性質が決まる ○ テストケースの抜けや漏れが生じない ● 厳密化した仕様を機械的にチェック ○ いわば「既知の範囲を押し拡げる」アプローチ

Slide 22

Slide 22 text

#JTF2019 #JTF2019_A 形式手法の分類 ● モデル検査 ○ システムが取りうる値を列挙して探索 ○ 有限個のパターンに収まれば自動化できる ● 定理証明(今日のテーマ) ○ いわゆる数学的な証明をプログラム的に表現 ○ 真に無限個のパターンを扱うことができる

Slide 23

Slide 23 text

#JTF2019 #JTF2019_A 型、付けてますか?

Slide 24

Slide 24 text

#JTF2019 #JTF2019_A 静的型による安全性 ● 型は値の不変条件を表す ○ 実行中に int 型が string 型に変わったりしない ○ 指定した関数・メソッドの存在を保証 ● 型システムは言語によって色々 ○ 豊かな型システムは複雑な条件を強制できる ○ 多くの場合、数学的な定式化が与えられている

Slide 25

Slide 25 text

#JTF2019 #JTF2019_A 直積型 ● ふたつの型をペアにする ○ 長さが型レベルで固定されている ○ 異なる種類の型の値をまとめられる # let pi = ("pi", 3.14);; val pi : string * float = ("pi", 3.14) # let pi2 = ["pi", 3.14];; #=> Type Error

Slide 26

Slide 26 text

#JTF2019 #JTF2019_A 直和型 ● ふたつの型のどちらか片方を持つ ○ パターンマッチでもれなく場合分けできる type result = | Success of int | Error of string fun handle r = match r with | Success val -> do_something val | Error msg -> show_message msg

Slide 27

Slide 27 text

#JTF2019 #JTF2019_A 関数型 ● 関数にも値と同じ扱いで型が付く ○ 型を引数にとる高階関数も型で守られる ○ あらかじめ最初の引数のみ与えたりもできる # let plus x y = x + y;; val plus : int -> int -> int = # let answer = plus 42;; val answer : int -> int =

Slide 28

Slide 28 text

#JTF2019 #JTF2019_A プログラムの世界 ● P 型と Q 型の直積型 ○ P 型と Q 型の両方の値が手に入る ● P 型と Q 型の直和型 ○ P 型か Q 型の値の少なくとも片方は手に入る ● P 型から Q 型への関数型 ○ P 型の値を受け取って Q 型の値を作り出す

Slide 29

Slide 29 text

#JTF2019 #JTF2019_A 本日のテーマは定理証明

Slide 30

Slide 30 text

#JTF2019 #JTF2019_A 証明の世界 ● P かつ Q ○ P と Q の両方の証明が手に入る ● P または Q ○ P か Q の証明の少なくとも片方は手に入る ● P ならば Q ○ P の証明を受け取って Q の証明が作り出す

Slide 31

Slide 31 text

#JTF2019 #JTF2019_A プログラムの世界(再掲) ● P 型と Q 型の直積型 ○ P 型と Q 型の両方の値が手に入る ● P 型と Q 型の直和型 ○ P 型か Q 型の値の少なくとも片方は手に入る ● P 型から Q 型への関数型 ○ P 型の値を受け取って Q 型の値を作り出す

Slide 32

Slide 32 text

#JTF2019 #JTF2019_A こいつら何か似てる!

Slide 33

Slide 33 text

#JTF2019 #JTF2019_A Curry-Howard 同型対応 証明の世界 プログラムの世界 命題 型 命題 P の証明 P 型の値を得るプログラム P かつ Q P と Q の直積型 P または Q P と Q の直和型 P ならば Q P から Q への関数型

Slide 34

Slide 34 text

#JTF2019 #JTF2019_A もうちょっと実例が欲しい

Slide 35

Slide 35 text

#JTF2019 #JTF2019_A 例:三段論法 ● 証明の世界 ○ 前提:ソクラテスならば人間、人間ならば死ぬ、 ○ 帰結:ソクラテスならば死ぬ ● プログラムの世界 ○ 実装済み:f : double -> int、g : int -> string ○ 関数合成:x => g (f x) : double -> string

Slide 36

Slide 36 text

#JTF2019 #JTF2019_A 例:Modus Ponens ● 証明の世界 ○ 前提:P ならば Q である、P は成り立つ ○ 帰結:Q は成り立つ ● プログラムの世界 ○ 実装済み:f : int -> string、x : int ○ 関数適用:f x : string

Slide 37

Slide 37 text

#JTF2019 #JTF2019_A Section 2 のポイント ● 形式手法とは ○ システムを数学的な対象にマッピング ○ 大きく分けてモデル検査と定理証明がある ○ 静的型付き言語も広い意味で形式手法 ● 命題とプログラムの型とが対応する ○ 証明をプログラムにエンコードして扱える

Slide 38

Slide 38 text

#JTF2019 #JTF2019_A 定理証明と数学的帰納法 Lemma 3.

Slide 39

Slide 39 text

#JTF2019 #JTF2019_A 証明、大学受験以来なんだけど

Slide 40

Slide 40 text

#JTF2019 #JTF2019_A 例:0 から n までの和の公式

Slide 41

Slide 41 text

#JTF2019 #JTF2019_A 数学的帰納法 ● 証明を 2 ステップに分割 ○ 出発点となるケースについて成立を示す ○ 任意の場所の「一歩手前」まで成立したと仮定 ○ その仮定の下で「一歩先」での成立を示す ● 無限性のある主張が証明できる ○ 直接計算で「全ての自然数 n について」は不可能

Slide 42

Slide 42 text

#JTF2019 #JTF2019_A n = 0 の場合

Slide 43

Slide 43 text

#JTF2019 #JTF2019_A n = 0 の場合 直接計算で成り立つ

Slide 44

Slide 44 text

#JTF2019 #JTF2019_A n = n’ + 1 の場合 仮定: 結論:

Slide 45

Slide 45 text

#JTF2019 #JTF2019_A n = n’ + 1 の場合 仮定: 結論: 仮定を利用

Slide 46

Slide 46 text

#JTF2019 #JTF2019_A n = n’ + 1 の場合 仮定: 結論: 等式変形

Slide 47

Slide 47 text

#JTF2019 #JTF2019_A n’ に対する命題の証明から n’ + 1 に対する命題の証明を構成

Slide 48

Slide 48 text

#JTF2019 #JTF2019_A n’ に対するプログラムから n’ + 1 に対するプログラムを構成

Slide 49

Slide 49 text

#JTF2019 #JTF2019_A Curry-Howard 同型対応ふたたび 証明の世界 プログラムの世界 命題 型 命題 P の証明 P 型の値を得るプログラム P かつ Q P と Q の直積型 P または Q P と Q の直和型 P ならば Q P から Q への関数型 数学的帰納法 再帰関数

Slide 50

Slide 50 text

#JTF2019 #JTF2019_A Coq https://github.com/coq/coq

Slide 51

Slide 51 text

#JTF2019 #JTF2019_A

Slide 52

Slide 52 text

#JTF2019 #JTF2019_A Coq で和を計算する関数 Inductive nat : Type := | O : nat | S : nat -> nat. Fixpoint sum_upto (n : nat) : nat := match n with | O => 0 | S n’ => n + sum_upto n’ end. Compute (sum_upto 100). (* 5050 *)

Slide 53

Slide 53 text

#JTF2019 #JTF2019_A Coq で和を計算する関数 Inductive nat : Type := | O : nat | S : nat -> nat. Fixpoint sum_upto (n : nat) : nat := match n with | O => 0 | S n’ => n + sum_upto n’ end. Compute (sum_upto 100). (* 5050 *) 自然数を直和型で定義

Slide 54

Slide 54 text

#JTF2019 #JTF2019_A Coq で和を計算する関数 Inductive nat : Type := | O : nat | S : nat -> nat. Fixpoint sum_upto (n : nat) : nat := match n with | O => 0 | S n’ => n + sum_upto n’ end. Compute (sum_upto 100). (* 5050 *) パターンマッチして 再帰

Slide 55

Slide 55 text

#JTF2019 #JTF2019_A Coq で証明すべき定理 Theorem sum_formula : forall n : nat, 2 * (sum_upto n) = n * S n. Proof. intro n. induction [| n’ H_n’ ] - (* n = 0 の場合の証明 *) ... - (* n = S n’ の場合の証明 *) ... Qed.

Slide 56

Slide 56 text

#JTF2019 #JTF2019_A Coq で証明すべき定理 Theorem sum_formula : forall n : nat, 2 * (sum_upto n) = n * S n. Proof. intro n. induction [| n’ H_n’ ] - (* n = 0 の場合の証明 *) ... - (* n = S n’ の場合の証明 *) ... Qed. パターンマッチに よる場合分け

Slide 57

Slide 57 text

#JTF2019 #JTF2019_A 実際に証明して見せてよ?

Slide 58

Slide 58 text

#JTF2019 #JTF2019_A (ライブ・プルービング)

Slide 59

Slide 59 text

#JTF2019 #JTF2019_A Program Extraction ● Coq 単体では I/O などが記述できない ○ 関数の性質を証明しても実装に組み込めない ● 通常の言語のコードに変換できる ○ 重要な関数のみ証明し、変換して使用する ○ デフォルトでは OCaml、Haskell、Scheme ○ プラグインで拡張することも可能

Slide 60

Slide 60 text

#JTF2019 #JTF2019_A Section 3 のポイント ● 数学的帰納法の利用 ○ プログラム的には再帰関数として実現できる ● Coq による対話的証明 ○ 書き換えによりゴールを引き寄せて進む ● Extraction によるコード生成 ○ 重要な関数を証明してプロダクトに埋め込む

Slide 61

Slide 61 text

#JTF2019 #JTF2019_A 分散システムの証明 Lemma 4.

Slide 62

Slide 62 text

#JTF2019 #JTF2019_A 数学っぽいのじゃなくて 「プログラムの証明」が知りたい

Slide 63

Slide 63 text

#JTF2019 #JTF2019_A 例:分散ロックサーバ ● サーバとエージェントが存在 ○ エージェントは LockMsg / UnlockMsg を要求 ○ サーバは LockMsg を先着順で処理 ○ ロックを与える場合は GrantMsg を送信 ● 二重ロックを防ぎたい ○ 非同期リクエストでも正しく処理できるか?

Slide 64

Slide 64 text

#JTF2019 #JTF2019_A Server Agent (A1) Other Process Agent (A2) Other Process Client Client

Slide 65

Slide 65 text

#JTF2019 #JTF2019_A Server Agent (A1) Other Process Agent (A2) Other Process Client Client Lock LockMsg

Slide 66

Slide 66 text

#JTF2019 #JTF2019_A Server Agent (A1) Other Process Agent (A2) Other Process Client Client LockMsg Lock

Slide 67

Slide 67 text

#JTF2019 #JTF2019_A Server Agent (A1) Other Process Agent (A2) Other Process Client Client GrantMsg Grant

Slide 68

Slide 68 text

#JTF2019 #JTF2019_A Server Agent (A1) Other Process Agent (A2) Other Process Client Client Unlock UnlockMsg

Slide 69

Slide 69 text

#JTF2019 #JTF2019_A Server Agent (A1) Other Process Agent (A2) Other Process Client Client GrantMsg Grant

Slide 70

Slide 70 text

#JTF2019 #JTF2019_A これゼロから書くの辛くない?

Slide 71

Slide 71 text

#JTF2019 #JTF2019_A Verdi https://github.com/uwplse/verdi

Slide 72

Slide 72 text

#JTF2019 #JTF2019_A 証明フレームワーク Verdi ● Coq 用のフレームワーク ○ 分散システムを体系的に証明できる ○ Extraction してランタイムに組み込み可能 ● ユーザが定義するシステムの動作 ○ ノード、外部入出力、内部メッセージ ○ 外部入力、内部メッセージに対するハンドラ

Slide 73

Slide 73 text

#JTF2019 #JTF2019_A Server Agent (A1) Other Process Agent (A2) Other Process Client Client 外部入出力 内部メッセージ

Slide 74

Slide 74 text

#JTF2019 #JTF2019_A 外部入力ハンドラ(エージェント) HandleInp (n: Name) (s: State n) (inp: Inp) := match n with | Server => nop | Agent n => match inp with | Lock => send (Server, LockMsg) | Unlock => if s == true then ( s := false;; send (Server, UnlockMsg))

Slide 75

Slide 75 text

#JTF2019 #JTF2019_A 外部入力ハンドラ(エージェント) HandleInp (n: Name) (s: State n) (inp: Inp) := match n with | Server => nop | Agent n => match inp with | Lock => send (Server, LockMsg) | Unlock => if s == true then ( s := false;; send (Server, UnlockMsg)) Lock が来たら サーバに LockMsg

Slide 76

Slide 76 text

#JTF2019 #JTF2019_A 外部入力ハンドラ(エージェント) HandleInp (n: Name) (s: State n) (inp: Inp) := match n with | Server => nop | Agent n => match inp with | Lock => send (Server, LockMsg) | Unlock => if s == true then ( s := false;; send (Server, UnlockMsg)) Unlock が来たら 自分の持つフラグを下ろして サーバに UnlockMsg

Slide 77

Slide 77 text

#JTF2019 #JTF2019_A メッセージハンドラ(サーバ) HandleMsg (n: Name) (s: State n) (src: Name)(msg: Msg) := match n with | Server => nop match msg with | LockMsg => if s == [] then send (src, GrantMsg);; s := s ++ [src] | UnlockMsg => s := tail s;; if s != [] then send (head s, GrantMsg)

Slide 78

Slide 78 text

#JTF2019 #JTF2019_A メッセージハンドラ(サーバ) HandleMsg (n: Name) (s: State n) (src: Name)(msg: Msg) := match n with | Server => nop match msg with | LockMsg => if s == [] then send (src, GrantMsg);; s := s ++ [src] | UnlockMsg => s := tail s;; if s != [] then send (head s, GrantMsg) リストの先頭が ロック保持中のエージェント

Slide 79

Slide 79 text

#JTF2019 #JTF2019_A メッセージハンドラ(エージェント) HandleMsg (n: Name) (s: State n) (src: Name)(msg: Msg) := match n with | Agent n => match msg with | GrantMsg => s := true;; output Grant

Slide 80

Slide 80 text

#JTF2019 #JTF2019_A メッセージハンドラ(エージェント) HandleMsg (n: Name) (s: State n) (src: Name)(msg: Msg) := match n with | Agent n => match msg with | GrantMsg => s := true;; output Grant サーバから返事が来たら 自分の持つフラグを立てて 外部システムに Grant 通知

Slide 81

Slide 81 text

#JTF2019 #JTF2019_A ノードの動作はわかった。証明は?

Slide 82

Slide 82 text

#JTF2019 #JTF2019_A Verdi によるシステムの定式化 ● 三つ組 (Σ, P, T) で「世界」を定義 ○ Σ : 各ノードが持つ内部状態 ○ P : 通信路の途中にいるパケットの多重集合 ○ T : 外部入出力イベントの列(トレース) ● 世界間の到達可能関係を考える ○ システムの挙動はグラフ構造として表される

Slide 83

Slide 83 text

#JTF2019 #JTF2019_A Σ = { Server : [], A1: false, A2: false } P = { (A1, LockMsg), (A2, LockMsg)} T = [ , ] Σ = { Server : [], A1: false, A2: false } P = { (A1, LockMsg) } T = [ ] Σ = { Server : [], A1: false, A2: false } P = {} T = []

Slide 84

Slide 84 text

#JTF2019 #JTF2019_A Σ = { Server : [], A1: false, A2: false } P = { (A1, LockMsg), (A2, LockMsg)} T = [ , ] Σ = { Server : [], A1: false, A2: false } P = { (A1, LockMsg) } T = [ ] Σ = { Server : [], A1: false, A2: false } P = {} T = [] A1 が Lock を要求

Slide 85

Slide 85 text

#JTF2019 #JTF2019_A Σ = { Server : [], A1: false, A2: false } P = { (A1, LockMsg), (A2, LockMsg)} T = [ , ] Σ = { Server : [], A1: false, A2: false } P = { (A1, LockMsg) } T = [ ] Σ = { Server : [], A1: false, A2: false } P = {} T = [] A2 が Lock を要求

Slide 86

Slide 86 text

#JTF2019 #JTF2019_A Σ = { Server : [A1, A2], A1: false, A2: false } P = {} T = [ , , ] Σ = { Server : [A1], A1: true, A2: false } P = { (A2, LockMsg) } T = [ , , ] Σ = { Server : [A1], A1: false, A2: false } P = { (A1, GrantMsg), (A2, LockMsg) } T = [ , ] Server が A1 を Grant

Slide 87

Slide 87 text

#JTF2019 #JTF2019_A Σ = { Server : [A1, A2], A1: false, A2: false } P = {} T = [ , , ] Σ = { Server : [A1], A1: true, A2: false } P = { (A2, LockMsg) } T = [ , , ] Σ = { Server : [A1], A1: false, A2: false } P = { (A1, GrantMsg), (A2, LockMsg) } T = [ , ] A1 がGrant を受信

Slide 88

Slide 88 text

#JTF2019 #JTF2019_A Σ = { Server : [A1, A2], A1: false, A2: false } P = {} T = [ , , ] Σ = { Server : [A1], A1: true, A2: false } P = { (A2, LockMsg) } T = [ , , ] Σ = { Server : [A1], A1: false, A2: false } P = { (A1, GrantMsg), (A2, LockMsg) } T = [ , ] Server が A2 の要求を認識

Slide 89

Slide 89 text

#JTF2019 #JTF2019_A Σ = { Server : [A2], A1: false, A2: true } P = { () } T = [ , , , , ] Σ = { Server : [A2], A1: false, A2: false } P = { (A2, GrantMsg) } T = [ , , , ] Σ = { Server : [A1, A2], A1: true, A2: false } P = { (A1, UnlockMsg) } T = [ , , , ] A1 が Unlock を要求

Slide 90

Slide 90 text

#JTF2019 #JTF2019_A Σ = { Server : [A2], A1: false, A2: true } P = { () } T = [ , , , , ] Σ = { Server : [A2], A1: false, A2: false } P = { (A2, GrantMsg) } T = [ , , , ] Σ = { Server : [A1, A2], A1: true, A2: false } P = { (A1, UnlockMsg) } T = [ , , , ] Server が A2 を Grant

Slide 91

Slide 91 text

#JTF2019 #JTF2019_A Σ = { Server : [A2], A1: false, A2: true } P = { () } T = [ , , , , ] Σ = { Server : [A2], A1: false, A2: false } P = { (A2, GrantMsg) } T = [ , , , ] Σ = { Server : [A1, A2], A1: true, A2: false } P = { (A1, UnlockMsg) } T = [ , , , ] A2 が Grant を受信

Slide 92

Slide 92 text

#JTF2019 #JTF2019_A 実際に何が証明できる?

Slide 93

Slide 93 text

#JTF2019 #JTF2019_A Verdi による仕様記述 ● トレース T に対して仕様を定義 ○ システムの内部メッセージには言及しない ● 例:二重ロックしない ○ 任意の a1, a2 を考えたとき、 T = t1 ++ [] ++ t2 ++ [] ++ t3 の形で表せるなら、t2 は を含む

Slide 94

Slide 94 text

#JTF2019 #JTF2019_A Verdi による証明 ● 世界間の遷移に対して数学的帰納法 ○ 初期状態の世界で仕様が成立 ○ ある世界で仕様が成立すると仮定したとき、 そこから 1 ステップ進んだすべての世界で成立 仮定 World World World World 証明 証明 証明

Slide 95

Slide 95 text

#JTF2019 #JTF2019_A 形式手法の分類(再掲) ● モデル検査 ○ システムが取りうる値を列挙して探索 ○ 有限個のパターンに収まれば自動化できる ● 定理証明 ○ いわゆる数学的な証明をプログラム的に表現 ○ 真に無限個のパターンを扱うことができる

Slide 96

Slide 96 text

#JTF2019 #JTF2019_A 参考:モデル検査の場合 ● 世界の遷移が作るグラフに対して探索 ○ 条件を満たさない T を持つ世界を探す ○ 世界が有限個になるようにモデル化する必要 Initial World World World World World World T が不正 World World World World

Slide 97

Slide 97 text

#JTF2019 #JTF2019_A 分散システムの不安定さの話は?

Slide 98

Slide 98 text

#JTF2019 #JTF2019_A 例:メッセージ重複への対処 ● Verdi が自動でラッパを適用 ○ メッセージにユニークな ID 番号を振る ○ 各ノードに「すでに読んだ ID」を記憶させる ○ すでに読んだ ID のメッセージは無視する ● 二重ロック禁止の仕様は守れるか? ○ 重複なしの場合の証明が成り立たない可能性

Slide 99

Slide 99 text

#JTF2019 #JTF2019_A Σ = { Server : [], read: { 000, … } A1: false, read: { 001, … } A2: false, read: { 002, … } } P = { (042, A1, LockMsg), } T = [ ..., ] Σ = { Server : [], read: { 000, … } A1: false, read: { 001, … } A2: false, read: { 002, … } } P = { (042, A1, LockMsg), (042, A1, LockMsg), } T = [ ..., ] 重複ありの場合 重複なしの場合

Slide 100

Slide 100 text

#JTF2019 #JTF2019_A Σ = { Server : [], read: { 000, … } A1: false, read: { 001, … } A2: false, read: { 002, … } } P = { (042, A1, LockMsg), } T = [ ..., ] Σ = { Server : [], read: { 000, … } A1: false, read: { 001, … } A2: false, read: { 002, … } } P = { (042, A1, LockMsg), (042, A1, LockMsg), } T = [ ..., ] Σ = { Server : [] A1: false A2: false } P = { (A1, LockMsg), } T = [ ..., ] 重複ありの場合 重複なしの場合 unwrap はトレース T を保存

Slide 101

Slide 101 text

#JTF2019 #JTF2019_A Σ = { Server : [], read: { 000, … } A1: false, read: { 001, … } A2: false, read: { 002, … } } P = { (042, A1, LockMsg), } T = [ ..., ] Σ = { Server : [], read: { 000, … } A1: false, read: { 001, … } A2: false, read: { 002, … } } P = { (042, A1, LockMsg), (042, A1, LockMsg), } T = [ ..., ] Σ = { Server : [] A1: false A2: false } P = { (A1, LockMsg), } T = [ ..., ] 重複ありの場合 重複なしの場合 unwrap 先でも 対応する(自明な) 遷移が存在

Slide 102

Slide 102 text

#JTF2019 #JTF2019_A Initial World 重複ありの場合 重複なしの場合 証明済: 到達可能な任意の世界で トレース T は条件を満たす World World World Initial World World World World World

Slide 103

Slide 103 text

#JTF2019 #JTF2019_A Initial World 重複ありの場合 重複なしの場合 証明済: 到達可能な任意の世界で トレース T は条件を満たす World World World Initial World World World World World 対応する遷移が存在 = ラップした状態でも 到達可能性は変わらない

Slide 104

Slide 104 text

#JTF2019 #JTF2019_A Initial World 重複ありの場合 重複なしの場合 証明済: 到達可能な任意の世界で トレース T は条件を満たす World World World Initial World World World World World 実は読み替え可能: 到達可能な任意の世界で トレース T は条件を満たす

Slide 105

Slide 105 text

#JTF2019 #JTF2019_A Verified System Transformer ● より複雑な意味論への変換 ○ メッセージとハンドラを自動でラップ ○ 故障への対応について改めて考える必要がない ● 実装だけでなく証明も変換可能 ○ 意味論の間にある模倣関係を利用 ○ 仕様をトレースに制限したのが効いている

Slide 106

Slide 106 text

#JTF2019 #JTF2019_A 故障を含む Verdi の意味論(一部) ● Duplicating Semantics ○ メッセージが通信路上で複製される ● Dropping Semantics ○ メッセージが消失、タイムアウトする ● Node Failure ○ ノードが落ちたり、勝手に復活したりする

Slide 107

Slide 107 text

#JTF2019 #JTF2019_A vard: 証明された Raft ベース KVS https://github.com/uwplse/verdi-raft

Slide 108

Slide 108 text

#JTF2019 #JTF2019_A Section 4 のポイント ● Verdi による分散システムの証明 ○ Runtime とセットで実行可能コードを生成 ● システムのトレースで仕様を表現 ○ 複雑に見えるが本質は数学的帰納法 ● Verified System Transformer ○ 故障に対応したバージョンに変換できる

Slide 109

Slide 109 text

#JTF2019 #JTF2019_A 本日のまとめ Theorem.

Slide 110

Slide 110 text

#JTF2019 #JTF2019_A 本日のまとめ ● 形式手法による検証 ○ テストでは扱いづらい分散システムにも使える ● Coq による定理証明 ○ プログラムと証明との Curry-Howard 対応 ● 証明フレームワーク Verdi ○ 分散システムの証明が可能、Raft も証明済み

Slide 111

Slide 111 text

#JTF2019 #JTF2019_A おまけ:モデル検査について Corollary.

Slide 112

Slide 112 text

#JTF2019 #JTF2019_A 【PR】分散システム + モデル検査 https://speakerdeck.com/ytaka23/builderscon-tokyo-2019

Slide 113

Slide 113 text

#JTF2019 #JTF2019_A 【PR】モデル検査ハンズオン ハッシュタグ「#モデル検査ハンズオン」で検索

Slide 114

Slide 114 text

#JTF2019 #JTF2019_A Qed. #JTF2019 #JTF2019_A Prove Your Distributed Systems! Presented By チェシャ猫 (@y_taka_23)