IsoPredict: Dynamic Predictive Analysis for Detecting Unserializable Behaviors in Weakly Isolated Data Store Applications
\minibox[frame]This extended version of a PLDI 2024 paper adds an appendix with additional material
IsoPredict: Dynamic Predictive Analysis for Detecting Unserializable Behaviors in Weakly Isolated Data Store Applications
Abstract.
Distributed data stores typically provide weak isolation levels, which are efficient but can lead to unserializable behaviors, which are hard for programmers to understand and often result in errors. This paper presents the first dynamic predictive analysis for data store applications under weak isolation levels, called IsoPredict. Given an observed serializable execution of a data store application, IsoPredict generates and solves SMT constraints to find an unserializable execution that is a feasible execution of the application. IsoPredict introduces novel techniques that handle divergent application behavior; solve mutually recursive sets of constraints; and balance coverage, precision, and performance. An evaluation on four transactional data store benchmarks shows that IsoPredict often predicts unserializable behaviors, 99% of which are feasible.
1. Introduction
Distributed data stores are the foundation of today’s service infrastructure, due to their scalability, fault tolerance, and ease of use (Corbett et al., 2012; Elhemali et al., 2022; Snowflake, 2023; MySQL, 2023b). Many real-world data stores only support weak isolation levels, such as causal consistency (causal) (Ahamad et al., 1995), which is the strongest level that achieves availability under network partitions (Burckhardt, 2014; Gilbert and Lynch, 2002). Another weak isolation level is read committed (rc) (Berenson et al., 1995), which is commonly used by database applications to balance performance and correctness (Crooks et al., 2017; Pavlo, 2017; Cheng et al., 2023; Tang et al., 2022). Under weak isolation, an execution may be unserializable, producing an outcome that is impossible for any serial execution. Unserializable behaviors are poorly understood by most programmers, and often lead to errors and failures in real-world systems (Cheng et al., 2023; Tang et al., 2022; Warszawski and Bailis, 2017).
Prior work has introduced techniques to find unserializable behaviors in data store applications under weak isolation, but has scalability or accuracy limitations. Static analysis can find unserializable behaviors, but its precision scales poorly with program complexity, leading to many false positives (infeasible unserializable behaviors) (Brutschy et al., 2018; Nagar and Jagannathan, 2018; Rahmani et al., 2019). Dynamic analysis can avoid false positives by analyzing only the observed execution (Biswas et al., 2021; Brutschy et al., 2017), or it can extrapolate from an observed execution but report numerous false positives (Gan et al., 2020; Warszawski and Bailis, 2017). §8 discusses prior work in more detail.
Motivating example
Algorithm 1 shows code of a transactional data store application. The provides a key–value interface. Our execution model requires that every (read) or (write) operation to execute in a transaction, so an operation starts a new transaction if the current session (i.e., client) is not in a transaction. A operation ends the session’s ongoing transaction.
Figure 1 shows two different executions of the application. In each execution, two sessions (i.e., clients) call deposit concurrently on the same empty account to deposit 50 and 60, respectively. Developers would expect that the ending balance will be 110, which is the only serializable outcome. However, under weak isolation levels causal and rc, the ending balance may be 110, 50, or 60.
Contributions
This paper introduces IsoPredict, the first predictive analysis for transactional data store applications, and shows that the approach is effective at finding unserializable behaviors. Given a serializable execution such as Figure 0(a) as input, IsoPredict finds an unserializable execution such as Figure 0(b). IsoPredict uses dynamic predictive analysis, which analyzes an observed execution of a program and detects alternative feasible, unserializable executions of the program.
Predictive analysis is powerful because, in essence, it explores many executions at once. To predict an unserializable execution from an observed serializable execution, IsoPredict generates SMT constraints that encode execution feasibility, unserializability, and weak isolation level (causal or rc), and uses an off-the-shelf SMT solver to solve them. We introduce analysis variants that trade coverage for performance, and precision for coverage. To account for the possibility of predicting infeasible executions, IsoPredict can optionally validate a predicted unserializable execution. An evaluation on transactional data store benchmarks shows that IsoPredict is effective at predicting unserializable executions from observed executions under causal and rc. More than 99% of predictions are validated as feasible executions.
While prior work introduces predictive analysis for shared-memory programs (Said et al., 2011; Kini et al., 2017; Roemer et al., 2020; Huang et al., 2014; Sinha et al., 2012; Tunç et al., 2023), to our knowledge IsoPredict is the first predictive analysis approach for transactional data store applications, which present unique challenges (§8). Compared to prior work MonkeyDB (Biswas et al., 2021), IsoPredict is comparably effective at finding unserializable executions of the evaluated programs (§7.3). However, IsoPredict and MonkeyDB use completely different approaches to find erroneous executions. While MonkeyDB uses random exploration to produce an erroneous execution, IsoPredict uses predictive analysis to evaluate an equivalence class of many executions at once. Furthermore, MonkeyDB requires applications to run on its specialized data store, while IsoPredict’s predictive analysis approach is in principle suitable for analyzing executions from any data store, although demonstrating so is outside the scope of this paper.
2. Background
This section introduces this paper’s formalisms for weakly isolated executions of transactional data store applications, which are closely based on the axiomatic framework of Biswas and Enea (Biswas and Enea, 2019). We use this framework because it supports a variety of isolation levels, is well suited to encoding as constraints, and has been employed by recent work (Biswas et al., 2021; Bouajjani et al., 2023).
2.1. Weakly Isolated Execution Histories
A transactional data store is modeled as a distributed store of key–value pairs. A data store application performs read (get) and write (put) operations on keys, all executed in transactions. Non-transactional applications can be handled by treating each read and write operation as a separate transaction. An execution consists of events in committed transactions (aborted transactions are not part of an execution). Each event is either read(), or write() or commit, where is a key. Other operations, such as insertion into and deletion from a set, can be modeled in terms of reads and writes. Multiple clients may open connections, or sessions, to the data store. If a session is not in a transaction, its next event implicitly starts a new transaction, ensuring every event is in a transaction. The commit event ends the current transaction. Within a session, transactions are ordered by the strict partial order session order ():
An important property of an execution is which write each read reads from. The strict partial order (write–read on key ) orders transactions if one reads from the other:
If a read reads from a write in the same transaction, the read is not included as an event in the transaction (and thus this write–read ordering is not included in ). If a transaction writes multiple times, only the last write is included as an event in the transaction. Thus a read() event always reads from a write() in another transaction, which is the transaction’s last write to . If a transaction reads from the data store’s initial state, then , where is a special transaction representing the initial state. The union of over all keys is , i.e., . The transitive closure of and is happens-before order, i.e., . An execution history of a data store application is the set of all committed transactions (), session order (), and write–read order (), i.e., . Every history includes the special transaction mentioned above that represents the initial state. implicitly writes the initial value to every key, and is -ordered before all other transactions.
Example
2.2. Serializablility
An execution history is serializable if and only if it could have been produced by a serial execution of the transactions in . (In a serial execution, transactions execute one at a time, and every read to reads from the most recent write to .) Equivalently, an execution is serializable if and only if there exists a commit order, , with the following constraints: (1) must be consistent with happens-before () order. (2) a transaction that writes to cannot be -ordered between two transactions ordered by . The second constraint’s ordering is called arbitration order and represented by the strict partial order , which is defined as follows:
(1) |
Note the circular dependency between and : Commit ordering may imply additional arbitration ordering, which in turn may imply additional commit ordering. This property leads to challenges in encoding SMT constraints that §4 explains and addresses. Thus a history is serializable if and only if there exists a that is consistent with and :
Equivalently, the history is serializable if and only if there exists such that is acyclic. An execution is unserializable if and only if it is not serializable.
Example
Figure 1(a)’s history is serializable because there exists a commit order (), shown in Figure 1(b), that is consistent with the serializable axioms. Note that the arbitration rule (Equation 1) never applies in Figure 1(a), and so Figure 1(b) shows no edges.
The history in Figure 2(a) is unserializable because there does not exist a commit order that satifies the serializable axioms. For example, as Figure 2(b) shows, if , then by Equation 1, which implies and thus is cyclic. Alternatively, if , then and thus , and again is cyclic.
2.3. Causal Consistency
Causal consistency (causal) is a weak isolation level that preserves the order of operations that are causally related (Ahamad et al., 1995). causal is of theoretical and practical interest because it is the strongest isolation level achievable when a data store requires availability under network partitions (Burckhardt, 2014; Gilbert and Lynch, 2002; Mahajan et al., 2011).
Similar to serializable, causal is defined in terms of whether there exists a commit order that is consistent with happens-before () and an arbitration order, which we call to distinguish it from the arbitration order for serializable (). Two transactions and are ordered by if they write the same key and if there is a third transaction that happens-after () and reads from ’s write to the same key (). More formally,
(2) |
A history is causal if and only if there exists a commit order consistent with and :
(3) |
Equivalently, a history is causal if and only if is acyclic.111Unlike serializable, causal can be defined in terms of whether is acyclic, which implies that a total commit order must exist. In contrast, serializable’s arbitration order () is dependent on the commit order, so serializable must be defined in terms of whether is acyclic.
Example
The history in Figure 1(a) is causal because there exists a commit order that is consistent with the causal axioms. (Or, since the history is serializable, which is strictly stronger than causal, the history must be causal.) The history in Figure 2(a) is causal because there exists a commit order, (or ), that is consistent with the causal axioms.
2.4. Read Committed
Read committed (rc) is a popular weak isolation level because of the balance between performance and consistency it provides (Berenson et al., 1995). Whereas causal requires transactions ordered by happens-before () to be viewed by other transactions in the same order, rc’s arbitration order, , only applies to write transactions that are read by multiple read events from the same transaction. More formally, rc is defined based on whether there exists a commit order that is consistent with and , which is defined as follows:
(4) |
where is program order, a strict partial order that orders events within a transaction; and is true if and only if is a read event that reads from a write in transaction (and thus ). Thus and must be events in the same transaction such that is a read() event that reads from write() in , and is a read event that reads from any write in . An execution history is rc if and only if there exists a commit order that is consistent with and :
(5) |
Example
3. IsoPredict Overview
IsoPredict consists of two main components, as shown in Figure 4: predictive analysis and validation.
The predictive analysis component takes as input an observed execution history that is recorded at the client application’s backend data store, generates SMT constraints, and uses an SMT solver to find a predicted unserializable execution if one exists. §4 describes IsoPredict’s predictive analysis.
The validation component tries to execute the predicted execution history to determine if it is feasible, and it generates and solves constraints to determine if the resulting execution is unserializable. If so, IsoPredict outputs the validated history alongside a visualization of the validated unserializable execution. §5 describes IsoPredict’s validation component.
Validation is optional; developers may choose to skip it for two reasons. First, it may be overkill—in our experiments, over 99% of predicted unserializable executions are successfully validated. Second, validation may be impractical if the application cannot be replayed easily. Validation is, however, useful to our evaluation to measure how many predicted executions are feasible.
4. Predictive Analysis
IsoPredict’s predictive analysis component takes as input an observed execution history of a data store application. The observed history consists of a set of transactions , session order between transactions, and observed write–read ordering . The goal of IsoPredict is to find a feasible, unserializable execution that is valid under a weak isolation model (i.e., causal or rc). To find such an execution, IsoPredict encodes and solves the following necessary and sufficient constraints for a predicted execution history, :
-
(1)
must be a feasible execution prefix222We allow to be a subset of to exclude transactions that may diverge from the observed execution (§4.5). An execution prefix is sufficient: If exists, a full execution history exists that has as a prefix and meets the criteria above. of the program that produced (§4.1).
-
(2)
must be unserializable (§4.2).
-
(3)
must be valid under (§4.3).
As an example, Figure 1(a) shows a serializable execution history that contains two deposit transactions (Algorithm 1) running concurrently. IsoPredict generates and solves the constraints sketched above, in order to predict the causal and rc but unserializable execution from Figure 2(a).
4.1. Encoding of Feasible Execution
This section describes the constraints that IsoPredict generates to ensure that is a feasible execution of the application that produced .
Session order
The predicted execution must preserve the observed execution’s session order (). IsoPredict generates constraints over a Boolean SMT function that takes two transactions as input; a transaction is an SMT data type representing the set of all executed transactions . The analysis generates the following constraints to preserve the observed execution’s :
otherwise |
For clarity, SMT constraints generated by IsoPredict are boxed throughput the paper. The way to understand the above is that, for every such that ,333Although the partial and total orders throughout the paper are irreflexive, the analysis never needs to generate irreflexivity constraints (e.g., for relation ) because it never generates any constraints that use . the analysis generates a constraint—either or depending on whether the transactions are ordered by .
Write–read order
Each read in the predicted execution can potentially read from any transaction that writes the same key.444Recall that a read to can only read from another transaction’s last write to (§2.1). To help reason about multiple reads in a transaction to the same key that have different writer transactions (and to help exclude potentially divergent events; §4.5), we introduce the notion of an event’s position: In each session, events are numbered with monotonically increasing integers. To ensure each read has exactly one writer transaction in the predicted execution, IsoPredict introduces an SMT function that takes as input a session and the position of a read event in the session, and returns the writer transaction that the read reads from. Like transactions, sessions are a finite SMT data type representing the set of all sessions. (Note that is left undefined if is not the position of a read event in .) IsoPredict generates the following constraints to ensure that is equal to some transaction that writes the same key:
where is ’s session, and is the set of positions of reads to in transaction .
IsoPredict encodes by generating constraints on Boolean SMT functions :
where is ’s session.
To encode , the analysis generates constraints on a Boolean SMT function that represents the union of all :
4.2. Encoding Unserializability
This section describes how the analysis encodes constraints for the predicted execution to be unserializable. The constraints must ensure that all possible commit orders are cyclic. §4.2.1 presents an approach that encodes the needed constraints exactly, resulting in long solving times. §4.2.2 presents an alternative approach that encodes a sufficient condition for unserializability, which has lower solving time than the first approach, but still has high coverage in our experiments.
4.2.1. Constraints that encode an exact condition
To encode that no acyclic exists for the predicted execution history, IsoPredict generates the following constraint:
where is defined as shown below. Note that in the constraint above, , which takes a transaction as input and evaluates to an integer indicating ’s position in the total order, is not an SMT function—it is a bound variable of the quantifier. Function is defined as follows:
where are all transactions in , and is a built-in SMT function that requires all input values to be distinct from each other. By mapping (t) to a unique integer for each , the first line of the equation above ensures that is a total order.
The second line of the equation ensures that is consistent with , , and , respectively. For simplicity and to reduce the size of the constraints, arbitration constraints are factored out into the function, which is defined as follows:
which is a straightforward encoding of the serializable arbitration constraints in Equation 1.
4.2.2. Constraints encoding a sufficient but unnecessary condition
Alternatively, the analysis can encode a sufficient, but unnecessary, condition for predicting an unserializable execution. We introduce a partial order, , that is a subset of every commit order for every valid predicted execution. If there exists a predicted execution for which is cyclic, then there cannot exist an acyclic for the predicted execution, meaning it is unserializable. In theory, this approach has the potential for missing unserializable executions that §4.2.1’s approach finds. But in our experiments, the -based approach predicts all unserializable executions that §4.2.1’s approach finds (§7.2).
We define to include all orders that must be in : session (), write–read (), and arbitration () orders. We also introduce an anti-dependency order () that must be in every , which allows adding more edges to and thus finding more unserializable executions. A challenge with encoding is that the arbitration and anti-dependency orders are both defined in terms of commit order, creating a circular dependency that leads to erroneous self-justifying edges in . We break both circular dependencies by introducing the notion of rank in the generated constraints. Next we describe anti-dependency order (), the circular dependency problem and our rank-based solution to it, and finally the constraints that the analysis generates.
Adding anti-dependency order () to
To make as large as possible while still being consistent with every valid , we add an anti-dependency () order to . must be part of any valid , as we prove in Appendix A. Intuitively, for any write–read relation , anti-dependency prevents future transactions that also write from being ordered between and in the commit order. More formally, we define as follows:
Figure 6 shows an example in which is cyclic only if is included.
The partial order can now be defined as the union of all orders that must be part of :
Adapting Equation 1 to use instead of , we define arbitration order, , as follows:
Circular dependency and rank
In the definitions above, note the circular dependencies between and and between and , which seem to permit “self-justifying” edges. As an example, consider Figure 6. According to the definitions, , and , allowing us to wrongly conclude and . To avoid such self-justifying edges, , , and in fact must be defined as the minimal relations that satisfy the above definitions.
How can we encode this “minimal relation” property in the SMT constraints? If IsoPredict simply encodes the above definitions as SMT constraints, the constraint solver will find self-justifying edges, resulting in spurious cycles and reporting executions that are not actually unserializable. For example, for Figure 6, the SMT solver would choose both and to be true, finding a cycle and wrongly reporting a predicted execution that is actually serializable.
We address this problem by introducing the notion of rank, which orders edges that depend on each other. IsoPredict relies on an integer SMT function to enforce the following rule:
For any relations and , if depends on , then .
Note that the rule does not require or . For Figure 6, rank constraints disallow and , which would require both and .
Generated constraints
IsoPredict generates arbitration and anti-dependency constraints on Boolean SMT functions and :
The following constraints ensure that is a partial order implied by , , , and :
To ensure that is cyclic, the analysis generates the following constraint:
If the solver finds a satisfying solution, a predicted unserializable execution exists. If the solver reports no satisfying solution, a predicted unserializable execution may or may not exist. In our experiments, a predicted unserializable execution never exists in this case.
We have not been able to come up with an execution for which our -based approach misses a predicted unserializable execution. We believe that such an execution should exist because otherwise it would imply a polynomial-time algorithm for deciding if an execution history is serializable—a problem that is NP-hard (Biswas and Enea, 2019).
4.3. Encoding Weak Isolation
This section describes the constraints that IsoPredict generates to ensure that the execution conforms to the target weak isolation model (causal or rc).
Regardless of the model, IsoPredict encodes as the transitive closure of and (§2.1), by generating constraints on a Boolean SMT function :
4.3.1. Causal consistency (causal)
To ensure that the predicted execution is causal, IsoPredict generates constraints that ensure that the transitive closure of causal arbitration order () and happens-before () is acyclic (§2.3). IsoPredict encodes the causal axiom (Equation 2) by generating constraints on a Boolean SMT function representing :
To ensure the execution is causal, there must exist a strict total order that is consistent with (Equation 3). IsoPredict generates the constraints on an integer SMT function :
4.3.2. Read committed (rc)
Similar to causal, IsoPredict generates constraints so that the transitive closure of rc arbitration order () and happens-before () is acyclic (§2.4). IsoPredict encodes the rc axiom (Equation 4) with the help of a Boolean SMT function that represents :
where is the set of positions of read events in transaction , is the set of positions of read to in transaction , and is ’s transaction. To ensure there exists a strict total order that is consistent with (Equation 5), IsoPredict generates constraints on an integer SMT function :
4.4. Prediction Examples
This section shows causal, unserializable behaviors predicted by IsoPredict on programs evaluated in §7. The actual executions consist of dozens of transactions and thousands of events; the figures show only the transactions and events relevant to predicting unserializable behavior.
Figure 6(a) shows an observed execution of the Wikipedia benchmark, and Figure 6(b) shows the causal, unserializable execution predicted by IsoPredict. In contrast, Figure 6(c) shows a different observed execution of Wikipedia, from which no causal, unserializable execution can be predicted. Figure 6(d) serves to illustrate that changing ’s read of to read from would lead to a non-causal execution (and thus will not be reported by IsoPredict).
Figure 7(a) shows an observed execution of the Smallbank benchmark, and Figure 7(b) shows the IsoPredict-predicted execution. As Figure 7(b) shows, a causal, unserializable predicted execution exists in which both reads read from the initial state (), as demonstrated by the cycle .
4.5. Handling Divergence in the Predicted Execution
Reading from a different write in the predicted execution than in the observed execution, may lead to different application behaviors. Specifically, code in the data store application that is control dependent on a read from a different writer transaction may generate different events. For example, consider the observed execution shown in Figures 8(a) and 8(b), which executes transactions shown in Algorithms 1 and 2. Figure 8(c) shows an unserializable predicted history that IsoPredict would find using the constraints presented so far. However, the predicted execution is infeasible: aborts if it reads from , making it impossible for to read from , as Figure 8(d) shows. IsoPredict (mostly) avoids make spurious predictions, by excluding (much of the) potentially divergent behavior.
Divergent behavior
To account for divergent behavior, we make a distinction between the predicted execution, which is generated by IsoPredict based on the observed execution, and what we call the validating execution, which is the execution that actually occurs if one tries to produce the predicted execution using the data store application. Divergent behaviors are behaviors that differ between the predicted and validating executions. We categorize divergent behaviors into two categories:
-
•
The validating execution reads or writes different keys or omits or adds events from the predicted execution, leading to a different execution history with different properties.
- •
The problem with divergent behavior is that an unserializable predicted execution can lead to a serializable validating execution. (The validating execution will always be a feasible execution conforming to the weak isolation model because validation ensures these properties; §5.)
Prediction boundary
IsoPredict accounts for divergence by generating prediction boundary constraints that exclude events that may be impacted by divergence—specifically, events that happen-after (i.e., inverse of ) any read event that reads from different writers in the predicted and observed executions. IsoPredict supports a prediction boundary that is strict or relaxed, as shown in Table 1. The strict boundary excludes events that happen-after events that read from a different writer in the predicted execution than in the observed execution. The strict boundary prevents false predictions except when a transaction in the predicted execution aborts in the validating execution. Alternatively, the relaxed boundary excludes events that happen-after transactions that read from a different writer, risking more false predictions but increasing the chances of finding an unserializable predicted execution.
Prediction | Divergent behaviors can | |
boundary | Excluded events | cause false predictions |
Strict |
Events that happen-after any read event with a different writer |
Abort-related only |
Relaxed |
Events that happen-after any transaction containing a read event with a different writer |
Any |
Figures 8(e) and 8(f) show strict and relaxed boundaries, respectively, applied to the prediction in Figure 8(c). The strict boundary excludes all events that happen-after ’s read (since it has a different writer than in Figure 8(b)); the resulting execution history is serializable. The relaxed boundary excludes all transactions that happen-after ’s read; the resulting execution history is unserializable. Although the relaxed boundary allows a false prediction in this example, in our evaluation the relaxed boundary results in few false predictions.
Generating prediction boundary constraints
Here we present IsoPredict’s constraints for excluding events using the prediction boundary. We show constraints for the strict prediction boundary, but the constraints for the relaxed prediction boundary are similar except they also constrain every session’s boundary to be the last event of a transaction.
The prediction boundary is delimited by a boundary event in each session, which is either (1) a read event, which reads from a different write in the predicted execution than in the observed execution, or (2) the last event in the session (which will always be a commit event). IsoPredict generates the following constraints on an integer SMT function to ensure that the boundary event for each session is either a read event or the last event (represented by position ):
Recall that (s) is the set of positions of reads to in the transaction .
To ensure that each read that happens-before the prediction boundary reads from the same write as in the observed execution, IsoPredict generates the following constraints, where is an integer SMT function that represents the last write of each read in the observed execution history (and is thus the analogue of for the observed execution):
where is ’s session and is ’s session.
A read to on or before the prediction boundary must read from a write to that is before the prediction boundary. IsoPredict ensures this property by generating the following constraints:
where is ’s session, is ’s session, and (t) is the position of ’s last write to key .
To exclude events after the prediction boundary, IsoPredict generates modified constraints for all arbitration and anti-dependency rules, as detailed in Appendix B.
5. Validation
Even by using the prediction boundary, IsoPredict’s predictive analysis may report unserializable predicted executions for which the corresponding validating execution is serializable. To rule out such predictions, IsoPredict can attempt to validate predicted executions, by executing the data store application based on the predicted execution history, and checking whether the resulting validating execution is unserializable.
Validating execution
Validation produces the validating execution using a query engine that takes the predicted execution as input. At each read() event, the query engine checks that (1) the corresponding read in the predicted execution also read from ; (2) the writer transaction from the predicted execution also wrote to in the validating execution; and (3) reading from in the validating execution will satisfy the weak isolation model (causal or rc). If any of these conditions is violated, we categorize the execution as having diverged, and the query engine chooses a different, weak isolation model–conforming writer for the read to read from. Note that it is always possible to keep executing while preserving causal or rc (Bouajjani et al., 2023). Furthermore, the validating execution may still be unserializable, as our evaluation shows.
Recall that the predicted execution history contains events only up to the prediction boundary. To avoid serendipitously introducing unserializable behaviors that were not part of the predicted execution (which could make it tough to measure the effectiveness of IsoPredict’s predictive analysis), validation executes each transaction in full that is on the boundary or that happens-before any transaction on the boundary—and then it terminates the execution. This approach is sufficient: If this execution prefix is unserializable, then so is the full execution.
Note that validation must directly control what transaction each read reads from, i.e., the write–read relation (). Our evaluation extends MonkeyDB (Biswas et al., 2021) to allow explicit control of (§6). In settings where MonkeyDB cannot be used, such as production systems, there are other ways to control . One is using resource locks (e.g., sp_getapplock in SQL Server) to force specific transaction orders that produce the desired relation.
Checking serializability
Validation generates constraints to check whether the validating execution history is serializable (which can be encoded more efficiently than unserializable, since serializable implies a total commit order exists). If the solver returns “satisfiable,” IsoPredict reports no prediction. Otherwise (the solver returns “unsatisfiable”), IsoPredict reports the validating execution, which is known to be a feasible, unserializable, weak isolation model–conforming execution.
6. Implementation
This section describes the implementation of IsoPredict, which is publicly available (Geng et al., 2024b).
Predictive analysis
We implemented IsoPredict’s predictive analysis (§4) as a Python program that uses Z3Py, the Python binding of the Z3 SMT solver (de Moura and Bjørner, 2008). Observed and predicted execution histories are in the form of traces containing read and write events and transaction and session identifiers, including the transaction that each read reads from. If Z3 finds a predicted unserializable execution, it either reports the predicted execution history in both textual and graphical forms, or passes the predicted history to the validation component, depending on how IsoPredict is configured.
To generate observed execution traces, we extended the implementation of MonkeyDB, a transactional key–value data store (Biswas et al., 2021). MonkeyDB handles relational queries by translating them to key–value queries. MonkeyDB executes transactions serially, and we configured it to choose the latest writer at each read, so observed executions are always serializable.
Validation
IsoPredict’s validation component replays the client application on a customized query engine that we also built on top of MonkeyDB. The query engine executes transactions one at a time, in an order dictated by the predicted execution, to ensure that read events always occur after their writers. At each read, the query engine chooses a last writer that satisfies the weak isolation model and, if possible, matches the predicted execution (§5). Validation uses Z3Py to generate and solve SMT constraints to determine if the validating execution history is unserializable, reporting the validating execution to the user in both textual and graphical forms if so.
The customized query engine handles transaction aborts by rewinding the predicted execution trace to the beginning of the current transaction. In our experiments, every transaction that aborted during the observed execution also aborts during the validating execution—except in a few cases, when a transaction that aborted in the observed execution and immediately precedes a committed transaction on the prediction boundary, actually commits in the validating execution. As for other divergent behavior, the resulting validating execution may or may not be unserializable.
7. Evaluation
This section evaluates how effectively and efficiently IsoPredict predicts unserializable executions under causal and rc, and it compares empirically against prior work MonkeyDB (Biswas et al., 2021).
7.1. Methodology
Prediction strategies
Pred. strategy | Encoding precision | Pred. boundary | Divergence false predictions? |
---|---|---|---|
Exact-Strict | Exact encoding | Strict | Only because of aborts |
Approx-Strict | Approximate encoding | Strict | Only because of aborts |
Approx-Relaxed | Approximate encoding | Relaxed | Yes |
Table 2 shows the combinations of unserializability constraints and prediction boundaries that we evaluated, which we call prediction strategies. The Exact-Strict prediction strategy uses precise encoding of unserializability (§4.2.1), while Approx-Strict and Approx-Relaxed encode the sufficient condition for unserializability (§4.2.2). Exact-Strict and Approx-Strict encode the strict prediction boundary, while Approx-Relaxed encode the relaxed prediction boundary.
Benchmarks
We evaluated IsoPredict and MonkeyDB using transactional workloads from OLTP-Bench, a database testing framework that generates various workloads for benchmarking relational databases (Difallah et al., 2013). Table 3 shows quantitative characteristics of the evaluated Benchmarks.
Our experiments used versions of the OLTP-Bench programs that the MonkeyDB authors ported to use simplified SQL queries recognized by MonkeyDB (Biswas et al., 2021). In these versions, each benchmark runs a nondeterministic number of transactions based on a specified time limit. For the purposes of our evaluation, we modified the benchmarks to be more deterministic for two reasons. First, determinism provides a more stable comparison among IsoPredict’s prediction strategies. Second, determinism helps with validation, since the validating execution can run the benchmark with the same RNG seed that the observed execution used. (To use validation in a production setting, one should record and replay the application (Galanis et al., 2008; Li et al., 2023).) We modified the benchmarks to be more deterministic by (1) fixing the number of sessions and transactions per session and (2) adding a random number generator (RNG) seed as a parameter to each benchmark. Although these modifications increase determinism, the benchmarks still execute nondeterministically because the interleaving of transactions is timing dependent. This source of nondeterminism does not hinder validation, which executes transactions in an order consistent with the predicted execution’s relation.
Small workload | Large workload | |||||||
---|---|---|---|---|---|---|---|---|
KV accesses | Committed txns | KV accesses | Committed txns | |||||
Program | Reads | Writes | Total | (Read-only) | Reads | Writes | Total | (Read-only) |
Smallbank | 669.7 | 14.7 | 11.0 | (3.5) | 1271.3 | 30.5 | 20.3 | (6.6) |
Voter | 763.0 | 6.0 | 12.0 | (11.0) | 919.0 | 6.0 | 24.0 | (23.0) |
TPC-C | 3297.3 | 763.0 | 11.9 | (0.9) | 7025.6 | 1502.4 | 23.8 | (1.7) |
Wikipedia | 1067.7 | 55.1 | 9.9 | (8.8) | 2677.1 | 111.1 | 22.8 | (20.6) |
We configured each benchmark with both small and large workloads, in which three sessions each execute four or eight transactions, resulting in 12 or 24 attempted transactions, respectively. The number of committed transactions is somewhat fewer because all programs except Voter occasionally abort a transaction based on application-specific logic.
Platform
All experiments ran on an Intel Xeon server at 2.3 GHz with 16 cores, hyperthreading enabled, and 187 GB of RAM, running Linux.
7.2. IsoPredict’s Effectiveness and Performance
Tables 4 and 5 show IsoPredict’s effectiveness and performance at predicting unserializable executions under causal and rc, respectively. For each benchmark and each of IsoPredict’s three prediction strategies, we ran IsoPredict on 10 executions, each of which used one of 10 RNG seeds, which we kept consistent across prediction strategies and isolation levels.
Prediction | Prediction | Validation | Constraint gen. | Solving time | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Program | strategy | Unk | Unsat | Sat | Validated | (Diverged) | # Literals | Time | Sat | Unsat |
Smallbank | Exact-Strict | 0 | 6 | 4 | 4 | (0) | K | s | s | s |
Approx-Strict | 0 | 6 | 4 | 4 | (1) | K | s | s | s | |
Approx-Relaxed | 0 | 0 | 10 | 9 | (1) | K | s | s | – | |
Voter | Exact-Strict | 0 | 10 | 0 | 0 | (0) | K | s | – | s |
Approx-Strict | 0 | 10 | 0 | 0 | (0) | K | s | – | s | |
Approx-Relaxed | 0 | 10 | 0 | 0 | (0) | K | s | – | s | |
TPC-C | Exact-Strict | 0 | 1 | 9 | 9 | (0) | K | s | s | s |
Approx-Strict | 0 | 1 | 9 | 9 | (0) | K | s | s | s | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (0) | K | s | s | – | |
Wikipedia | Exact-Strict | 1 | 9 | 0 | 0 | (0) | K | s | – | s |
Approx-Strict | 0 | 10 | 0 | 0 | (0) | K | s | – | s | |
Approx-Relaxed | 0 | 8 | 2 | 2 | (1) | K | s | s | s |
Prediction | Prediction | Validation | Constraint gen. | Solving time | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Program | strategy | T/O | Unsat | Sat | Validated | (Diverged) | # Literals | Time | Sat | Unsat |
Smallbank | Exact-Strict | 4 | 1 | 5 | 5 | (1) | K | s | s | s |
Approx-Strict | 1 | 0 | 9 | 9 | (0) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (0) | K | s | s | – | |
Voter | Exact-Strict | 9 | 1 | 0 | 0 | (0) | K | s | – | s |
Approx-Strict | 0 | 10 | 0 | 0 | (0) | K | s | – | s | |
Approx-Relaxed | 0 | 10 | 0 | 0 | (0) | K | s | – | s | |
TPC-C | Exact-Strict | 4 | 3 | 3 | 3 | (0) | K | s | s | s |
Approx-Strict | 2 | 0 | 8 | 8 | (0) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (2) | K | s | s | – | |
Wikipedia | Exact-Strict | 8 | 1 | 1 | 1 | (0) | K | s | s | s |
Approx-Strict | 0 | 9 | 1 | 1 | (0) | K | s | s | s | |
Approx-Relaxed | 0 | 8 | 2 | 2 | (2) | K | s | s | s |
Prediction | Prediction | Validation | Constraint gen. | Solving time | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Program | strategy | Unk | Unsat | Sat | Validated | (Diverged) | # Literals | Time | Sat | Unsat |
Smallbank | Exact-Strict | 0 | 0 | 10 | 10 | (0) | K | s | s | – |
Approx-Strict | 0 | 0 | 10 | 10 | (0) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (0) | K | s | s | – | |
Voter | Exact-Strict | 0 | 0 | 10 | 10 | (2) | K | s | s | – |
Approx-Strict | 0 | 0 | 10 | 10 | (7) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (10) | K | s | s | – | |
TPC-C | Exact-Strict | 0 | 0 | 10 | 10 | (0) | K | s | s | – |
Approx-Strict | 0 | 0 | 10 | 10 | (0) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (3) | K | s | s | – | |
Wikipedia | Exact-Strict | 2 | 1 | 7 | 7 | (2) | K | s | s | s |
Approx-Strict | 0 | 3 | 7 | 7 | (1) | K | s | s | s | |
Approx-Relaxed | 0 | 3 | 7 | 7 | (7) | K | s | s | s |
Prediction | Prediction | Validation | Constraint gen. | Solving time | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Program | strategy | T/O | Unsat | Sat | Validated | (Diverged) | # Literals | Time | Sat | Unsat |
Smallbank | Exact-Strict | 0 | 0 | 10 | 9 | (1) | K | s | s | – |
Approx-Strict | 0 | 0 | 10 | 10 | (1) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (1) | K | s | s | – | |
Voter | Exact-Strict | 0 | 0 | 10 | 10 | (2) | K | s | s | – |
Approx-Strict | 0 | 0 | 10 | 10 | (6) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (10) | K | s | s | – | |
TPC-C | Exact-Strict | 0 | 0 | 10 | 10 | (2) | K | s | s | – |
Approx-Strict | 0 | 0 | 10 | 10 | (2) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 10 | (4) | K | s | s | – | |
Wikipedia | Exact-Strict | 0 | 0 | 10 | 10 | (1) | K | s | s | – |
Approx-Strict | 0 | 0 | 10 | 10 | (1) | K | s | s | – | |
Approx-Relaxed | 0 | 0 | 10 | 9 | (10) | K | s | s | – |
Predictive analysis
The Sat column under Prediction reports the number of unserializable executions (out of 10) that IsoPredict found. The Approx-Relaxed prediction strategy generally predicts more than the other strategies because it uses the relaxed boundary. Although Exact-Strict can theoretically predict more executions than Approx-Strict, this never happened in our experiments.
IsoPredict consistently predicts more unserializable executions under rc than under causal, which makes sense because rc is strictly weaker than causal. Voter has the biggest difference—there were no successful predictions under causal. This is because every observed execution of Voter has only one writing (i.e., non-read-only) transaction (see Algorithm 3), which is not sufficient to predict an unserializable execution under causal.555More specifically, the initial state transaction and the writing transaction constitute the only pair of conflicting writes. If a transaction reads from the initial state, then a commit order with preceding is acyclic. On the other hand, if reads from another transaction, a commit order following is acyclic. Similarly, IsoPredict has low prediction rates for Wikipedia, which has few writing transactions. In contrast, under rc, a transaction may legally read both the initial state and the writing transaction, which is why IsoPredict has higher prediction rates for Voter and Wikipedia under rc than under causal. §4.4 and Appendix C present some observed and predicted executions from the evaluated benchmarks.
Validation
We configured IsoPredict to validate every predicted unserializable execution. The Validated column reports the number of validating executions that were unserializable. Across all experiments, all but three predicted executions were successfully validated as unserializable.
The Diverged column shows that, in many cases, the validating execution diverged, i.e., it could not match the predicted execution history (§5). Unsurprisingly, the relaxed boundary experienced significantly more divergence than the strict boundary. However, divergence rarely resulted in failed validation: Among the 81 divergent executions across Tables 4 and 5, only three failed validation (i.e., produced serializable executions). One validation failure was caused by divergent behavior unrelated to aborts (§5), and the other two failures were caused by previously aborted transactions being committed (an implementation issue discussed in §6).
Performance
The four rightmost columns of each table report the performance of IsoPredict’s predictive analysis, which consists of two components: (1) the time the analysis takes to generate SMT constraints (Constraint gen.) and (2) SMT solving time (Solving time). Each table also reports the size of the generated constraints (# Literals),666The Approx-Strict and Approx-Relaxed prediction strategies generate different constraints, but they have the same size. which correlates with constraint generation time. SMT solving is significantly faster for successful prediction (Sat) than for failed prediction (Unsat),777It makes sense that successful prediction, which finds a single satisfying solution, is faster than failed prediction, which requires the solver to prove that no satisfying solution exists. so the table reports the two average solving times separately.
Compared to the other prediction strategies, Exact-Strict, which generates a single quantified constraint, spends less time generating constraints but more time solving constraints because its constraints are inherently harder to solve. Approx-Relaxed and Approx-Strict have performance similar to each other, which makes sense since they share the same approximation techniques.
Generating constraints can take a long time—often longer than constraint-solving time. We investigated this issue by using the perf (perf, 2024) and py-spy (Frederickson, 2024) performance analysis tools on the slowest instance of constraint generation: the large workload of TPC-C under rc using the Approx-Relaxed strategy (Table 5). To the best of our understanding, 97% of time is spent in Python code (IsoPredict and Z3Py), and 3% is spent in C code (Z3). Of the time spent in Python, 81% is spent in Z3Py functions, with most time spent in the following Z3PY API functions and their callees: __call__(), And(), and Or(). The __call__() function is part of Z3Py’s implementation of SMT functions, which act as callable objects in Python. The And() and Or() functions create conjunction and disjunction clauses, respectively. Z3Py functions call into Z3 code written in C; an unknown fraction of the time spent in Z3Py is due to making cross-language calls from Z3Py to Z3.
7.3. Comparison with MonkeyDB
MonkeyDB is a transactional key–value data store that aims to produce unusual executions that are legal under a target isolation level (Biswas et al., 2021). MonkeyDB handles each read to a key by returning a randomly chosen value among the set of legal values under the target isolation level.
MonkeyDB and IsoPredict both aim to find erroneous executions under weak isolation, but they use completely different approaches. MonkeyDB relies on a customized query engine that produces a single execution, while IsoPredict uses predictive analysis to analyze an equivalence class of many executions at once. They also differ in how they define and expose unserializable behavior: IsoPredict tries to find an unserializable execution, while MonkeyDB uses programmer-crafted assertions to detect unserializable behaviors.
Tables 7 and 7 compare MonkeyDB and IsoPredict’s effectiveness at predicting unserializable executions. To account for MonkeyDB’s randomized approach, we ran it 100 times for each configuration: 10 times for each of the 10 RNG seeds used as benchmark input (§7.1). The percentage of these executions with an assertion failure is reported in the Fail column.
MonkeyDB | IsoPredict | ||
---|---|---|---|
Program | Fail | Unser | Unser |
Smallbank | 70% | 98% | 90% |
Voter | 70% | 80% | 0% |
TPC-C | 98% | 100% | 100% |
Wikipedia | 0% | 11% | 20% |
MonkeyDB | IsoPredict | ||
---|---|---|---|
Program | Fail | Unser | Unser |
Smallbank | 84% | 100% | 100% |
Voter | 56% | 80% | 0% |
TPC-C | 100% | 100% | 100% |
Wikipedia | 0% | 19% | 20% |
MonkeyDB | IsoPredict | MySQL | ||
---|---|---|---|---|
Program | Fail | Unser | Unser | Fail |
Smallbank | 100% | 100% | 100% | 0% |
Voter | 89% | 100% | 100% | 0% |
TPC-C | 100% | 100% | 100% | 50% |
Wikipedia | 54% | 54% | 70% | 0% |
MonkeyDB | IsoPredict | MySQL | ||
---|---|---|---|---|
Program | Fail | Unser | Unser | Fail |
Smallbank | 100% | 100% | 100% | 0% |
Voter | 95% | 100% | 100% | 0% |
TPC-C | 100% | 100% | 100% | 70% |
Wikipedia | 89% | 89% | 100% | 0% |
To compare MonkeyDB and IsoPredict directly, we computed whether each execution produced by MonkeyDB was unserializable, by generating and solving constraints corresponding to the definition of serializable. An assertion failure is a sufficient but unnecessary condition for an unserializable execution; hence, for MonkeyDB, the number of executions failing assertions (Fail) never exceeds the number of unserializable executions (Unser).
The IsoPredict column shows the percentage of executions that led to unserializable predictions that were successfully validated (i.e., same results as the Validation columns in Tables 4 and 5). The tables use the best-performing prediction strategy for each isolation level.
Quantitatively, MonkeyDB and IsoPredict are comparable, finding erroneous executions at similar rates, except for two cases. In one case—Voter under causal—MonkeyDB produces unserializable executions, but IsoPredict never predicts any. Voter issues only one write transaction under serializable (Algorithm 3), from which it is impossible to predict an unserializable execution under causal, because IsoPredict cannot predict events that did not happen in the observed execution. In contrast, since MonkeyDB chooses values on the fly, its choices of reads can lead Voter to perform additional writes, leading to unserializable behavior. In another case—Wikipedia under causal—IsoPredict is able to predict several unserializable executions while MonkeyDB never has assertion failures, since its assertions are not sensitive enough to detect unserializable behaviors.
Qualitatively, the approaches differ in two significant ways. First, IsoPredict does not require programmers to write assertions. Second and more significantly, IsoPredict predicts unserializable executions from observed executions, which in theory could be produced by any data store. In contrast, MonkeyDB’s approach requires its specialized query engine.
Comparison with regular execution
Both MonkeyDB and IsoPredict routinely produce unserializable executions for the evaluated programs, but a natural question is whether executing these programs normally on a real-world data store yields unserializable executions. To evaluate this question, we executed the programs using MySQL (MySQL, 2023a) in rc mode (MySQL does not support causal). As for the MonkeyDB runs, we executed each program 100 times—10 times for each of the 10 RNG seeds used as input to the program—and evaluated the assertions used by MonkeyDB.
Table 7’s MySQL columns show the percentage of runs in which an assertion failed, a sufficient condition for an unserializable history. The results show that Smallbank, Voter, and Wikipedia never experienced an assertion failure under regular execution.888It is an open question whether MySQL in rc mode can actually produce unserializable executions for these programs. Data store implementations may preclude behaviors that are theoretically possible under the target isolation level. TPC-C experienced an assertion failure half of the time on the small workload and 70% of the time on the large workload. In contrast, MonkeyDB and IsoPredict often produce assertion-failing, unserializable executions.
Differences between our MonkeyDB results and the MonkeyDB paper’s results
In our experiments, MonkeyDB triggered fewer assertion failures than reported in the MonkeyDB paper (Biswas et al., 2021). These differences exist because we found and fixed a few bugs in the ported benchmarks and their assertions, which eliminated a few spurious failures. We confirmed all of the bugs and fixes with the MonkeyDB authors (Biswas et al., 2023). To be clear, the differences do not impact the MonkeyDB paper’s takeaway: MonkeyDB often produces unserializable, erroneous executions for the evaluated programs.
8. Related Work
The closest existing approaches to IsoPredict are arguably MonkeyDB (Biswas et al., 2021), IsoDiff (Gan et al., 2020), 2AD (Warszawski and Bailis, 2017), and Sinha et al.’s predictive analysis (Sinha et al., 2012). As §7.3 explained, MonkeyDB produces a single execution, which may or may not be unserializable, while IsoPredict predicts unserializable executions from an observed execution. IsoPredict can in theory work with any data store that can generate execution traces, while MonkeyDB requires its specialized query engine.
IsoDiff and 2AD detect unserializable behaviors based on an observed execution (Gan et al., 2020; Warszawski and Bailis, 2017). They build an abstract graph that does not take into account potential dependencies between read values. As the 2AD paper acknowledges, “2AD’s abstract histories are value-agnostic and do not account for control flow within a program; in effect, 2AD’s abstract history construction process assumes that each variable read and written can assume arbitrary values. However, there are often dependencies (e.g., ) between the values that variables assume” (Warszawski and Bailis, 2017). As a result, 2AD incurs high false positive rates even after using programmer-guided refinement: 37 reported “witnesses” on average per application, but only 22 bugs across 12 applications, or 2 bugs on average per application (Warszawski and Bailis, 2017).
In contrast, IsoPredict accounts for dependencies among read values through its axiomatic encoding of constraints, which permits encoding of potential dependencies using the prediction boundary. IsoPredict may still report false positives, but for narrower reasons: divergent aborts or (only when using the relaxed boundary) intra-transaction dependencies.
Sinha et al.’s analysis predicts atomicity violations in shared-memory multithreaded programs by encoding the conditions for unserializability as SMT constraints (Sinha et al., 2012). A key difference with IsoPredict is that Sinha et al.’s work deals with execution histories of shared-memory programs, in which all pairs of conflicting accesses are fully ordered, while IsoPredict deals with execution histories of distributed data store applications, in which conflicting accesses are unordered in general. As a result, Sinha et al.’s work only needs to encode graph cyclicity, while IsoPredict must encode that every potential commit order is acyclic. Addressing this unique challenge led us to develop IsoPredict’s approximate encoding (§4.2.2). Other differences include the different prediction spaces: Sinha et al.’s analysis predicts different orderings of critical sections on the same lock, while IsoPredict predicts different write–read orders.
Dynamic analysis
Non-predictive dynamic analysis can check if an observed execution satisfies an isolation level. ECRacer checks whether an observed execution is serializable, using a relaxed definition of serializability that accounts for commutative operations (Brutschy et al., 2017). In contrast, IsoPredict finds new executions that violate serializability.
Prior work uses run-time testing and constraint solving to check if a data store provides a stated weak isolation level (Biswas and Enea, 2019; Kingsbury and Alvaro, 2020; Tan et al., 2020; Zhang et al., 2023; Zennou et al., 2022). In contrast, IsoPredict assumes the data store provides the target weak isolation level and predicts feasible unserializable executions.
Model checking explores multiple executions, avoiding exhaustively exploring all possible executions by using techniques such as dynamic partial order reduction (DPOR) (Abdulla et al., 2023; Bouajjani et al., 2023; Ghafoor et al., 2016). Conschecker uses a DPOR-based stateless model checking algorithm to verify distributed shared-memory programs under causal consistency (Abdulla et al., 2023). Bouajjani et al.’s work adapts DPOR-based algorithms to transactional database applications to check them under a range of isolation levels (Bouajjani et al., 2023).
Static analysis
Static analysis can find unserializable behavior, but precision and performance scale poorly with program size. C4 and Nagar and Jagannathan’s analysis detect serializability violations under causal consistency, eventual consistency, and snapshot isolation (Brutschy et al., 2018; Nagar and Jagannathan, 2018). Clotho uses static analysis, model checking, and test generation to detect unserializable executions; it avoids false positives by verifying the feasibility of unserializable behaviors (Rahmani et al., 2019). In contrast, IsoPredict detects unserializable behaviors with high precision by basing it on a single observed execution.
Isolation levels
IsoPredict generates constraints based on isolation levels encoded in Biswas and Enea’s axiomatic framework (Biswas and Enea, 2019). Other prior work besides Biswas and Enea’s has introduced axiomatic encodings of weak isolation levels (Bouajjani et al., 2017; Perrin et al., 2016; Cerone et al., 2015; Kaki et al., 2018).
Adya et al. define various isolation levels with dependency graphs where each level allows certain types of cycles (Adya et al., 2000). Their approach encompasses “classical” database isolation levels such as read committed and snapshot isolation, but not isolation levels typically used in distributed data stores such as causal consistency (Alglave et al., 2014; Bouajjani et al., 2017; Burckhardt, 2014; Hamza, 2015; Perrin et al., 2016) and eventual consistency (Burckhardt, 2014).
IsoPredict currently supports only causal and rc, by encoding axioms from Biswas and Enea’s framework (Biswas and Enea, 2019). We expect that extending IsoPredict to more isolation levels from their framework—read atomic (a.k.a. repeated reads) and snapshot isolation—to be straightforward. We do not know how difficult it would be to encode other isolation levels (e.g., eventual consistency and monotonic atomic view) into Biswas and Enea’s framework or into IsoPredict.
9. Conclusion
IsoPredict is the first predictive analysis for detecting unserializable behaviors of applications backed by weakly isolated data stores. IsoPredict’s design introduces novel approaches to address challenges involving constraint complexity, constraint encoding, and divergent behaviors. An evaluation shows that, based on observed executions of data store applications, IsoPredict effectively, precisely, and efficiently predicts feasible, unserializable behaviors.
Data-Availability Statement
An artifact reproducing this paper’s results is publicly available (Geng et al., 2024a).
Acknowledgements.
We thank the MonkeyDB authors (Biswas et al., 2021) for making their implementation publicly available and answering our questions about it; Vincent Beardsley and Noah Charlton for helpful discussions; and the anonymous reviewers for valuable feedback. This material is based in part upon work supported by the National Science Foundation under Grant Numbers NSF CCF-2118745, CSR-2106117, and OAC-2112606, and by Oracle America, Inc.References
- (1)
- Abdulla et al. (2023) Parosh Abdulla, Mohamed Faouzi Atig, S. Krishna, Ashutosh Gupta, and Omkar Tuppe. 2023. Optimal Stateless Model Checking for Causal Consistency. In Tools and Algorithms for the Construction and Analysis of Systems, Sriram Sankaranarayanan and Natasha Sharygina (Eds.). Springer Nature Switzerland, Cham, 105–125.
- Adya et al. (2000) A. Adya, B. Liskov, and P. O’Neil. 2000. Generalized isolation level definitions. In Proceedings of 16th International Conference on Data Engineering (Cat. No.00CB37073). IEEE Computer Society, Los Alamitos, CA, USA, 67–78. https://doi.org/10.1109/ICDE.2000.839388
- Ahamad et al. (1995) Mustaque Ahamad, Gil Neiger, James E. Burns, Prince Kohli, and P.W. Hutto. 1995. Causal Memory: Definitions, Implementation and Programming. Distributed Computing 9, 1 (1995), 37–49. https://doi.org/10.1007/BF01784241
- Alglave et al. (2014) Jade Alglave, Luc Maranget, and Michael Tautschnig. 2014. Herding Cats: Modelling, Simulation, Testing, and Data Mining for Weak Memory. ACM Trans. Program. Lang. Syst. 36, 2, Article 7 (Jul 2014), 74 pages. https://doi.org/10.1145/2627752
- Berenson et al. (1995) Hal Berenson, Phil Bernstein, Jim Gray, Jim Melton, Elizabeth O’Neil, and Patrick O’Neil. 1995. A Critique of ANSI SQL Isolation Levels. In Proceedings of the 1995 ACM SIGMOD International Conference on Management of Data (San Jose, California, USA) (SIGMOD ’95). ACM, New York, NY, USA, 1–10. https://doi.org/10.1145/223784.223785
- Biswas and Enea (2019) Ranadeep Biswas and Constantin Enea. 2019. On the Complexity of Checking Transactional Consistency. Proc. ACM Program. Lang. 3, OOPSLA, Article 165 (Oct 2019), 28 pages. https://doi.org/10.1145/3360591
- Biswas et al. (2021) Ranadeep Biswas, Diptanshu Kakwani, Jyothi Vedurada, Constantin Enea, and Akash Lal. 2021. MonkeyDB: Effectively Testing Correctness under Weak Isolation Levels. Proc. ACM Program. Lang. 5, OOPSLA, Article 132 (Oct 2021), 27 pages. https://doi.org/10.1145/3485546
- Biswas et al. (2023) Ranadeep Biswas, Diptanshu Kakwani, Jyothi Vedurada, Constantin Enea, and Akash Lal. 2023. Personal communication.
- Bouajjani et al. (2017) Ahmed Bouajjani, Constantin Enea, Rachid Guerraoui, and Jad Hamza. 2017. On verifying causal consistency. In Proceedings of the 44th ACM SIGPLAN Symposium on Principles of Programming Languages (Paris, France) (POPL ’17). Association for Computing Machinery, New York, NY, USA, 626–638. https://doi.org/10.1145/3009837.3009888
- Bouajjani et al. (2023) Ahmed Bouajjani, Constantin Enea, and Enrique Román-Calvo. 2023. Dynamic Partial Order Reduction for Checking Correctness against Transaction Isolation Levels. Proc. ACM Program. Lang. 7, PLDI, Article 129 (Jun 2023), 26 pages. https://doi.org/10.1145/3591243
- Brutschy et al. (2017) Lucas Brutschy, Dimitar Dimitrov, Peter Müller, and Martin Vechev. 2017. Serializability for Eventual Consistency: Criterion, Analysis, and Applications. In Proceedings of the 44th ACM SIGPLAN Symposium on Principles of Programming Languages (Paris, France) (POPL ’17). Association for Computing Machinery, New York, NY, USA, 458–472. https://doi.org/10.1145/3009837.3009895
- Brutschy et al. (2018) Lucas Brutschy, Dimitar Dimitrov, Peter Müller, and Martin Vechev. 2018. Static Serializability Analysis for Causal Consistency. In Proceedings of the 39th ACM SIGPLAN Conference on Programming Language Design and Implementation (Philadelphia, PA, USA) (PLDI 2018). Association for Computing Machinery, New York, NY, USA, 90–104. https://doi.org/10.1145/3192366.3192415
- Burckhardt (2014) Sebastian Burckhardt. 2014. Principles of Eventual Consistency. Found. Trends Program. Lang. 1, 1–2 (oct 2014), 1–150. https://doi.org/10.1561/2500000011
- Cerone et al. (2015) Andrea Cerone, Giovanni Bernardi, and Alexey Gotsman. 2015. A Framework for Transactional Consistency Models with Atomic Visibility. In 26th International Conference on Concurrency Theory (CONCUR 2015) (Leibniz International Proceedings in Informatics (LIPIcs), Vol. 42), Luca Aceto and David de Frutos Escrig (Eds.). Schloss Dagstuhl – Leibniz-Zentrum für Informatik, Dagstuhl, Germany, 58–71. https://doi.org/10.4230/LIPIcs.CONCUR.2015.58
- Cheng et al. (2023) Chaoyi Cheng, Mingzhe Han, Nuo Xu, Spyros Blanas, Michael D. Bond, and Yang Wang. 2023. Developer’s Responsibility or Database’s Responsibility? Rethinking Concurrency Control in Databases. In 13th Conference on Innovative Data Systems Research, CIDR 2023, Amsterdam, The Netherlands, January 8-11, 2023. www.cidrdb.org. https://www.cidrdb.org/cidr2023/papers/p30-cheng.pdf
- Corbett et al. (2012) James C. Corbett, Jeffrey Dean, Michael Epstein, Andrew Fikes, Christopher Frost, JJ Furman, Sanjay Ghemawat, Andrey Gubarev, Christopher Heiser, Peter Hochschild, Wilson Hsieh, Sebastian Kanthak, Eugene Kogan, Hongyi Li, Alexander Lloyd, Sergey Melnik, David Mwaura, David Nagle, Sean Quinlan, Rajesh Rao, Lindsay Rolig, Yasushi Saito, Michal Szymaniak, Christopher Taylor, Ruth Wang, and Dale Woodford. 2012. Spanner: Google’s Globally-Distributed Database. In 10th USENIX Symposium on Operating Systems Design and Implementation (OSDI 12). USENIX Association, Hollywood, CA, 261–264. https://www.usenix.org/conference/osdi12/technical-sessions/presentation/corbett
- Crooks et al. (2017) Natacha Crooks, Youer Pu, Lorenzo Alvisi, and Allen Clement. 2017. Seeing is Believing: A Client-Centric Specification of Database Isolation. In Proceedings of the ACM Symposium on Principles of Distributed Computing (Washington, DC, USA) (PODC ’17). ACM, New York, NY, USA, 73–82. https://doi.org/10.1145/3087801.3087802
- de Moura and Bjørner (2008) Leonardo de Moura and Nikolaj Bjørner. 2008. Z3: An Efficient SMT Solver. In Tools and Algorithms for the Construction and Analysis of Systems, C. R. Ramakrishnan and Jakob Rehof (Eds.). Springer Berlin Heidelberg, Berlin, Heidelberg, 337–340.
- Difallah et al. (2013) Djellel Eddine Difallah, Andrew Pavlo, Carlo Curino, and Philippe Cudre-Mauroux. 2013. OLTP-Bench: An Extensible Testbed for Benchmarking Relational Databases. Proc. VLDB Endow. 7, 4 (Dec 2013), 277–288. https://doi.org/10.14778/2732240.2732246
- Elhemali et al. (2022) Mostafa Elhemali, Niall Gallagher, Nick Gordon, Joseph Idziorek, Richard Krog, Colin Lazier, Erben Mo, Akhilesh Mritunjai, Somasundaram Perianayagam, Tim Rath, Swami Sivasubramanian, James Christopher Sorenson III, Sroaj Sosothikul, Doug Terry, and Akshat Vig. 2022. Amazon DynamoDB: A Scalable, Predictably Performant, and Fully Managed NoSQL Database Service. In 2022 USENIX Annual Technical Conference (USENIX ATC 22). USENIX Association, Carlsbad, CA, 1037–1048. https://www.usenix.org/conference/atc22/presentation/elhemali
- Frederickson (2024) Ben Frederickson. 2024. https://github.com/benfred/py-spy
- Galanis et al. (2008) Leonidas Galanis, Supiti Buranawatanachoke, Romain Colle, Benoît Dageville, Karl Dias, Jonathan Klein, Stratos Papadomanolakis, Leng Leng Tan, Venkateshwaran Venkataramani, Yujun Wang, and Graham Wood. 2008. Oracle Database Replay. In Proceedings of the 2008 ACM SIGMOD International Conference on Management of Data (Vancouver, Canada) (SIGMOD ’08). Association for Computing Machinery, New York, NY, USA, 1159–1170. https://doi.org/10.1145/1376616.1376732
- Gan et al. (2020) Yifan Gan, Xueyuan Ren, Drew Ripberger, Spyros Blanas, and Yang Wang. 2020. IsoDiff: Debugging Anomalies Caused by Weak Isolation. Proc. VLDB Endow. 13, 12 (Jul 2020), 2773–2786. https://doi.org/10.14778/3407790.3407860
- Geng et al. (2024a) Chujun Geng, Spyros Blanas, Michael D. Bond, and Yang Wang. 2024a. IsoPredict artifact. https://doi.org/10.5281/zenodo.10802748
- Geng et al. (2024b) Chujun Geng, Spyros Blanas, Michael D. Bond, and Yang Wang. 2024b. IsoPredict implementation. https://github.com/PLaSSticity/IsoPredict-implementation
- Ghafoor et al. (2016) M. Ghafoor, M. Mahmood, and J. Siddiqui. 2016. Effective Partial Order Reduction in Model Checking Database Applications. In 2016 IEEE International Conference on Software Testing, Verification and Validation (ICST). IEEE Computer Society, Los Alamitos, CA, USA, 146–156. https://doi.org/10.1109/ICST.2016.25
- Gilbert and Lynch (2002) Seth Gilbert and Nancy Lynch. 2002. Brewer’s conjecture and the feasibility of consistent, available, partition-tolerant web services. SIGACT News 33 (June 2002), 51–59. Issue 2. https://doi.org/10.1145/564585.564601
- Hamza (2015) Jad Hamza. 2015. Algorithmic Verification of Concurrent and Distributed Data Structures. Ph. D. Dissertation. PhD thesis, Université Paris Diderot.
- Huang et al. (2014) Jeff Huang, Patrick O’Neil Meredith, and Grigore Rosu. 2014. Maximal sound predictive race detection with control flow abstraction. In Proceedings of the 35th ACM SIGPLAN Conference on Programming Language Design and Implementation (Edinburgh, United Kingdom) (PLDI ’14). Association for Computing Machinery, New York, NY, USA, 337–348. https://doi.org/10.1145/2594291.2594315
- Kaki et al. (2018) Gowtham Kaki, Kapil Earanky, KC Sivaramakrishnan, and Suresh Jagannathan. 2018. Safe replication through bounded concurrency verification. Proc. ACM Program. Lang. 2, OOPSLA, Article 164 (Oct 2018), 27 pages. https://doi.org/10.1145/3276534
- Kingsbury and Alvaro (2020) Kyle Kingsbury and Peter Alvaro. 2020. Elle: Inferring Isolation Anomalies from Experimental Observations. Proc. VLDB Endow. 14, 3 (Nov 2020), 268–280. https://doi.org/10.14778/3430915.3430918
- Kini et al. (2017) Dileep Kini, Umang Mathur, and Mahesh Viswanathan. 2017. Dynamic race prediction in linear time. In Proceedings of the 38th ACM SIGPLAN Conference on Programming Language Design and Implementation (Barcelona, Spain) (PLDI 2017). Association for Computing Machinery, New York, NY, USA, 157–170. https://doi.org/10.1145/3062341.3062374
- Leino and Pit-Claudel (2016) K. R. M. Leino and Clément Pit-Claudel. 2016. Trigger Selection Strategies to Stabilize Program Verifiers. In Computer Aided Verification, Swarat Chaudhuri and Azadeh Farzan (Eds.). Springer International Publishing, Cham, 361–381.
- Li et al. (2023) Qian Li, Peter Kraft, Michael Cafarella, Çağatay Demiralp, Goetz Graefe, Christos Kozyrakis, Michael Stonebraker, Lalith Suresh, Xiangyao Yu, and Matei Zaharia. 2023. R3: Record-Replay-Retroaction for Database-Backed Applications. Proc. VLDB Endow. 16, 11 (Jul 2023), 3085–3097. https://doi.org/10.14778/3611479.3611510
- Mahajan et al. (2011) P. Mahajan, L. Alvisi, and M. Dahlin. 2011. Consistency, Availability, Convergence. Technical Report TR-11-22. Computer Science Department, University of Texas at Austin.
- MySQL (2023a) MySQL 2023a. http://www.mysql.com
- MySQL (2023b) MySQL 2023b. MySQL Cluster. https://www.mysql.com/products/cluster/
- Nagar and Jagannathan (2018) Kartik Nagar and Suresh Jagannathan. 2018. Automated Detection of Serializability Violations under Weak Consistency. arXiv:1806.08416 [cs.PL]
- Pavlo (2017) Andrew Pavlo. 2017. What Are We Doing With Our Lives? Nobody Cares About Our Concurrency Control Research. In Proceedings of the 2017 ACM International Conference on Management of Data (Chicago, Illinois, USA) (SIGMOD ’17). Association for Computing Machinery, New York, NY, USA, 3. https://doi.org/10.1145/3035918.3056096
- perf (2024) perf 2024. https://perf.wiki.kernel.org/index.php/Main_Page
- Perrin et al. (2016) Matthieu Perrin, Achour Mostefaoui, and Claude Jard. 2016. Causal Consistency: Beyond Memory. In Proceedings of the 21st ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming (Barcelona, Spain) (PPoPP ’16). Association for Computing Machinery, New York, NY, USA, Article 26, 12 pages. https://doi.org/10.1145/2851141.2851170
- Rahmani et al. (2019) Kia Rahmani, Kartik Nagar, Benjamin Delaware, and Suresh Jagannathan. 2019. CLOTHO: Directed Test Generation for Weakly Consistent Database Systems. Proc. ACM Program. Lang. 3, OOPSLA, Article 117 (Oct 2019), 28 pages. https://doi.org/10.1145/3360543
- Roemer et al. (2020) Jake Roemer, Kaan Genç, and Michael D. Bond. 2020. SmartTrack: efficient predictive race detection. In Proceedings of the 41st ACM SIGPLAN Conference on Programming Language Design and Implementation (London, UK) (PLDI 2020). Association for Computing Machinery, New York, NY, USA, 747–762. https://doi.org/10.1145/3385412.3385993
- Said et al. (2011) Mahmoud Said, Chao Wang, Zijiang Yang, and Karem Sakallah. 2011. Generating Data Race Witnesses by an SMT-Based Analysis. In NASA Formal Methods, Mihaela Bobaru, Klaus Havelund, Gerard J. Holzmann, and Rajeev Joshi (Eds.). Springer Berlin Heidelberg, Berlin, Heidelberg, 313–327. https://doi.org/10.1007/978-3-642-20398-5_23
- Sinha et al. (2012) Arnab Sinha, Sharad Malik, Chao Wang, and Aarti Gupta. 2012. Predicting Serializability Violations: SMT-Based Search vs. DPOR-Based Search. In Hardware and Software: Verification and Testing, Kerstin Eder, João Lourenço, and Onn Shehory (Eds.). Springer Berlin Heidelberg, Berlin, Heidelberg, 95–114. https://doi.org/10.1007/978-3-642-34188-5_11
- Snowflake (2023) Snowflake 2023. Snowflake transactions. https://docs.snowflake.com/en/sql-reference/transactions
- Tan et al. (2020) Cheng Tan, Changgeng Zhao, Shuai Mu, and Michael Walfish. 2020. COBRA: making transactional key-value stores verifiably serializable. In Proceedings of the 14th USENIX Conference on Operating Systems Design and Implementation (OSDI’20). USENIX Association, USA, Article 4, 18 pages. https://www.usenix.org/conference/osdi20/presentation/tan
- Tang et al. (2022) Chuzhe Tang, Zhaoguo Wang, Xiaodong Zhang, Qianmian Yu, Binyu Zang, Haibing Guan, and Haibo Chen. 2022. Ad Hoc Transactions in Web Applications: The Good, the Bad, and the Ugly. In Proceedings of the 2022 International Conference on Management of Data (Philadelphia, PA, USA) (SIGMOD ’22). Association for Computing Machinery, New York, NY, USA, 4–18. https://doi.org/10.1145/3514221.3526120
- Tunç et al. (2023) Hünkar Can Tunç, Umang Mathur, Andreas Pavlogiannis, and Mahesh Viswanathan. 2023. Sound Dynamic Deadlock Prediction in Linear Time. Proc. ACM Program. Lang. 7, PLDI, Article 177 (Jun 2023), 26 pages. https://doi.org/10.1145/3591291
- Warszawski and Bailis (2017) Todd Warszawski and Peter Bailis. 2017. ACIDRain: Concurrency-Related Attacks on Database-Backed Web Applications. In Proceedings of the 2017 ACM International Conference on Management of Data (Chicago, Illinois, USA) (SIGMOD ’17). ACM, New York, NY, USA, 5–20. https://doi.org/10.1145/3035918.3064037
- Zennou et al. (2022) Rachid Zennou, Ranadeep Biswas, Ahmed Bouajjani, Constantin Enea, and Mohammed Erradi. 2022. Checking Causal Consistency of Distributed Databases. Computing 104, 10 (Oct 2022), 2181–2201. https://doi.org/10.1007/s00607-021-00911-3
- Zhang et al. (2023) Jian Zhang, Ye Ji, Shuai Mu, and Cheng Tan. 2023. Viper: A Fast Snapshot Isolation Checker. In Proceedings of the Eighteenth European Conference on Computer Systems (Rome, Italy) (EuroSys ’23). Association for Computing Machinery, New York, NY, USA, 654–671. https://doi.org/10.1145/3552326.3567492
Appendix A Proof that Anti-Dependency Implies Commit Order
Here we prove the following claim from §4.2.2: Anti-dependency order must imply commit order, i.e., for every valid . The proof proceeds by showing that violating anti-dependency order violates arbitration order:
Proof.
Suppose there exist such that , but ). By the definition of anti-dependency, let be a key and be a transaction such that writes , , and . Because and is a total order, therefore . Then according to the arbitration rule (Equation 1). However, contradicts since is a total order. ∎
Appendix B IsoPredict’s Full Constraints using the Prediction Boundary
This section shows the constraints generated by IsoPredict’s predictive analysis using the strict prediction boundary. For completeness we show all constraints generated by IsoPredict, including those that are unchanged compared with §4.
B.1. Encoding of Feasible Execution
otherwise |
where is ’s session and is ’s session.
where is ’s session and is ’s session, and (t) is the position of ’s last write to key .
Recall that (s) is the set of positions of reads to in the transaction .
where is ’s session.
B.2. Encoding of Unserializability
B.2.1. Precise encoding
where is defined as follows:
where are all transactions in , and is a built-in SMT function that requires all input values to be distinct from each other.
B.2.2. Approximate encoding
B.3. Encoding of Weak Isolation
B.3.1. Causal consistency
B.3.2. Read committed
where is the set of positions of read events in transaction , is the set of reads to in transaction , and is ’s session.
Appendix C Patterns of Observed and Predicted Executions
Figure 10 shows several observed executions and their unserializable predictions from our experiments. The actual executions consist of dozens of transactions and thousands of events, but the figures show only the transactions and events relevant to predicting unserializable behavior.