al8n / skipdb

An embedded, in-memory, zero-copy, atomicity, consistency, isolation, MVCC, almost lock-free and serializable snapshot isolation database engine.
Apache License 2.0
204 stars 4 forks source link

Transactions are not serializable #10

Closed arthurprs closed 6 months ago

arthurprs commented 6 months ago

Write transactions are not serializable as stated.

See example below (one more here)

#[test]
fn txn_write_skew3() {
  let db: ComparableDb<&'static str, u64> = ComparableDb::new();

  // Setup
  let mut txn = db.write();
  txn.insert("a1", 10).unwrap();
  txn.insert("b1", 100).unwrap();
  txn.insert("b2", 200).unwrap();
  txn.commit().unwrap();
  assert_eq!(1, db.version());

  //
  let mut txn1 = db.write();
  let val = txn1
    .range("a".."b")
    .unwrap()
    .map(|ele| *ele.value())
    .sum::<u64>();
  txn1.insert("b3", 10).unwrap();
  assert_eq!(10, val);

  let mut txn2 = db.write();
  let val = txn2
    .range("b".."c")
    .unwrap()
    .map(|ele| *ele.value())
    .sum::<u64>();
  txn2.insert("a3", 300).unwrap();
  assert_eq!(300, val);
  txn2.commit().unwrap();
  txn1.commit().unwrap_err();

  let mut txn3 = db.write();
  let val = txn3
    .iter()
    .unwrap()
    .filter_map(|ele| {
      if ele.key().starts_with('a') {
        Some(*ele.value())
      } else {
        None
      }
    })
    .sum::<u64>();
  assert_eq!(310, val);
}
al8n commented 6 months ago

Hi, Thanks for pointing it out!

I found that the issue is that the logical dependency tracker is not working because, in this test case, txn1 reads do not overlap with txn2 reads, so in the has_conflict, it cannot detect logical conflict as a new a3 written by txn2. Anyway, I need more work on the logical dependency tracker.

al8n commented 6 months ago

Thanks again for pointing out those two test cases! The test cases are fixed in #11.