def test_init(self) -> None: h: History # empty history # with self.assertRaises(ValueError): h = History([]) # transaction order # with self.assertRaises(ValueError): h = History([ HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 8, 0.0, 0.0), HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 0, 0.0, 0.0, ), ]) h = self.get_history()
def gen_final_txn(txn_id: int, obj_list: List[DBObject]) -> Transaction: """ Generate final transaction to reads all objects This transaction should happend without concurrency """ ops: List[Operation] = [ Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Operation(Operation.Type.BEGIN), ] for obj in obj_list: ops.append(Operation(Operation.Type.READ, obj=obj)) ops.append(Operation(Operation.Type.COMMIT)) return Transaction(txn_id, ops)
def gen_init_txn(txn_id: int, obj_list: List[DBObject]) -> Transaction: """ Generate initial transaction to set initial value for objects This transaction should happend without concurrency """ ops: List[Operation] = [ Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Operation(Operation.Type.BEGIN), ] for obj in obj_list: ops.append(Operation(Operation.Type.WRITE, obj=obj, value=obj_ver[obj.id])) ops.append(Operation(Operation.Type.COMMIT)) return Transaction(txn_id, ops)
def gen_op( obj_list: List[DBObject], table_names: List[str], write_rate: float, predicate_read_rate: float, for_update: bool, chosen_len: int, ) -> List[Operation]: """ Generate a single operation By fixing a chosen len across a transaction, it makes it more likely for there to be conflicts """ rnd: float = random.random() if rnd < write_rate: obj: DBObject = random.choice(obj_list) # This creates an object if it doesn't exist # Note that we cannot rely on the object being created if obj_ver[obj_id] > 0. # This is because obj_ver denotes the order in which the statements are *generated* not executed # It is incremental to ensure *uniqueness*, not *order* # For instance, "1,2,0,4,3" is a valid value for an object, but "1,2,1,4,3" is not # obj_ver[obj.id] += 1 return [ Operation(Operation.Type.READ, obj=obj, for_update=for_update), Operation(Operation.Type.WRITE, obj=obj, value=obj_ver[obj.id]), ] elif write_rate <= rnd < write_rate + predicate_read_rate: return [ Operation( Operation.Type.PREDICATE_READ, tables=table_names, value=chosen_len, for_update=for_update, ) ] else: return [ Operation( Operation.Type.READ, obj=random.choice(obj_list), for_update=for_update, ) ]
def process_txn(obj_list: List[DBObject], conn: DBConn, conn_id: int, txn: Transaction) -> Iterator[HistoryElem]: """ Process a transaction as an iterator """ object_versions: Dict[int, List[int]] = dict() try: for op in txn.ops: invoc: float = time.time() ret: Optional[List[Tuple[Any, ...]]] if op.type == Operation.Type.WRITE: prev_version = object_versions[op.obj.id] if op.obj.id in object_versions else list() ret = conn.execute(op.stmt(prev_version)) else: ret = conn.execute(op.stmt()) if op.type == Operation.Type.PREDICATE_READ: resp: float = time.time() yield HistoryElem(op, Result(value=ret), conn_id, txn.id, invoc, resp) for tup in ret: object_versions[tup[0]] = [int(v) for v in tup[1].strip().split(",")] yield HistoryElem( Operation(Operation.Type.READ, obj=obj_list[tup[0]]), Result(value=[(tup[1],)]), conn_id, txn.id, invoc, resp, ) else: res: Result = Result(value=ret) if ret is not None else Result() if res.is_ok() and res.is_value(): object_versions[op.obj.id] = res.value() yield HistoryElem(op, res, conn_id, txn.id, invoc) except Exception as e: conn.process_exception(e) yield HistoryElem(op, Result(exception=e), conn_id, txn.id, invoc)
def gen_transaction( txn_id: int, obj_list: List[DBObject], table_names: List[str], isolation_level: str, min_size: int, max_size: int, abort_rate: float, write_rate: float, predicate_read_rate: float, for_update: bool, ) -> Transaction: """ Generates a list of SQL statemtents for a transaction <obj_list>: list of objects <isolation_level>: isolation level for the transaction <min_size>: minimum size of the transaction (in number of operations) <max_size>: maximum size of the transaction (in number of operations) <abort_rate>: abort rate (domain = [0.0, 1.0]) <write_rate>: write rate (domain = [0.0, 1.0]) <predicate_read_rate>: predicate read rate (domain = [0.0, 1.0]) """ def gen_op( obj_list: List[DBObject], table_names: List[str], write_rate: float, predicate_read_rate: float, for_update: bool, chosen_len: int, ) -> List[Operation]: """ Generate a single operation By fixing a chosen len across a transaction, it makes it more likely for there to be conflicts """ rnd: float = random.random() if rnd < write_rate: obj: DBObject = random.choice(obj_list) # This creates an object if it doesn't exist # Note that we cannot rely on the object being created if obj_ver[obj_id] > 0. # This is because obj_ver denotes the order in which the statements are *generated* not executed # It is incremental to ensure *uniqueness*, not *order* # For instance, "1,2,0,4,3" is a valid value for an object, but "1,2,1,4,3" is not # obj_ver[obj.id] += 1 return [ Operation(Operation.Type.READ, obj=obj, for_update=for_update), Operation(Operation.Type.WRITE, obj=obj, value=obj_ver[obj.id]), ] elif write_rate <= rnd < write_rate + predicate_read_rate: return [ Operation( Operation.Type.PREDICATE_READ, tables=table_names, value=chosen_len, for_update=for_update, ) ] else: return [ Operation( Operation.Type.READ, obj=random.choice(obj_list), for_update=for_update, ) ] size: int = random.randint(min_size, max_size) # How many times, on average, each txn will write to an object # AVG_WRITE_PER_OBJECT_PER_TXN: float = (write_rate * 0.5 * (min_size + max_size)) / len(obj_list) # This is a bit hacky, but multiplying AVG_WRITE_PER_OBJECT_PER_TXN by # the transaction id gives the approximate average size of each object at this point # since it approximates sum([AVG_WRITE_PER_OBJECT_PER_TXN] * N_TXN_UNTIL_THIS_POINT) # AVG_OBJECT_SIZE: int = int(AVG_WRITE_PER_OBJECT_PER_TXN * txn_id) ops: List[Operation] = [ Operation(Operation.Type.SET_ISOLATION, isolation_level=isolation_level), Operation(Operation.Type.BEGIN), ] for _ in range(size): # Using this hacky math makes the predicate reads more likely to return # interesting queries # # We intentionally skew in favour of returning less values, which # makes this more prone to returning less values, and consequently # generating more anti-dependencies # for op in gen_op( obj_list, table_names, write_rate, predicate_read_rate, for_update, random.randint(int(AVG_OBJECT_SIZE * 0.85), int(AVG_OBJECT_SIZE * 1.35)), ): ops.append(op) if random.random() < abort_rate: ops.append(Operation(Operation.Type.ROLLBACK)) else: ops.append(Operation(Operation.Type.COMMIT)) return Transaction(txn_id, ops)
def get_g1b_anomaly_hist(self) -> History: obj: DBObject = DBObject(0, "tab") hist: History = History([ # 0 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 0, 0.0, 0.0), # 1 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 0, 0.0, 0.0), # 2 HistoryElem(Operation(Operation.Type.WRITE, obj=obj, value=0), Result(), 0, 0, 0.0, 0.0), # 3 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 0, 0.0, 0.0), # 4 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 1, 0.0, 0.0), # 5 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 1, 0.0, 0.0), # 6 HistoryElem(Operation(Operation.Type.WRITE, obj=obj, value=1), Result(), 0, 1, 0.0, 0.0), # 7 HistoryElem(Operation(Operation.Type.WRITE, obj=obj, value=2), Result(), 0, 1, 0.0, 0.0), # 8 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 1, 0.0, 0.0), # 9 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 2, 0.0, 0.0), # 10 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 2, 0.0, 0.0), # 11 HistoryElem(Operation(Operation.Type.READ, obj=obj), Result(value=[("0,1", )]), 0, 2, 0.0, 0.0), # 12 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 2, 0.0, 0.0), # 13 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 3, 0.0, 0.0), # 14 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 3, 0.0, 0.0), # 15 HistoryElem(Operation(Operation.Type.READ, obj=obj), Result(value=[("0", )]), 0, 3, 0.0, 0.0), # 16 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 8, 0.0, 0.0), ]) hist[2].op.stmt([]) hist[6].op.stmt([0]) hist[7].op.stmt([0, 1]) return hist
def get_history(self) -> History: obj_list = [ DBObject(0, "tab"), DBObject(1, "tab"), DBObject(2, "tab"), ] self._obj_list = obj_list hist: History = History( [ # 0 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 0, 0.0, 0.0), # 1 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 0, 0.0, 0.0), # 2 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[0], value=0), Result(), 0, 0, 0.0, 0.0), # 3 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[1], value=0), Result(), 0, 0, 0.0, 0.0), # 4 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[2], value=0), Result(), 0, 0, 0.0, 0.0), # 5 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 0, 0.0, 0.0), # 6 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 1, 0.0, 0.0), # 7 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 1, 0.0, 0.0), # 8 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[0], value=1), Result(), 0, 1, 0.0, 0.0), # 9 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[1], value=3), Result(), 0, 1, 0.0, 0.0), # 9 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 1, 0.0, 0.0), # 11 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 2, 0.0, 0.0), # 12 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 2, 0.0, 0.0), # 13 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[1], value=1), Result(), 0, 2, 0.0, 0.0), # 14 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[1], value=2), Result(), 0, 2, 0.0, 0.0), # 15 HistoryElem(Operation(Operation.Type.READ, obj=obj_list[0]), Result(value=[("0,1", )]), 0, 2, 0.0, 0.0), # 16 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 2, 0.0, 0.0), # 17 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 3, 0.0, 0.0), # 18 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 3, 0.0, 0.0), # 19 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[2], value=1), Result(), 0, 3, 0.0, 0.0), # 20 HistoryElem( Operation(Operation.Type.ROLLBACK, isolation_level="serializable"), Result(), 0, 3, 0.0, 0.0), # 21 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 4, 0.0, 0.0), # 22 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 4, 0.0, 0.0), # 23 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[2], value=2), Result(), 0, 4, 0.0, 0.0), # 24 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(exception=TimeoutError("Connection Reset")), 0, 4, 0.0, 0.0, ), # 25 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 5, 0.0, 0.0), # 26 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 5, 0.0, 0.0), # 27 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[2], value=3), Result(), 0, 5, 0.0, 0.0), # 28 HistoryElem( Operation(Operation.Type.ROLLBACK, isolation_level="serializable"), Result(exception=TimeoutError("Connection Reset")), 0, 5, 0.0, 0.0, ), # 29 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 6, 0.0, 0.0), # 30 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 6, 0.0, 0.0), # 31 HistoryElem( Operation(Operation.Type.WRITE, obj=obj_list[2], value=4), Result(exception=TimeoutError("Connection Reset")), 0, 6, 0.0, 0.0, ), # 32 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 7, 0.0, 0.0), # 33 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 7, 0.0, 0.0), # 34 HistoryElem( Operation(Operation.Type.READ, obj=obj_list[2]), Result(exception=TimeoutError("Connection Reset")), 0, 7, 0.0, 0.0, ), # 35 HistoryElem( Operation(Operation.Type.SET_ISOLATION, isolation_level="serializable"), Result(), 0, 8, 0.0, 0.0), # 36 HistoryElem( Operation(Operation.Type.BEGIN, isolation_level="serializable"), Result(), 0, 8, 0.0, 0.0), # 37 HistoryElem(Operation(Operation.Type.READ, obj=obj_list[0]), Result(value=[("0,1", )]), 0, 8, 0.0, 0.0), # 38 HistoryElem(Operation(Operation.Type.READ, obj=obj_list[1]), Result(value=[("0,1,2", )]), 0, 8, 0.0, 0.0), # 39 HistoryElem(Operation(Operation.Type.READ, obj=obj_list[2]), Result(value=[("0", )]), 0, 8, 0.0, 0.0), # 40 HistoryElem( Operation(Operation.Type.COMMIT, isolation_level="serializable"), Result(), 0, 8, 0.0, 0.0), ]) hist[2].op.stmt([]) hist[3].op.stmt([]) hist[4].op.stmt([]) hist[8].op.stmt([0]) hist[9].op.stmt([0]) hist[13].op.stmt([0]) hist[14].op.stmt([0, 1]) return hist