Upgrade to Pro — share decks privately, control downloads, hide ads and more …

「厳密な共通言語」としての形式手法 #devsumi / Developers Summit 2020

y_taka_23
February 13, 2020
5.5k

「厳密な共通言語」としての形式手法 #devsumi / Developers Summit 2020

Developers Summit 2020 で使用したスライドです。

言葉というものは曖昧です。複数人が「ともにつくる」システムにおいて、メンバ間で仕様を正しく共有することは非常に重要ですが、一方で言葉の裏側に隠された「暗黙の仮定」を見抜くことは簡単ではありません。このような仕様の曖昧性への対抗手段として、本セッションでは「形式手法」を紹介します。形式手法ではシステムの挙動を数学的に記述することにより、自然言語の持つ曖昧性を排除し、仕様が満たされるかどうかを厳密に検証することが可能になります。あなたの頭の中にある仕様がどのように「数学的な記述」に変換されるのか、具体例を通して体験してみませんか?

イベント概要:https://event.shoeisha.jp/devsumi/20200213/session/2380/

y_taka_23

February 13, 2020
Tweet

More Decks by y_taka_23

Transcript

  1. #devsumi #devsumiF #devsumi #devsumiF 本日のアジェンダ • システムの仕様と曖昧性 • 形式手法とツールの概要 •

    モデリングを通した仕様の明確化 • 環境と相互作用する複雑な挙動の解析
  2. #devsumi #devsumiF #devsumi #devsumiF 曖昧さはイメージの限界から生まれる • 仕様を理解した「つもり」の思い込み ◦ ポーンが成るなら当然、同色の駒である ◦

    比較可能な値なら当然、大きい方が取れる ◦ 基準値を上回るかどうかなら当然、最小値を見ればよい • 最初に思いついたもの以外は見落としがち ◦ 指摘されるまでは見落としていること自体気づかない
  3. #devsumi #devsumiF #devsumi #devsumiF とある決済サービスの仕様 • システム横断的な決済基盤 ◦ 複数の EC

    サイトから利用される • 複数の決済手段をサポート ◦ 銀行引き落とし / ポイント払い / ギフト券払い ◦ 各々が別々のマイクロサービスとして稼働 ◦ 複数の手段に按分して支払うことも可能
  4. #devsumi #devsumiF #devsumi #devsumiF TCC パターン • 全体を二つのフェイズに分割する • Try

    フェイズ ◦ 各メンバーに更新内容を仮登録させる • Confirm / Cancel フェイズ ◦ 全員から準備 OK が返って来たら改めて Confirm ◦ 一人でも失敗した場合には全員に Cancel させる
  5. #devsumi #devsumiF #devsumi #devsumiF TCC パターンの「曖昧」な部分 • 未定義動作のないシステムの正確な挙動 ◦ 各コンポーネントの動作と、非同期に動いた系全体の挙動

    • 保証したい仕様の意味 ◦ 不整合とは何か、どういう状況なら許容できるのか • アルゴリズム外の環境との相互作用 ◦ 動作している各サーバの信頼性、ネットワークの状態
  6. #devsumi #devsumiF #devsumi #devsumiF 分散コンピューティングの落とし穴 • ネットワークは信頼できる • レイテンシはゼロである •

    帯域幅は無限である • ネットワークはセキュアである • ネットワークトポロジは不変である • 管理者は一人である • トランスポートコストはゼロである • ネットワークは均質である 参考文献:https://web.archive.org/web/20171107014323/https://blog.fogcreek.com/eight-fallacies-of-distributed-computing-tech-talk/
  7. #devsumi #devsumiF #devsumi #devsumiF 分散コンピューティングの落とし穴 • ネットワークは信頼できる • レイテンシはゼロである •

    帯域幅は無限である • ネットワークはセキュアである • ネットワークトポロジは不変である • 管理者は一人である • トランスポートコストはゼロである • ネットワークは均質である イメージの限界による曖昧さの発生 参考文献:https://web.archive.org/web/20171107014323/https://blog.fogcreek.com/eight-fallacies-of-distributed-computing-tech-talk/
  8. #devsumi #devsumiF #devsumi #devsumiF 曖昧性ハンドリングの重要性 • アーキテクチャの大規模・複雑化 ◦ マイクロサービス間の疎結合性 ◦

    チーム間での仕様や制約の合意のコスト ◦ マネージドインフラにおける変えられない前提条件 • 開発環境やチーム構成の多様化 ◦ 均質な前提知識やイメージを期待できない
  9. #devsumi #devsumiF #devsumi #devsumiF テストでカバーできない点 • テストケースの網羅性・再現性 ◦ テストケースとして明示的に思いつく必要がある ◦

    非同期性による実行パスのランダム性 • 実装が先行する必要性 ◦ 実際にテスト対象となるシステムの実装が必要 ◦ 暗黙の仮定を仕様バグとして作り込んでしまうと手戻り
  10. #devsumi #devsumiF #devsumi #devsumiF 第 1 章のまとめ • 仕様を厳密に表現するのは意外と難しい ◦

    イメージできる範囲の挙動に引きずられる ◦ 知識や前提が共有されている必要がある • 実システムの挙動は大量の曖昧さを含む ◦ テストは曖昧さを排除する方法の一つだが万能ではない ◦ システムの性質をより正確に記述できる方法論が欲しい
  11. #devsumi #devsumiF #devsumi #devsumiF 形式手法とは • システムを数学的対象により表現 ◦ その対象の性質に基づいて検証を行う ◦

    対象として何を選ぶかによってツールの性質が決まる • テストに対する優位点 ◦ テストケースの抜けや漏れが生じない ◦ 実装ではなく仕様や設計に対して検査ができる
  12. #devsumi #devsumiF #devsumi #devsumiF 形式手法の分類 • モデル検査 ◦ システムが取りうる状態を列挙して探索 ◦

    有限個の探索に帰着できる範囲で自動化が可能 • 定理証明 ◦ いわゆる数学的な証明をプログラムとして表現 ◦ 本当に無限個のパターンがある対象を扱える
  13. #devsumi #devsumiF #devsumi #devsumiF TLA+ の特徴 • モデル検査系のツール ◦ Eclipse

    ベースの IDE(TLA Toolbox)とセットで提供 ◦ Lamport(Paxos の発案者)が中心となって開発 ◦ Temporal Logic of Actions と呼ばれる論理がベース • システムを状態の列として表現 ◦ 起こり得る動作の前後で成り立つ関係を記述
  14. #devsumi #devsumiF #devsumi #devsumiF TLA+ の採用事例 • AWS DynamoDB、S3 ◦

    https://lamport.azurewebsites.net/tla/formal-methods-amazon.pdf • CockroachDB の並列コミット ◦ https://www.cockroachlabs.com/blog/parallel-commits/ • FINAL FANTASY XV POCKET EDITION ◦ https://d1.awsstatic.com/events/jp/2018/summit/tokyo/customer/37.pdf
  15. #devsumi #devsumiF #devsumi #devsumiF サンプル:LampLighter • まず非常に単純なシステムを考える • 世界にはフラグがひとつだけ存在する ◦

    変数 lamp で表す • プロセスがひとつだけ稼働している ◦ 断続的にフラグを反転させる ◦ 式で書けば lamp = ~lamp
  16. #devsumi #devsumiF VARIABLE lamp (* 初期状態 *) Init == lamp

    = TRUE (* 状態遷移時の関係 *) Next == lamp' = ~lamp
  17. #devsumi #devsumiF #devsumi #devsumiF サンプル:TwinLighter • フラグがふたつ存在する ◦ 変数名は lamp1

    と lamp2 • プロセスがふたつ非同期で稼働している ◦ それぞれ P1 と P2 とする ◦ P1 は lamp1 を、P2 は lamp2 を反転させる ◦ 同時には動かないが、順番はランダムで交互とも限らない
  18. #devsumi #devsumiF lamp1: false lamp2: true lamp1: false lamp2: false

    lamp1: true lamp2: true lamp1: true lamp2: false
  19. #devsumi #devsumiF VARIABLE lamp (* 初期状態 *) Init == lamp1

    = TRUE /\ lamp2 = TRUE (* 状態遷移時の関係 *) Next == lamp1' = ~lamp1 \/ lamp2' = ~lamp2
  20. #devsumi #devsumiF #devsumi #devsumiF PlusCal による生成 • 生の TLA+ を書くのは人間には辛い

    ◦ そもそも直接、遷移を書けるなら最初から曖昧性などない • PlusCal と呼ばれる DSL からコード生成 ◦ Pascal 風の記法と C 言語風の記法がある • AWS の事例論文でも有効性を強調 ◦ 現場のプログラマからの心理的障壁を下げる効果
  21. #devsumi #devsumiF --algorithm LampLighter process P \in { 1, 2

    } variable lamp = TRUE; begin while TRUE do lamp := ~lamp; end while; end process; end algorithm;
  22. #devsumi #devsumiF --algorithm LampLighter process P \in { 1, 2

    } variable lamp = TRUE; begin while TRUE do lamp := ~lamp; end while; end process; end algorithm; ループっぽい記法
  23. #devsumi #devsumiF --algorithm LampLighter process P \in { 1, 2

    } variable lamp = TRUE; begin while TRUE do lamp := ~lamp; end while; end process; end algorithm; 複数プロセスのインスタンス化
  24. #devsumi #devsumiF #devsumi #devsumiF 第 2 章のまとめ • 形式手法によるシステムの表現 ◦

    システムを数学的な対象にマッピング ◦ 厳密な記述を強制し「イメージの外」の余地を残さない • 今回は TLA+ をモデル検査器として使用 ◦ 研究用だけでなく、国内を含めて産業界で実績がある ◦ PlusCal を使用してプログラムっぽく書ける
  25. #devsumi #devsumiF #devsumi #devsumiF 3. モデリングと仕様の明確化 Formal Modeling of the

    Specifications 参考文献:http://muratbuffalo.blogspot.com/2018/12/2-phase-commit-and-beyond.html
  26. #devsumi #devsumiF #devsumi #devsumiF TCC パターンの「曖昧」な部分 • 未定義動作のないシステムの正確な挙動 ◦ 各コンポーネントの動作と、非同期に動いた系全体の挙動

    • 保証したい仕様の意味 ◦ 不整合とは何か、どういう状況なら許容できるのか • アルゴリズム外の環境との相互作用 ◦ 動作している各サーバの信頼性、ネットワークの状態
  27. #devsumi #devsumiF fair process Payment begin (* 決済サービス側の挙動 *) end

    process; fair process Method \in METHOD begin (* 決済手段側の挙動 *) end process;
  28. #devsumi #devsumiF #devsumi #devsumiF 決済サービス側の挙動 • まず Try リクエストを出す ◦

    モデリングは Try した状態からスタート • 確定なら Confirm リクエストを出す ◦ 全員が OK のレスポンスを返した場合 • 取り消しなら Cancel リクエストを出す ◦ 一人でも OK のレスポンスを返さなかった場合
  29. #devsumi #devsumiF variable paymentState = "try" fair process Payment begin

    either (* Confirm リクエストを発行 *) await confirmable; paymentState = "confirm"; or (* Cancel リクエストを発行 *) await cancerable; paymentState = "cancel": end either; end process;
  30. #devsumi #devsumiF variable paymentState = "try" fair process Payment begin

    either (* Confirm リクエストを発行 *) await confirmable; paymentState = "confirm"; or (* Cancel リクエストを発行 *) await cancerable; paymentState = "cancel": end either; end process; either ... or … で条件分岐
  31. #devsumi #devsumiF define (* 全員が準備 OK なら確定できる *) confirmable ==

    \A m \in METHOD: methodState[m] \in { "reserved" } (* 一人でもキャンセルなら全員キャンセル *) cancerable == \E m \in METHOD: methodState[m] \in { "canceled" } end define;
  32. #devsumi #devsumiF define (* 全員が準備 OK なら確定できる *) confirmable ==

    \A m \in METHOD: methodState[m] \in { "reserved" } (* 一人でもキャンセルなら全員キャンセル *) cancerable == \E m \in METHOD: methodState[m] \in { "canceled" } end define; forall: 任意の ... exists: 少なくとも一つ存在
  33. #devsumi #devsumiF #devsumi #devsumiF 決済手段側の挙動 • Try に対して仮登録を行う ◦ 待機中に

    Try リクエストを受けたら仮登録状態になる • Confirm に対して確定処理を行う ◦ 自分が仮登録状態ならそれを確定状態に変える • Cancel に対してキャンセル処理を行う ◦ さらに自分自身が失敗した場合もキャンセル状態になる
  34. #devsumi #devsumiF fair process Method \in METHOD begin while methodState[self]

    \in { "idling", "reserved" } do either (* 待機 → 仮登録 *) or (* 仮登録 → 確定 *) or (* キャンセル *) end either; end while; end process; either ... or … で 3 通りに分岐
  35. #devsumi #devsumiF either (* 待機 → 仮登録 *) await methodState[self]

    = "idling"; methodState[self] := "reserved"; or (* 仮登録 → 確定 *) await methodState[self] = "reserved" /\ paymentState = "confirm"; methodState[self] := "confirmed"; or (* キャンセル *) ... end either;
  36. #devsumi #devsumiF either (* 待機 → 仮登録 *) await methodState[self]

    = "idling"; methodState[self] := "reserved"; or (* 仮登録 → 確定 *) await methodState[self] = "reserved" /\ paymentState = "confirm"; methodState[self] := "confirmed"; or (* キャンセル *) ... end either; 仮登録状態になる
  37. #devsumi #devsumiF either (* 待機 → 仮登録 *) await methodState[self]

    = "idling"; methodState[self] := "reserved"; or (* 仮登録 → 確定 *) await methodState[self] = "reserved" /\ paymentState = "confirm"; methodState[self] := "confirmed"; or (* キャンセル *) ... end either; 確定状態になる
  38. #devsumi #devsumiF either (* 待機 → 仮登録 *) ... or

    (* 仮登録 → 確定 *) ... or (* キャンセル *) await methodState[self] = "idling" \/ (methodState[self] = "reserved" /\ paymentState = "cancel"); methodState[self] := "canceled"; end either;
  39. #devsumi #devsumiF either (* 待機 → 仮登録 *) ... or

    (* 仮登録 → 確定 *) ... or (* キャンセル *) await methodState[self] = "idling" \/ (methodState[self] = "reserved" /\ paymentState = "cancel"); methodState[self] := "canceled"; end either; キャンセル状態になる
  40. #devsumi #devsumiF #devsumi #devsumiF TCC パターンの「曖昧」な部分 • 未定義動作のないシステムの正確な挙動 ◦ 各コンポーネントの動作と、非同期に動いた系全体の挙動

    • 保証したい仕様の意味 ◦ 不整合とは何か、どういう状況なら許容できるのか • アルゴリズム外の環境との相互作用 ◦ 動作している各サーバの信頼性、ネットワークの状態
  41. #devsumi #devsumiF #devsumi #devsumiF 検証したい TCC の性質 • トランザクションとしての一貫性 ◦

    Confirmed と Canceded が混在しないか? ◦ 混在するとデータに不整合が発生する • 一連の手続きが完了する保証 ◦ いつかは Confirmed もしくは Canceled になる? ◦ デッドロックや無限ループに陥ると困る
  42. #devsumi #devsumiF #devsumi #devsumiF 安全性と活性 • 安全性 (Safety) ◦ 何か悪いことが「起こらない」ことを要求

    ◦ 通常のプログラムでのアサーションに近い • 活性 (Liveness)一連の手続きが完了する保証 ◦ 何か良いことが「起こる」ことを要求 ◦ 何もしないシステムは無意味なので活性は必須
  43. #devsumi #devsumiF #devsumi #devsumiF 時相論理 (Temporal Logic) • 通常の論理式に記号を追加した体系 ◦

    [] A:現時点以降、常に A が成り立つ ◦ <> A:現時点以降、いつかは A が成り立つ • 状態の列に対して真偽を判定 ◦ 時間的に幅がある振る舞いについて性質を記述できる ◦ 式の真偽の与え方は何種類か存在する (LTL、CTL など)
  44. #devsumi #devsumiF <> A : 現時点以降、いずれは A が成り立つ A true

    false [] A : 現時点以降、常に A が成り立つ A A A A A false true
  45. #devsumi #devsumiF (* 確定とキャンセルが混在しない *) Consistency == \A m1, m2

    \in METHOD: ~ (methodState[m1] = "confirmed" /\ methodState[m1] = "canceled") (* どの決済手段もいつかは確定もしくはキャンセルになる *) Completed == <>(\A m \in METHOD: methodState[m] \in { "confirmed", "canceled" })
  46. #devsumi #devsumiF (* 確定とキャンセルが混在しない *) Consistency == \A m1, m2

    \in METHOD: ~ (methodState[m1] = "confirmed" /\ methodState[m1] = "canceled") (* どの決済手段もいつかは確定もしくはキャンセルになる *) Completed == <>(\A m \in METHOD: methodState[m] \in { "confirmed", "canceled" }) 「いつかは」= 時相論理
  47. #devsumi #devsumiF #devsumi #devsumiF 検証したい TCC の性質(再掲) • トランザクションとしての一貫性 ◦

    Confirmed と Canceded が混在しないか? ◦ 混在するとデータに不整合が発生する • 一連の手続きが完了する保証 ◦ いつかは Confirmed もしくは Canceled になる? ◦ デッドロックや無限ループに陥ると困る
  48. #devsumi #devsumiF #devsumi #devsumiF 第 3 章のまとめ • TLA+ による

    TCC のモデリング ◦ 各遷移が起こりうる条件を分割整理し、漏れをなくす ◦ 各コンポーネントごとに記述すれば合成してくれる • 時相論理による検証 ◦ 安全性と活性の両サイドを検証 ◦ テストでは難しい「一連の振る舞い」が検査可能
  49. #devsumi #devsumiF #devsumi #devsumiF TCC パターンの「曖昧」な部分 • 未定義動作のないシステムの正確な挙動 ◦ 各コンポーネントの動作と、非同期に動いた系全体の挙動

    • 保証したい仕様の意味 ◦ 不整合とは何か、どういう状況なら許容できるのか • アルゴリズム外の環境との相互作用 ◦ 動作している各サーバの信頼性、ネットワークの状態
  50. #devsumi #devsumiF #devsumi #devsumiF 決済サービスの故障 • 先ほどのモデリングは故障しない前提 ◦ 直感的なイメージが難しいのはむしろ異常系 ◦

    故障の発生はランダムなので分岐が爆発的に増える • Confirm または Cancel リクエスト後に故障 ◦ 故障後はいかなるリクエストも発しない ◦ 故障後はずっと故障していて、自動で復旧はしない
  51. #devsumi #devsumiF 故障しない 性質の良いシステム Crash-Stop 故障 : 故障後、完全に沈黙 Crash-Recovery 故障

    : 故障後、復活の可能性 ビザンチン故障:サーバが任意の応答を返す 性質の悪いシステム
  52. #devsumi #devsumiF #devsumi #devsumiF エラートレースの内容 1. 決済手段サーバ 1, 2, 3

    の順に Try が到達し 3 台とも Reserved 状態に 2. 決済サーバが Confirm リクエスト動作中に故障 3. Confirm リクエストは決済手段サーバに到達せず 4. 決済手段サーバは Reserved のまま動けない
  53. #devsumi #devsumiF #devsumi #devsumiF フェイルオーバによる冗長化 • 決済サービスの代替サーバを用意 ◦ 代替サーバは故障しないと仮定する •

    代替サーバの挙動 ◦ 本体が故障していないかを監視 ◦ 本体が正常なら何もしない ◦ 故障していたら代わりに Confirm / Cancel リクエスト
  54. #devsumi #devsumiF #devsumi #devsumiF エラートレースの内容 1. 決済手段サーバが 3 台とも Reserved

    になる 2. 決済サーバが手段サーバ 1 にのみ Confirm を送った状態で故障 3. 「全員 Reserved」ではないため代替サーバ動作せず
  55. #devsumi #devsumiF #devsumi #devsumiF 第 4 章のまとめ • システムが取りうる異常系をあらかじめ記述 ◦

    どのような故障を想定するか ◦ どのような故障であれば耐えられるように設計するか • TLA+ による探索 ◦ 全探索により仕様に合わない挙動を検出 ◦ 人間ではすぐには思いつかないような条件の漏れを指摘
  56. #devsumi #devsumiF #devsumi #devsumiF 本日のまとめ • 自然言語による仕様は曖昧性を持つ ◦ 理解しているつもりで意外な見落としの原因になる •

    形式手法によるモデリング ◦ 数学的な表現を用いることで厳密な表現が可能に • より複雑な状況における解析の自動化 ◦ 長く複雑な実行パスであっても自動で網羅される