21 releases (5 breaking)

0.6.0 Mar 18, 2024
0.5.1 Mar 9, 2024
0.4.3 Mar 9, 2024
0.3.0 Mar 9, 2024
0.1.11 Mar 5, 2024

#45 in Database implementations

26 downloads per month

MIT license

29KB
566 lines

Airomem-rs

Release at crates.io
(Toy) persistence library for Rust inspired by prevayler for java and named after its wrapper airomem.

It is an implementation of the Prevalent System design pattern, in which business objects are kept live in memory and transactions are journaled for system recovery.

Assumptions

  • All data lives in memory.
  • By default every transaction is saved to append-only journal file and immediately fsynced. By that, individual writes are slow, but they SHOULD survive crashes (e.g. power outage, software panic).
    However, you can set manual sync. See JournalFlushPolicy for more info. Recommended for data that may be lost (e.g. cache).
  • I don't guarantee durability, it's created for toy projects or non-relevant data like cache.

Features

  • - saving executed transactions to append only file
  • - split journal log file if too big - while restoring data, all journal logs are loaded at once from disk to maximise throughput (and for simplicity reasons)
  • - snapshots for faster recovery
  • - stable api

Resources

Example

type UserId = usize;
type SessionsStore = JsonStore<Sessions, SessionsTx>;

#[derive(Serialize, Deserialize, Default)]
pub struct Sessions {
    tokens: HashMap<String, UserId>,
    operations: usize,
}

MergeTx!(pub SessionsTx<Sessions> = CreateSession | DeleteSession);

#[derive(Serialize, Deserialize)]
pub struct CreateSession {
    token: String,
    user_id: UserId,
}

impl Tx<Sessions> for CreateSession {
    fn execute(self, data: &mut Sessions) {
        data.operations += 1;
        data.tokens.insert(self.token, self.user_id);
    }
}

#[derive(Serialize, Deserialize)]
pub struct DeleteSession {
    token: String,
}

impl Tx<Sessions, Option<UserId>> for DeleteSession {
    fn execute(self, data: &mut Sessions) -> Option<UserId> {
        data.operations += 1;
        data.tokens.remove(&self.token)
    }
}

#[tokio::test]
async fn test_mem_commit() {
    let dir = tempdir().unwrap();
    let mut store: SessionsStore =
        Store::open(JsonSerializer, StoreOptions::default(), dir.into_path())
            .await
            .unwrap();
    let example_token = "access_token".to_string();
    let example_uid = 1;
    store
        .commit(CreateSession {
            token: example_token.clone(),
            user_id: example_uid,
        })
        .await
        .unwrap();

    let mut expected_tokens = HashMap::new();
    expected_tokens.insert(example_token.clone(), example_uid);
    assert_eq!(store.query().tokens, expected_tokens);

    let deleted_uid = store
        .commit(DeleteSession {
            token: example_token,
        })
        .await
        .unwrap();
    assert_eq!(deleted_uid, Some(example_uid));
}

Dependencies

~3.5–5.5MB
~98K SLoC