pjtatlow / jammdb

Just Another Memory Mapped Database
Apache License 2.0
273 stars 21 forks source link

Read-write operations #27

Closed lveillard closed 1 year ago

lveillard commented 1 year ago

Hello! Noob question here as i'm using your project to learn rust.

Is there a way to read and write in the same transaction in order to do some conditional .put() ?

For instance adding one (key,value) only if the value is not present already

Example: I'm opening a tx in the root,

then sending the buckets to some functions as references and inside that function i'm checking if a key exist, and if it exist, i'm throwing an error , and if not, i'm doing a .put()

 match schema_index_kinds_bucket.get(kindName) {
        Some(data) => match data {
            Data::Bucket(_) => Err(Error::KindExists),
            Data::KeyValue(_) => Err(Error::KindExists),
        },
        None => {
            schema_kinds_bucket
                .put(kindId.to_be_bytes(), kindName)
                .map_err(Error::Database)?;
            schema_index_kinds_bucket
                .put(kindName, kindId.to_be_bytes())
                .map_err(Error::Database)?;
            Ok(())
        }
    }

The error I get, which makes sense if this is not compatible with read-write, is that the "schema_kinds_bucket" may not live long enough

pjtatlow commented 1 year ago

This is definitely something that's supported! Without seeing more code, I'm not 100% sure what's going wrong, but here's a little test I wrote based on your code that seems to work fine.

#[test]
fn test() -> Result<(), Error> {
    let random_file = common::RandomFile::new();

    let db = OpenOptions::new()
        .strict_mode(true)
        .open(&random_file.path)?;

    let tx = db.tx(true)?;
    let schema_index_kinds_bucket = tx.get_or_create_bucket("schema-index-kinds")?;
    let schema_kinds_bucket = tx.get_or_create_bucket("schema-kinds")?;
    let kindName = "kindName";
    let kindId = 123_u8;

    match schema_index_kinds_bucket.get(kindName) {
        Some(_) => return Err(Error::IncompatibleValue),
        None => {
            schema_kinds_bucket.put(kindId.to_be_bytes(), kindName)?;
            schema_index_kinds_bucket.put(kindName, kindId.to_be_bytes())?;
        }
    }
    Ok(())
}
lveillard commented 1 year ago

I was messing with borrows and other rust concepts but it definitively works.

Thanks for the example as it helped me to enhance some of my patterns :) Let me close the issue.

Btw, I did some benchmarking and is crazy how fast this goes!

The big limitations: 1) One single transaction with a million keys write is really fast, but jammDB is less adapted to situations where the transactions are batched. The differ

For instance, for hundred keys in a slow laptop:

[basic]:write_batch_one_key_per_tx
time:   [162.69 ms 165.52 ms 168.72 ms]

[basic]:write_batch_single_tx 
time:   [3.0183 ms 3.2803 ms 3.5590 ms]

2) No concurrent writes.

On the other hand buckets are really performant to store edges where the predicate is the name of the bucket. The difference with other KVs forced me to check my code like 100 times looking for a mistake 😅 but it seem just to be way more performant!