Giter Club home page Giter Club logo

elle's Introduction

Elle

Via Clojars

Elle is a transactional consistency checker for black-box databases. Based purely on client observations of transactions, and given some minimal constraints on datatypes and operations, it can tell you whether that observation exhibits a variety of transactional anomalies. Like a clever lawyer, Elle looks for a sequence of events in a story which couldn't possibly have happened in that order, and uses that inference to prove the story can't be consistent.

In a nutshell, Elle is:

  • General: Elle works over a variety of datatypes and places only minimal, practical constraints on transaction structure.
  • Efficient: Elle is ~linear in history length, and ~constant, rather than exponential, with respect to concurrency.
  • Effective: Elle has found unexpected anomalies in every database we've checked, ranging from internal consistency violations to anti-dependency cycles to dirty read to lost updates to realtime violations.
  • Sound: Elle can find every (non-predicate) anomaly from Adya, Liskov, & O'Neil's Generalized Isolation Level Definitions.
  • Elucidative: Elle can point to a minimal set of transactions which witness a consistency violation; its conclusions are easy to understand and verify.

This repository encompasses a Clojure implementation of the Elle consistency checker and its accompanying test suite, which you can use to check your own histories. Our paper provides deep insight into the goals and intuition behind Elle, and a rough formalization of its soundness proof. A nowhere-near-complete formal proof sketch is written in the Isabelle/HOL proof language.

If you want to check a database using Elle, see Jepsen; Elle comes built-in. If you want to use Elle to check your own histories without using Jepsen, you can add Elle as a dependency to any JVM project, and invoke its checker functions directly. If you're working in a non-JVM language, you can write your history to a file or stream, and call a small wrapper program to produce output.

Elle is still under active development, and we're not 100% confident in its inference rules yet. Jepsen recommends checking reported anomalies by hand to make sure they're valid. If you'd like to contribute, we'd especially welcome your help in the formal proof, and in rigorously defining consistency models.

Questions? Read the paper!

Demo

First, you'll need a copy of Graphviz installed.

Imagine a database where each object (identified by keys like :x or :y) is a list of numbers. Transactions are made up of reads [:r :x [1 2 3]], which return the current value of the given list, and writes [:append :y 4], which append a number to the end of the list.

=> (require '[elle.list-append :as a]
            '[jepsen.history :as h])
nil

We construct a history of three transactions, each of which is known to have committed (:type :ok). The first transaction appends 1 to :x and observes :y = [1]. The second appends 2 to :x and 1 to :y. The third observes x, and sees its value as [1 2].

=> (def h (h/history
            [{:process 0, :type :ok, :value [[:append :x 1] [:r :y [1]]]}
             {:process 1, :type :ok, :value [[:append :x 2] [:append :y 1]]}
             {:process 2, :type :ok, :value [[:r :x [1 2]]]}]))
h

Now, we ask Elle to check this history, expecting it to be serializable, and have it dump anomalies to a directory called out/.

=> (pprint (a/check {:consistency-models [:serializable], :directory "out"} h))
{:valid? false,
 :anomaly-types (:G1c),
 :anomalies
 {:G1c
  [{:cycle
    [{:process 1,
      :type :ok,
      :f nil,
      :value [[:append :x 2] [:append :y 1]],
      :index 1,
      :time -1}
     {:process 0,
      :type :ok,
      :f nil,
      :value [[:append :x 1] [:r :y [1]]],
      :index 0,
      :time -1}
     {:process 1,
      :type :ok,
      :f nil,
      :value [[:append :x 2] [:append :y 1]],
      :index 1,
      :time -1}],
    :steps
    ({:type :wr, :key :y, :value 1, :a-mop-index 1, :b-mop-index 1}
     {:type :ww,
      :key :x,
      :value 1,
      :value' 2,
      :a-mop-index 0,
      :b-mop-index 0}),
    :type :G1c}]},
 :not #{:read-committed},
 :also-not
 #{:consistent-view :cursor-stability :forward-consistent-view
   :monotonic-atomic-view :monotonic-snapshot-read :monotonic-view
   :repeatable-read :serializable :snapshot-isolation :strong-serializable
   :strong-session-serializable :strong-session-snapshot-isolation
   :strong-snapshot-isolation :update-serializable}}

Here, Elle can infer the write-read relationship between T1 and T2 on the basis of their respective reads and writes. The write-write relationship between T2 and T1 is inferrable because T3 observed x = [1,2], which constrains the possible orders of appends. This is a G1c anomaly: cyclic information flow. The :cycle field shows the operations in that cycle, and :steps shows the dependencies between each pair of operations in the cycle.

On the basis of this anomaly, Elle has concluded that this history is not read-committed---this is the weakest level Elle can demonstrate is violated. In addition, several stronger isolation levels, such as consistent-view and update-serializable, are also violated by this history.

Let's see the G1c anomaly in text:

$ cat out/G1c.txt
G1c #0
Let:
  T1 = {:index 1, :time -1, :type :ok, :process 1, :f nil,
        :value [[:append :x 2] [:append :y 1]]}
  T2 = {:index 0, :time -1, :type :ok, :process 0, :f nil,
        :value [[:append :x 1] [:r :y [1]]]}


Then:
  - T1 < T2, because T2 observed T1's append of 1 to key :y.
  - However, T2 < T1, because T1 appended 2 after T2 appended 1 to :x: a contradiction!

In the out/G1c directory, you'll find a corresponding plot.

A plot showing the G1c dependency

In addition to rendering a graph for each individual cycle, Elle generates a plot for each strongly-connected component of the dependency graph. This can be helpful for getting a handle on the scope of an anomalous behavior, whereas cycles show as small a set of transactions as possible. Here's a plot from a more complex history, involving realtime edges, write-write, write-read, and read-write dependencies:

A dependency graph showing read-write, write-read, write-write, and realtime dependencies

Usage

As a user, your main entry points into Elle will be elle.list-append/check and elle.rw-register/check. Both namespaces also have code for generating sequences of transactions which you can apply to your database; see, for example, elle.list-append/gen.

Elle has a broad variety of anomalies and consistency models; see elle.consistency-model for their definitions. Not every anomaly is detectable, but we aim for completeness.

If you'd like to define your own relationships between transactions, see elle.core.

Observed Histories

Elle expects its observed histories in the same format as Jepsen. See jepsen.history for the structure of these histories.

Types of Tests

  • elle.core: The heart of Elle's inference system. Computes transaction graphs and finds cycles over them. Includes general-purpose graphs for per-process and realtime orders.
  • elle.rw-register: Write/Read registers. Weaker inference rules, but applicable to basically all systems. Objects are registers; writes blindly replace values.
  • elle.list-append: Elle's most powerful inference rules. Objects are lists, writes append unique elements to those lists.

Consistency Models

The following plot shows Elle's relationships between consistency models: an arrow a -> b implies if a holds, then so does b. Sources for this structure can be found in elle.consistency-model.

This plot shows the relationships between Elle's anomalies. An arrow a -> b implies if we observe anomaly a in a history, then b exists in the history as well.

Soundness

Elle can check for every non-predicate anomaly from Adya, Liskov, and O'Neil's Generalized Isolation Level Definitions. These include:

  • G0: Write cycle.
  • G1a: Aborted read.
  • G1b: Intermediate read.
  • G1c: Cyclic information flow.
  • G-Single: Read skew.
  • G2: Anti-dependency cycle.

There are additional anomalies (e.g. garbage reads, dirty updates, inconsistent version orders) available for specific checkers. Not all of these are implemented fully yet---see the paper for details.

  • Internal Inconsistency: A transaction fails to observe its own prior reads/writes.
  • Inconsistent Version Orders: Inference rules suggested a cyclic order of updates to a single key.
  • Dirty Updates: A write promotes aborted state into committed state.
  • Duplicate Writes: A write occurs more than once.
  • Garbage Reads: A read observes a state which could not have been the product of any write.

In addition, Elle can infer transaction dependencies on the basis of process (e.g. session) or realtime order, allowing it to distinguish between, say, strict serializability and serializability.

For lists, Elle can infer a complete prefix of the Adya version order for a key based on a single read. For registers, Elle can infer version orders on the basis of the initial state, writes-follow-reads, process, and real-time orders.

When Elle claims an anomaly in an observable history, it specifically means that in any abstract Adya-style history which is compatible with that observed history, either a corresponding anomaly exists, or something worse happened---e.g. an aborted read. This is a natural consequence of testing real-world databases; if the database lies in just the right way, it might appear to exhibit anomalies which didn't actually happen, or mask anomalies which did. We limit the impact of this problem by being able to distinguish between many classes of reads, and sampling many anomalies---hoping that eventually, we get lucky and see the anomaly for what it "really is".

Completeness

Elle is not complete: it may fail to identify anomalies which were present in the system under test. This is a consequence of two factors:

  1. Elle checks histories observed from real databases, where the results of transactions might go unobserved, and timing information might not be as precise as one would like.
  2. Serializability checking is NP-complete; Elle intentionally limits its inferences to those solvable in linear (or log-linear) time.

In practice, we believe Elle is "complete enough". Indeterminacy is generally limited to unobserved transactions, or a small set of transactions at the very end of the history.

Performance

Elle has been extensively optimized and many of its components are parallelized. It can check real-world histories of 22 million transactions for (e.g.) strong session serializability in in roughly two minutes, consuming ~60 GB of heap. 100-160,000 transactions/sec is readily attainable on modern hardware. Most of Elle's analyses scale linearly or as n log(n).

Graphs of Elle's performance vs Knossos

These plots, from the original Elle paper before optimization, show Elle's performance vs the Knossos linearizability checker, verifying histories of various lengths (l) and concurrencies (c), recorded from a simulated serializable snapshot isolated in-memory database. Lower is better.

In general, Elle checks real-world histories in a matter of seconds to minutes, rather than seconds to millennia. Where Knossos is often limited to a few hundred operations per history, Elle can handle hundreds of thousands of operations easily.

Knossos runtimes diverge exponentially with concurrency; Elle is effectively constant. There's a slight drop in runtime as concurrency increases, as more transactions abort due to conflicts. Knossos is also mildly superlinear in history length; Elle is effectively linear.

License

Elle is copyright 2019--2020 Jepsen, LLC and Peter Alvaro. The Elle library is available under the Eclipse Public License, version 2.0, or, at your option, GPL-2.0 with the classpath exception.

Thanks

Elle was inspired by conversations with Asha Karim, and Kit Patella (@mkcp) wrote the first prototype of the Elle checker.

See Also

  • elle-cli, a standalone command-line frontend to Elle (and other checkers)

elle's People

Contributors

aphyr avatar groxx avatar ligurio avatar nano-o avatar nurturenature avatar stevana avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

elle's Issues

The rw-register checker succeeds on the history: wx1, rx2.

The following history succeds with the rw-register checker (with :strict-serializable model and linearisable-keys? true):

{:type :invoke, :f :txn :value [[:w :x 1]],   :process 0, :index 1}
{:type :ok,     :f :txn :value [[:w :x 1]],   :process 0, :index 2}
{:type :invoke, :f :txn :value [[:r :x nil]], :process 0, :index 3}
{:type :ok,     :f :txn :value [[:r :x 2]],   :process 0, :index 4}

Adding an other read to the history makes it fail, e.g.:

{:type :invoke, :f :txn :value [[:r :x nil]], :process 0, :index 5}
{:type :ok,     :f :txn :value [[:r :x 1]],   :process 0, :index 6}

This doesn't seem right to me, but perhaps I'm missing something?

I've captured the above in two test cases in the following PR #7.

Modeling linearizable keys with the list-append workload

I'm trying to test our database with jepsen, and having trouble to model our (probably unusual) guarantees. Currently our transactions guarantee serializable isolation, but additionally non-stale reads (all committed changes must be visible in subsequent transactions), and due to implementation it's probably per key (or even per shard) linearizable. Due to single-shard optimizations we are not yet strict-serializable however: independent single-key writes may be assigned local shard timestamps, which appear reordered from the global time perspective.

Example of a possible history:

(require '[elle.list-append :as a]
         '[jepsen.history :as h])

(def h (h/history
        [{:process 0, :time 1000, :type :invoke, :f :txn, :value [[:r :x nil] [:r :y nil]]}
         {:process 1, :time 1001, :type :invoke, :f :txn, :value [[:append :x 1]]}
         {:process 1, :time 1010, :type :ok, :f :txn, :value [[:append :x 1]]}
         {:process 1, :time 1011, :type :invoke, :f :txn, :value [[:append :y 2]]}
         {:process 1, :time 1020, :type :ok, :f :txn, :value [[:append :y 2]]}
         {:process 0, :time 1030, :type :ok, :f :txn, :value [[:r :x []] [:r :y [2]]]}]))

(pprint (a/check {:consistency-models [:serializable]} h))
(pprint (a/check {:consistency-models [:strict-serializable]} h))

This history is obviously serializable, but not strict serializable.

Consider this history however:

(def h (h/history
        [{:process 1, :time 1001, :type :invoke, :f :txn, :value [[:append :x 1]]}
         {:process 1, :time 1010, :type :ok, :f :txn, :value [[:append :x 1]]}
         {:process 1, :time 1011, :type :invoke, :f :txn, :value [[:r :x nil]]}
         {:process 1, :time 1020, :type :ok, :f :txn, :value [[:r :x []]]}]))

This history is also serializable, and not strict serializable. But it also shows stale read, and is not per key linearizable.

Unfortunately, both :strong-read-committed and :linearizable show this history as valid for some reason:

...=> (pprint (a/check {:consistency-models [:strong-read-committed]} h))
{:valid? true}
nil
...=> (pprint (a/check {:consistency-models [:linearizable]} h))
{:valid? true}
nil

I'm not sure whether it's because :strong-read-committed and :linearizable are not implemented, or whether I misunderstand what they mean. Unfortunately testing with :strict-serializable shows G-single-item-realtime and G-nonadjacent-item-realtime anomalies, which appear to be due to reordered writes. And :serializable is too weak.

I'm a novice in clojure, so don't quite understand how to extend elle to do what I want, can you please point me in the right direction on how to check for additional realtime restrictions over serializable isolation?

Elle may miss two types of transaction anomalies:

During my testing, I found the following two anomalies may be ignored by elle.

  1. Cycle of wr-process-wr
    This case comes from transactional causal consistency:
(def h
  [{:process 1, :type :invoke, :f :txn, :value [[:r :x] [:append :y 1]], :index 0}
   {:process 1, :type :ok, :f :txn, :value [[:r :x [1]] [:append :y 1]], :index 1}
   {:process 2, :type :invoke, :f :txn, :value [[:r :y]], :index 2}
   {:process 2, :type :ok, :f :txn, :value [[:r :y [1]]], :index 3}
   {:process 2, :type :invoke, :f :txn, :value [[:append :x 1]], :index 4}
   {:process 2, :type :ok, :f :txn, :value [[:append :x 1]], :index 5}])

Here is my REPL screenshot
image

It is obvious that there is a cycle and I think this cycle should be prohibited by stong-session-snapshot-isolation
image

  1. Future Read
    This case is about the internal consistency for a transaction.

image

I know elle do something special for internal-cases for read-your-writes condition, but maybe elle does not check for the future read.

I would appreciate it if you could give a prompt reply.

Sincerely,
Young

Elle checks :fail results?

I'm working on a test with Elle to verify serializable transactions.
I have a question about checking anomalies.

Does Elle check the anomalies with :fail results?
I tried Elle via Jepsen (jepsen.tests.cycle.append/test), but I had anomalies for :fail results.
I'd like to ignore these :fail results for checking anomalies.

{:clock {:valid? true},
 :stats
 {:valid? true,
  :count 1504,
  :ok-count 318,
  :fail-count 1184,
  :info-count 2,
  :by-f
  {:txn
   {:valid? true,
    :count 1504,
    :ok-count 318,
    :fail-count 1184,
    :info-count 2}}},
 :exceptions {:valid? true},
 :workload
 {:valid? false,
  :anomaly-types (:G1a :G1b :dirty-update),
  :anomalies
  {:G1a
   ({:op
     {:type :ok,
      :f :txn,
      :value [[:r 18 [1]] [:r 17 [3]] [:append 6 10] [:append 17 9]],
      :time 24868956077,
      :process 0,
      :index 101},
     :mop [:r 18 [1]],
     :writer
     {:type :fail,
      :f :txn,
      :value
      [[:append 14 6]
       [:append 18 1]
       [:r 17 nil]
       [:r 17 nil]
       [:r 17 nil]
       [:append 14 7]
       [:append 18 2]],
      :time 24441252230,
      :process 1,
      :error [:crud-error "rollback is toward non-prepared record"],
      :index 91},
     :element 1}
...

Example for running test-cases with Elle?

Hello Jepsen developers,

I'm currently working as a researcher in a company project that is inventing a new key-value store, and I have found this Jepsen I/O, which can be an excellent benchmark for checking the anomalies.

I'm checking the scope of the workload that needs to be done so that the Elle can be used to the company's new key-value store. For now, what I see from the provided test directory is that Elle can run the consistency model using the history hand-created by the developer.

Are there any good examples that I can follow to run Elle with Jepsen I/O and a database as a whole? (not the unit-test) I have read the amazing papers that Elle is actually applied in many different databases, and I want to see how the test case is done, so I can propose the Jepsen I/O as the main benchmark for checking the anomalies.

Cannot run on the latest version of Clojure on Ubuntu 22.04: reducers.clj is not found

Run has failed with latest Clojure on Ubuntu 22.04:

[0] ~/sources/elle-cli/elle$ lein deps                                                                                                  
Retrieving com/gfredericks/test.chuck/0.2.14/test.chuck-0.2.14.pom from clojars                                                         
Retrieving com/gfredericks/test.chuck/0.2.14/test.chuck-0.2.14.jar from clojars                                                         
[0] ~/sources/elle-cli/elle$ lein uberjar                                                                                               
Compiling 3 source files to /home/sergeyb/sources/elle-cli/elle/target/classes                                                          
warning: [options] bootstrap class path not set in conjunction with -source 8                                                           
Note: Some input files use unchecked or unsafe operations.                                                                              
Note: Recompile with -Xlint:unchecked for details.                                                                                      
1 warning                                                                                                                               
Created /home/sergeyb/sources/elle-cli/elle/target/elle-0.1.8.jar                                                                       
Created /home/sergeyb/sources/elle-cli/elle/target/elle-0.1.8-standalone.jar                                                            
[0] ~/sources/elle-cli/elle$ java -jar ./target/elle-0.1.8-standalone.jar                                                               
Clojure 1.2.1                                                                                                                           
user=> (require '[elle.list-append :as a]                                                                                               
            '[jepsen.history :as h])                                                                                                    
java.io.FileNotFoundException: Could not locate clojure/core/reducers__init.class or clojure/core/reducers.clj on classpath:  (list_append.clj:1)

Is it possible to somehow check minimal version before start? Otherwise, it is not clear why reducers.clj is not found. (Hint: required Clojure version is 1.11.1 according to project.clj).

Ubuntu 22.04 x86_64, Clojure 1.10.2

Build fails with `lein check`

Building with lein check fails, with a Reflection warning and a syntax warning on the current master (531ea9a).

The problem seems to be a missing variable txn/int-write-mops

$ lein check
Compiling namespace elle.graph
Reflection warning, elle/graph.clj:683:15 - call to method select can't be resolved (target class is unknown).
Compiling namespace elle.version-graph
Compiling namespace elle.history
Compiling namespace elle.util
Compiling namespace elle.set-add
Syntax error compiling at (elle/recovery.clj:102:10).
No such var: txn/int-write-mops

Full report at:
/tmp/clojure-17876742899113241601.edn
Failed.

Contents of the report:

{:clojure.main/message
 "Syntax error compiling at (elle/recovery.clj:102:10).\nNo such var: txn/int-write-mops\n",
 :clojure.main/triage
 {:clojure.error/phase :compile-syntax-check,
  :clojure.error/line 102,
  :clojure.error/column 10,
  :clojure.error/source "recovery.clj",
  :clojure.error/path "elle/recovery.clj",
  :clojure.error/class java.lang.RuntimeException,
  :clojure.error/cause "No such var: txn/int-write-mops"},
 :clojure.main/trace
 {:via
  [{:type clojure.lang.Compiler$CompilerException,
    :message "Syntax error compiling at (elle/recovery.clj:102:10).",
    :data
    {:clojure.error/phase :compile-syntax-check,
     :clojure.error/line 102,
     :clojure.error/column 10,
     :clojure.error/source "elle/recovery.clj"},
    :at [clojure.lang.Compiler analyze "Compiler.java" 6808]}
   {:type java.lang.RuntimeException,
    :message "No such var: txn/int-write-mops",
    :at [clojure.lang.Util runtimeException "Util.java" 221]}],
  :trace
  [[clojure.lang.Util runtimeException "Util.java" 221]
   [clojure.lang.Compiler resolveIn "Compiler.java" 7388]
   [clojure.lang.Compiler resolve "Compiler.java" 7358]
   [clojure.lang.Compiler analyzeSymbol "Compiler.java" 7319]
   [clojure.lang.Compiler analyze "Compiler.java" 6768]
   [clojure.lang.Compiler analyze "Compiler.java" 6745]
   [clojure.lang.Compiler$InvokeExpr parse "Compiler.java" 3888]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7109]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler analyze "Compiler.java" 6745]
   [clojure.lang.Compiler$InvokeExpr parse "Compiler.java" 3888]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7109]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler analyze "Compiler.java" 6745]
   [clojure.lang.Compiler$InvokeExpr parse "Compiler.java" 3888]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7109]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7095]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler analyze "Compiler.java" 6745]
   [clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6120]
   [clojure.lang.Compiler$LetExpr$Parser parse "Compiler.java" 6436]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7107]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7095]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler analyze "Compiler.java" 6745]
   [clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6120]
   [clojure.lang.Compiler$FnMethod parse "Compiler.java" 5467]
   [clojure.lang.Compiler$FnExpr parse "Compiler.java" 4029]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7105]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7095]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler access$300 "Compiler.java" 38]
   [clojure.lang.Compiler$DefExpr$Parser parse "Compiler.java" 596]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7107]
   [clojure.lang.Compiler analyze "Compiler.java" 6789]
   [clojure.lang.Compiler analyze "Compiler.java" 6745]
   [clojure.lang.Compiler eval "Compiler.java" 7181]
   [clojure.lang.Compiler load "Compiler.java" 7636]
   [clojure.lang.RT loadResourceScript "RT.java" 381]
   [clojure.lang.RT loadResourceScript "RT.java" 372]
   [clojure.lang.RT load "RT.java" 459]
   [clojure.lang.RT load "RT.java" 424]
   [clojure.core$load$fn__6839 invoke "core.clj" 6126]
   [clojure.core$load invokeStatic "core.clj" 6125]
   [clojure.core$load doInvoke "core.clj" 6109]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [clojure.core$load_one invokeStatic "core.clj" 5908]
   [clojure.core$load_one invoke "core.clj" 5903]
   [clojure.core$load_lib$fn__6780 invoke "core.clj" 5948]
   [clojure.core$load_lib invokeStatic "core.clj" 5947]
   [clojure.core$load_lib doInvoke "core.clj" 5928]
   [clojure.lang.RestFn applyTo "RestFn.java" 142]
   [clojure.core$apply invokeStatic "core.clj" 667]
   [clojure.core$load_libs invokeStatic "core.clj" 5989]
   [clojure.core$load_libs doInvoke "core.clj" 5969]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.core$apply invokeStatic "core.clj" 667]
   [clojure.core$require invokeStatic "core.clj" 6007]
   [clojure.core$require doInvoke "core.clj" 6007]
   [clojure.lang.RestFn invoke "RestFn.java" 457]
   [elle.set_add$eval568$loading__6721__auto____569
    invoke
    "set_add.clj"
    1]
   [elle.set_add$eval568 invokeStatic "set_add.clj" 1]
   [elle.set_add$eval568 invoke "set_add.clj" 1]
   [clojure.lang.Compiler eval "Compiler.java" 7177]
   [clojure.lang.Compiler eval "Compiler.java" 7166]
   [clojure.lang.Compiler load "Compiler.java" 7636]
   [clojure.lang.RT loadResourceScript "RT.java" 381]
   [clojure.lang.RT loadResourceScript "RT.java" 372]
   [clojure.lang.RT load "RT.java" 459]
   [clojure.lang.RT load "RT.java" 424]
   [clojure.core$load$fn__6839 invoke "core.clj" 6126]
   [clojure.core$load invokeStatic "core.clj" 6125]
   [clojure.core$load doInvoke "core.clj" 6109]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [user$eval140$fn__151 invoke "form-init12736845212758803140.clj" 1]
   [user$eval140 invokeStatic "form-init12736845212758803140.clj" 1]
   [user$eval140 invoke "form-init12736845212758803140.clj" 1]
   [clojure.lang.Compiler eval "Compiler.java" 7177]
   [clojure.lang.Compiler eval "Compiler.java" 7167]
   [clojure.lang.Compiler load "Compiler.java" 7636]
   [clojure.lang.Compiler loadFile "Compiler.java" 7574]
   [clojure.main$load_script invokeStatic "main.clj" 475]
   [clojure.main$init_opt invokeStatic "main.clj" 477]
   [clojure.main$init_opt invoke "main.clj" 477]
   [clojure.main$initialize invokeStatic "main.clj" 508]
   [clojure.main$null_opt invokeStatic "main.clj" 542]
   [clojure.main$null_opt invoke "main.clj" 539]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :cause "No such var: txn/int-write-mops",
  :phase :compile-syntax-check}}

Is there any example on how to use the gen function?

First of all, I'm not a closure programmer. So it might be a very dump question.

(ns jepsen.test
  (:require [elle.list-append :as a]
            [elle.txn :as ct]
            [jepsen.history :as h]
            [clojure.pprint :refer [pprint]]))
(defn -main
    (pprint (take 100 (ct/wr-txns {:key-count 3}))))

I'm trying to print out some txn history so that I can use the sequence to check my work. But it doesn't compile. lein run gives me this error:

Syntax error macroexpanding clojure.core/defn at (jepsen/test.clj:11:1).
pprint - failed: vector? at: [:fn-tail :arity-n :bodies :params] spec: :clojure.core.specs.alpha/param-list
(pprint (take 100 (ct/wr-txns {:key-count 3}))) - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list

I don't know what the error messages mean.

Could I check a history that with real-time order

suppose I have a history like this

(def his 
[{:type :ok, :process 1, :value [[:r 0 nil] [:w 0 1]], :time 0}
 {:type :ok, :process 2, :value [[:w 0 2]], :time 1}
 {:type :ok, :process 3, :value [[:r 0 1]], :time 2}])

processes 1 and 2 are concurrent.
process 3 is executed when processes 1 and 2 finish.
Obviously, this history is not serializable, but elle could not work due to missing real-time order

list-append/sorted-values might be broken with history object

I am running a Jepsen test from Jepsen-io/mongodb and using [jepsen "0.3.3-SNAPSHOT"], I am seeing this error below. Can anyone help? Thanks
here is the lein run command
lein run test-all -w list-append -n n1 -n n2 -n n3 -n n4 -n n5 -n n6 -n n7 -n n8 -n n9 -r 1000 --concurrency 3n --time-limit 240 --max-writes-per-key 128 --read-concern majority --write-concern majority --txn-read-concern snapshot --txn-write-concern majority --nemesis-interval 1 --nemesis partition --test-count 1

here is the error message:

[2023/08/24 00:06:52.509] INFO [2023-08-24 00:06:52,428] jepsen test runner - jepsen.core Analyzing...
[2023/08/24 00:06:52.509] WARN [2023-08-24 00:06:52,508] clojure-agent-send-off-pool-78 - jepsen.checker Error while checking history:
[2023/08/24 00:06:52.509] java.lang.ClassCastException: class clojure.lang.LazySeq cannot be cast to class jepsen.history.IHistory (clojure.lang.LazySeq and jepsen.history.IHistory are in unnamed module of loader 'app')
[2023/08/24 00:06:52.509] at elle.list_append$sorted_values.invokeStatic(list_append.clj:257)
[2023/08/24 00:06:52.509] at elle.list_append$sorted_values.invoke(list_append.clj:223)
[2023/08/24 00:06:52.509] at jepsen.mongodb.list_append$divergence_stats_checker$reify__2906.check(list_append.clj:174)
[2023/08/24 00:06:52.509] at jepsen.checker$check_safe.invokeStatic(checker.clj:86)
[2023/08/24 00:06:52.509] at jepsen.checker$check_safe.invoke(checker.clj:79)
[2023/08/24 00:06:52.509] at jepsen.checker$compose$reify__10709$fn__10711.invoke(checker.clj:102)
[2023/08/24 00:06:52.509] at clojure.core$pmap$fn__8552$fn__8553.invoke(core.clj:7089)
[2023/08/24 00:06:52.509] at clojure.core$binding_conveyor_fn$fn__5823.invoke(core.clj:2047)
[2023/08/24 00:06:52.509] at clojure.lang.AFn.call(AFn.java:18)
[2023/08/24 00:06:52.509] at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[2023/08/24 00:06:52.509] at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[2023/08/24 00:06:52.509] at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[2023/08/24 00:06:52.509] at java.base/java.lang.Thread.run(Thread.java:829)
[2023/08/24 00:06:54.572] INFO [2023-08-24 00:06:54,572] jepsen test runner - jepsen.core Analysis complete

Could Elle tell the difference between snapshot isolation and strong-session-snapshot-isolation?

image

In the consistency models you provided in README, I found that you have distinguished PL-SI (snapshot-isolation), strong-session-snapshot-isolation and strong-snapshot-isolation. However, it seems that you have not implemented this in Elle?

image
I constructed this history of 4 transactions running in 3 processes. There is a cycle as shown in the picture above. In other words, strong-session-snapshot-isolation is violated by this history.

(def h [{:type :invoke, :value [[:r :x nil] [:r :z nil]], :process 0, :index 0}
        {:type :ok, :value [[:r :x nil] [:r :z [1]]], :process 0, :index 1}
        {:type :invoke, :value [[:append :x 1]], :process 1, :index 2}
        {:type :ok, :value [[:append :x 1]], :process 1, :index 3}
        {:type :invoke, :value [[:r :z nil]], :process 1, :index 4}
        {:type :ok, :value [[:r :z nil]], :process 1, :index 5}
        {:type :invoke, :value [[:append :z 1]], :process 2, :index 6}
        {:type :ok, :value [[:append :z 1]], :process 2, :index 7}])

However, when I cloned the latest release v0.1.5, and checked this history by Elle, the result is valid.

image

Did I miss any key steps or did not set any key parameters when checking?

I would appreciate it if you could give a prompt reply.

Sincerely,
Young

Elle couldn't check list-append example described in a paper

There is a regression in comparison to previous Elle version.
Elle cannot check example described in paper 1:

elle.core=>  (require '[elle.list-append :as a]
       #_=>             '[jepsen.history :as h])
nil
elle.core=>  (def h (h/history
       #_=>             [{:index 0 :type :invoke  :value [[:append 253 1] [:append 253 3] [:append 253 4] [:append 255 2] [:append 255 3] [:append 255 4] [:append 255 5] [:append 256 1] [:append 256 2]]}
       #_=> {:index 1 :type :ok      :value [[:append 253 1] [:append 253 3] [:append 253 4] [:append 255 2] [:append 255 3] [:append 255 4] [:append 255 5] [:append 256 1] [:append 256 2]]}
       #_=> {:index 2 :type :invoke, :value [[:append 255 8] [:r 253 nil]]}
       #_=> {:index 3 :type :ok,     :value [[:append 255 8] [:r 253 [1 3 4]]]}
       #_=> {:index 4 :type :invoke, :value [[:append 256 4] [:r 255 nil] [:r 256 nil] [:r 253 nil]]}
       #_=> {:index 5 :type :ok,     :value [[:append 256 4] [:r 255 [2 3 4 5 8]] [:r 256 [1 2 4]] [:r 253 [1 3 4]]]}
       #_=> {:index 6 :type :invoke, :value [[:append 250 10] [:r 253 nil] [:r 255 nil] [:append 256 3]]}
       #_=> {:index 7 :type :ok      :value [[:append 250 10] [:r 253 [1 3 4]] [:r 255 [2 3 4 5]] [:append 256 3]]}]))
#'elle.core/h
elle.core=> (pprint (a/check {:consistency-models [:serializable], :directory "out"} h))
{:valid? :unknown,
 :anomaly-types (:empty-transaction-graph),
 :anomalies {:empty-transaction-graph true},
 :not #{},
 :also-not #{}}
nil
elle.core=> 

Version: 49a209f

Footnotes

  1. https://github.com/jepsen-io/elle/blob/main/histories/paper-example.edn โ†ฉ

Example with list-append history is broken in 0.1.6

In a new version 0.1.6 example described in a README 1 is broken:

elle.core=> (require '[elle.list-append :as a])
nil
elle.core=> h
[{:type :ok, :value [[:append :x 1] [:r :y [1]]]} {:type :ok, :value [[:append :x 2] [:append :y 1]]} {:type :ok, :value [[:r :x [1 2]]]}]
elle.core=> (pprint (a/check {:consistency-models [:serializable], :directory "out"} h))
Execution error (ExceptionInfo) at jepsen.history/filter (history.clj:1143).
Assert failed:
{:type :jepsen.history/not-history,
 :history-type clojure.lang.PersistentVector}


elle.core=> 

This is probably due to significant changes in history format, https://github.com/jepsen-io/jepsen/releases/tag/v0.3.0

Footnotes

  1. https://github.com/jepsen-io/elle#demo โ†ฉ

PL-2L(monotonic-view) G-monotonic: experiment with adding the anomaly

Hi,

I'm working on using Elle as a checker for causal consistency.
Yes, the CRDTs are finally coming in the form of local first sync layers. :)

While testing with :consistency-models [:consistent-view], it seemed like lots of anomalies were not being caught.
Looking at the graphs and cycle-anomalies, consistent-view was:

  • missing the process-order graph
  • reporting anomalies at higher consistency levels, e.g. strong-session-snapshot-isolation

Looking at how elle.txn used -process, reportable-anomalies, etc lead to looking at Adya's Weak Consistency to see where process order and the expected anomalies were introduced. And in 4.2.2 PL-2L:

G-monotonic: Monotonic Reads. A history H exhibits phenomenon G-monotonic for transaction Ti if there exists a cycle in USG(H, Ti ) containing exactly one anti-dependency edge from a read node ri (xj ) (or ri (P: xj )) to some transaction node Tk (and any number of order or dependency edges).

Which Elle expresses as:

:G-single {:rels        (rels/union ww wwp wr wrp)
           :single-rels (rels/union rw rwp)
           :type        :G-single}

And if we test using G-single-process as additional :anomalies [:G-single-process]:

(def opts
  {:consistency-models [:consistent-view] ; Adya formalism for causal consistency
   :sequential-keys? true                 ; infer version order from elle/process-graph
   :wfr-keys? true                        ; rw/wfr-version-graph within txns
   })

(def mono-writes-anomaly
  (->> [{:process 0 :value [[:w :x 0]]   :type :ok :f :txn}
        {:process 0 :value [[:w :y 0]]   :type :ok :f :txn}
        {:process 1 :value [[:r :y 0]]   :type :ok :f :txn}
        {:process 1 :value [[:r :x nil]] :type :ok :f :txn}]
       h/history))

(rw/check opts mono-writes-anomaly)
{:valid? true}

(rw/check (assoc opts :anomalies [:G-single-process]) mono-writes-anomaly)
{:valid? false,
 :anomaly-types [:G-single-item-process],
 :anomalies {:G-single-item-process [{:cycle [{:index 3, :time -1, :type :ok, :process 1, :f :txn, :value [[:r :x nil]]}
                                              {:index 0, :time -1, :type :ok, :process 0, :f :txn, :value [[:w :x 0]]}
                                              {:index 1, :time -1, :type :ok, :process 0, :f :txn, :value [[:w :y 0]]}
                                              {:index 2, :time -1, :type :ok, :process 1, :f :txn, :value [[:r :y 0]]}
                                              {:index 3, :time -1, :type :ok, :process 1, :f :txn, :value [[:r :x nil]]}],
                                      :steps [{:key :x, :value nil, :value' 0, :type :rw, :a-mop-index 0, :b-mop-index 0}
                                              {:type :process, :process 0}
                                              {:type :wr, :key :y, :value 0, :a-mop-index 0, :b-mop-index 0}
                                              {:type :process, :process 1}], :type :G-single-item-process}]},
 :not #{:strong-session-snapshot-isolation},
 :also-not #{:strong-serializable :strong-session-serializable :strong-snapshot-isolation}}

๐ŸŽ‰
It also catches more writes-follow-reads in other tests too.

But if G-single-process is added to the direct-proscribed-anomalies:

:monotonic-view [:G1 :G-monotonic   ; Adya
                 :G-single-process]

We no longer find the G-single-item-process, but only the stronger consistency strong-session-snapshot-isolation-cycle-exists:

(et/reportable-anomaly-types opts)
#{:G-cursor :G-monotonic :G-single :G-single-item :G-single-item-process :G-single-process :G0 :G1 :G1a :G1b :G1c :PL-1-cycle-exists :PL-2-cycle-exists :cycle-search-timeout :cyclic-versions :dirty-update :duplicate-elements :empty-transaction-graph :future-read :incompatible-order :lost-update}

(rw/check opts mono-writes-anomaly)
{:valid? true}

(:anomalies (et/cycles! opts (partial rw/graph opts) mono-writes-anomaly))
{:strong-session-snapshot-isolation-cycle-exists [{:type :strong-session-snapshot-isolation-cycle-exists,
                                                   :not :strong-session-snapshot-isolation,
                                                   :subgraph [:extension [:union :ww :wwp :wr :wrp :process] [:union :rw :rwp]],
                                                   :scc-size 3,
                                                   :scc #{0 1 2}}]}

I've looked at opts :anomalies handling in prohibited-anomaly-types, cycle identification in cycle-explainer, and a few other places and I can't find anything anomalous. :)

I do think that:

  • monotonic-view's G-monotonic needs to be expressed, it's currently a no-op
  • causal, consistent-view, has to end up with process order
  • G-monotonic, as G-single-process, would:
    - more correctly identify what consistency level you are and :not :also-not
    - catch some of the tests labeled not possible to catch at the given consistency level as they are actually writes-follows-reads and/or monotonic-writes anomalies.

Was hoping you would be able to confirm that this is generally a good idea, desired?
And if so, any ideas on where to look to understand and change the behavior?

Thanks!

(P.S. This is my first time really trying to understand DAGs, cycles, etc and Elle is a wonderful teaching tool. I keep thinking, Wow! It's as if Elle was designed for this, then remembering it most certainly was. ๐Ÿ˜†

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.