-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Promises for Workflows (#1515)
- Loading branch information
1 parent
dbf2f3e
commit bdf0106
Showing
25 changed files
with
1,200 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH. | ||
// All rights reserved. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the LICENSE file. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0. | ||
|
||
use crate::keys::{define_table_key, KeyKind, TableKey}; | ||
use crate::owned_iter::OwnedIterator; | ||
use crate::scan::TableScan; | ||
use crate::{PartitionStore, TableKind, TableScanIterationDecision}; | ||
use crate::{RocksDBTransaction, StorageAccess}; | ||
use anyhow::anyhow; | ||
use bytes::Bytes; | ||
use bytestring::ByteString; | ||
use futures::Stream; | ||
use futures_util::stream; | ||
use restate_storage_api::promise_table::{ | ||
OwnedPromiseRow, Promise, PromiseTable, ReadOnlyPromiseTable, | ||
}; | ||
use restate_storage_api::{Result, StorageError}; | ||
use restate_types::identifiers::{PartitionKey, ServiceId, WithPartitionKey}; | ||
use restate_types::storage::StorageCodec; | ||
use std::ops::RangeInclusive; | ||
|
||
define_table_key!( | ||
TableKind::Promise, | ||
KeyKind::Promise, | ||
PromiseKey( | ||
partition_key: PartitionKey, | ||
service_name: ByteString, | ||
service_key: Bytes, | ||
key: ByteString | ||
) | ||
); | ||
|
||
fn create_key(service_id: &ServiceId, key: &ByteString) -> PromiseKey { | ||
PromiseKey::default() | ||
.partition_key(service_id.partition_key()) | ||
.service_name(service_id.service_name.clone()) | ||
.service_key(service_id.key.as_bytes().clone()) | ||
.key(key.clone()) | ||
} | ||
|
||
fn get_promise<S: StorageAccess>( | ||
storage: &mut S, | ||
service_id: &ServiceId, | ||
key: &ByteString, | ||
) -> Result<Option<Promise>> { | ||
storage.get_value(create_key(service_id, key)) | ||
} | ||
|
||
fn all_promise<S: StorageAccess>( | ||
storage: &mut S, | ||
range: RangeInclusive<PartitionKey>, | ||
) -> impl Stream<Item = Result<OwnedPromiseRow>> + Send + '_ { | ||
let iter = storage.iterator_from(TableScan::FullScanPartitionKeyRange::<PromiseKey>(range)); | ||
stream::iter(OwnedIterator::new(iter).map(|(mut k, mut v)| { | ||
let key = PromiseKey::deserialize_from(&mut k)?; | ||
let metadata = StorageCodec::decode::<Promise, _>(&mut v) | ||
.map_err(|err| StorageError::Generic(err.into()))?; | ||
|
||
let (partition_key, service_name, service_key, promise_key) = key.into_inner_ok_or()?; | ||
|
||
Ok(OwnedPromiseRow { | ||
service_id: ServiceId::with_partition_key( | ||
partition_key, | ||
service_name, | ||
ByteString::try_from(service_key) | ||
.map_err(|e| anyhow!("Cannot convert to string {e}"))?, | ||
), | ||
key: promise_key, | ||
metadata, | ||
}) | ||
})) | ||
} | ||
|
||
fn put_promise<S: StorageAccess>( | ||
storage: &mut S, | ||
service_id: &ServiceId, | ||
key: &ByteString, | ||
metadata: Promise, | ||
) { | ||
storage.put_kv(create_key(service_id, key), metadata); | ||
} | ||
|
||
fn delete_all_promises<S: StorageAccess>(storage: &mut S, service_id: &ServiceId) { | ||
let prefix_key = PromiseKey::default() | ||
.partition_key(service_id.partition_key()) | ||
.service_name(service_id.service_name.clone()) | ||
.service_key(service_id.key.as_bytes().clone()); | ||
|
||
let keys = storage.for_each_key_value_in_place( | ||
TableScan::SinglePartitionKeyPrefix(service_id.partition_key(), prefix_key), | ||
|k, _| TableScanIterationDecision::Emit(Ok(Bytes::copy_from_slice(k))), | ||
); | ||
|
||
for k in keys { | ||
storage.delete_cf(TableKind::Promise, &k.unwrap()); | ||
} | ||
} | ||
|
||
impl ReadOnlyPromiseTable for PartitionStore { | ||
async fn get_promise( | ||
&mut self, | ||
service_id: &ServiceId, | ||
key: &ByteString, | ||
) -> Result<Option<Promise>> { | ||
get_promise(self, service_id, key) | ||
} | ||
|
||
fn all_promises( | ||
&mut self, | ||
range: RangeInclusive<PartitionKey>, | ||
) -> impl Stream<Item = Result<OwnedPromiseRow>> + Send { | ||
all_promise(self, range) | ||
} | ||
} | ||
|
||
impl<'a> ReadOnlyPromiseTable for RocksDBTransaction<'a> { | ||
async fn get_promise( | ||
&mut self, | ||
service_id: &ServiceId, | ||
key: &ByteString, | ||
) -> Result<Option<Promise>> { | ||
get_promise(self, service_id, key) | ||
} | ||
|
||
fn all_promises( | ||
&mut self, | ||
range: RangeInclusive<PartitionKey>, | ||
) -> impl Stream<Item = Result<OwnedPromiseRow>> + Send { | ||
all_promise(self, range) | ||
} | ||
} | ||
|
||
impl<'a> PromiseTable for RocksDBTransaction<'a> { | ||
async fn put_promise(&mut self, service_id: &ServiceId, key: &ByteString, metadata: Promise) { | ||
put_promise(self, service_id, key, metadata) | ||
} | ||
|
||
async fn delete_all_promises(&mut self, service_id: &ServiceId) { | ||
delete_all_promises(self, service_id) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH. | ||
// All rights reserved. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the LICENSE file. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0. | ||
|
||
// Unfortunately we need this because of https://github.com/rust-lang/rust-clippy/issues/9801 | ||
#![allow(clippy::borrow_interior_mutable_const)] | ||
#![allow(clippy::declare_interior_mutable_const)] | ||
|
||
use crate::storage_test_environment; | ||
use bytes::Bytes; | ||
use bytestring::ByteString; | ||
use restate_storage_api::promise_table::{ | ||
Promise, PromiseState, PromiseTable, ReadOnlyPromiseTable, | ||
}; | ||
use restate_storage_api::Transaction; | ||
use restate_types::identifiers::{InvocationId, InvocationUuid, JournalEntryId, ServiceId}; | ||
use restate_types::journal::EntryResult; | ||
|
||
const SERVICE_ID_1: ServiceId = ServiceId::from_static(10, "MySvc", "a"); | ||
const SERVICE_ID_2: ServiceId = ServiceId::from_static(11, "MySvc", "b"); | ||
|
||
const PROMISE_KEY_1: ByteString = ByteString::from_static("prom1"); | ||
const PROMISE_KEY_2: ByteString = ByteString::from_static("prom2"); | ||
const PROMISE_KEY_3: ByteString = ByteString::from_static("prom3"); | ||
|
||
const PROMISE_COMPLETED: Promise = Promise { | ||
state: PromiseState::Completed(EntryResult::Success(Bytes::from_static(b"{}"))), | ||
}; | ||
|
||
#[tokio::test] | ||
async fn test_promise_table() { | ||
let mut rocksdb = storage_test_environment().await; | ||
|
||
let promise_not_completed = Promise { | ||
state: PromiseState::NotCompleted(vec![ | ||
JournalEntryId::from_parts( | ||
InvocationId::from_parts( | ||
10, | ||
InvocationUuid::from_parts(1706027034946, 12345678900001), | ||
), | ||
1, | ||
), | ||
JournalEntryId::from_parts( | ||
InvocationId::from_parts( | ||
11, | ||
InvocationUuid::from_parts(1706027034946, 12345678900021), | ||
), | ||
2, | ||
), | ||
]), | ||
}; | ||
|
||
// Fill in some data | ||
let mut txn = rocksdb.transaction(); | ||
txn.put_promise(&SERVICE_ID_1, &PROMISE_KEY_1, PROMISE_COMPLETED) | ||
.await; | ||
txn.put_promise(&SERVICE_ID_1, &PROMISE_KEY_2, promise_not_completed.clone()) | ||
.await; | ||
txn.put_promise(&SERVICE_ID_2, &PROMISE_KEY_3, PROMISE_COMPLETED) | ||
.await; | ||
txn.commit().await.unwrap(); | ||
|
||
// Query | ||
assert_eq!( | ||
rocksdb | ||
.get_promise(&SERVICE_ID_1, &PROMISE_KEY_1,) | ||
.await | ||
.unwrap(), | ||
Some(PROMISE_COMPLETED) | ||
); | ||
assert_eq!( | ||
rocksdb | ||
.get_promise(&SERVICE_ID_1, &PROMISE_KEY_2,) | ||
.await | ||
.unwrap(), | ||
Some(promise_not_completed) | ||
); | ||
assert_eq!( | ||
rocksdb | ||
.get_promise(&SERVICE_ID_2, &PROMISE_KEY_3,) | ||
.await | ||
.unwrap(), | ||
Some(PROMISE_COMPLETED) | ||
); | ||
assert_eq!( | ||
rocksdb | ||
.get_promise(&SERVICE_ID_1, &PROMISE_KEY_3,) | ||
.await | ||
.unwrap(), | ||
None | ||
); | ||
|
||
// Delete and query afterwards | ||
let mut txn = rocksdb.transaction(); | ||
txn.delete_all_promises(&SERVICE_ID_1).await; | ||
txn.commit().await.unwrap(); | ||
|
||
assert_eq!( | ||
rocksdb | ||
.get_promise(&SERVICE_ID_1, &PROMISE_KEY_1,) | ||
.await | ||
.unwrap(), | ||
None | ||
); | ||
assert_eq!( | ||
rocksdb | ||
.get_promise(&SERVICE_ID_1, &PROMISE_KEY_2,) | ||
.await | ||
.unwrap(), | ||
None | ||
); | ||
assert_eq!( | ||
rocksdb | ||
.get_promise(&SERVICE_ID_2, &PROMISE_KEY_3,) | ||
.await | ||
.unwrap(), | ||
Some(PROMISE_COMPLETED) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.