Skip to content

Commit d077284

Browse files
author
Ole Krüger
committed
feat(durable-storage): support persisting node without its key value
1 parent 9b1c76e commit d077284

8 files changed

Lines changed: 664 additions & 444 deletions

File tree

durable-storage/src/avl/node.rs

Lines changed: 95 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
use std::borrow::Borrow;
88
use std::cmp::Ordering;
9+
use std::ops::Deref;
910
use std::sync::OnceLock;
1011

1112
use bincode::Decode;
@@ -20,6 +21,9 @@ use octez_riscv_data::foldable::NodeFold;
2021
use octez_riscv_data::hash::Hash;
2122
use octez_riscv_data::hash::HashFold;
2223
use octez_riscv_data::mode::Mode;
24+
use octez_riscv_data::mode::Normal;
25+
use octez_riscv_data::serialisation::deserialise;
26+
use octez_riscv_data::serialisation::serialise;
2327
use perfect_derive::perfect_derive;
2428

2529
use super::resolver::TreeResolver;
@@ -28,6 +32,10 @@ use crate::avl::resolver::AvlResolver;
2832
use crate::errors::Error;
2933
use crate::errors::OperationalError;
3034
use crate::key::Key;
35+
use crate::storage::KeyValueStore;
36+
use crate::storage::Loadable;
37+
use crate::storage::Storable;
38+
use crate::storage::StoreOptions;
3139

3240
/// Metadata of a [`Node`] needed for accesses.
3341
#[derive(Clone, Default, Debug, Encode, Decode)]
@@ -38,22 +46,12 @@ pub(crate) struct Meta {
3846
balance_factor: i64,
3947
}
4048

41-
/// A serialisable representation of [`Meta`].
49+
/// This type is a compact serialised form of a [`Node`] with metadata and child subtree hashes.
4250
#[derive(Encode, Decode)]
43-
pub(super) struct MetaHashRepresentation<K: Borrow<self::Key>> {
44-
key: K,
45-
balance_factor: i64,
46-
}
47-
48-
/// A serialisable representation of [`Node`].
49-
#[derive(Encode, Decode)]
50-
pub(super) struct NodeHashRepresentation<Data, K: Borrow<self::Key>, H: Borrow<self::Hash>> {
51-
meta: MetaHashRepresentation<K>,
52-
data: Data,
53-
// The hash of the left subtree.
54-
left: H,
55-
// The hash of the right subtree.
56-
right: H,
51+
struct StoredNode {
52+
meta: Meta,
53+
left: Hash,
54+
right: Hash,
5755
}
5856

5957
/// A node that supports rebalancing and Merklisation.
@@ -71,25 +69,6 @@ pub struct Node<TreeId, M: Mode> {
7169
hash: OnceLock<Hash>,
7270
}
7371

74-
impl<TreeId, M: BytesMode + AtomMode> From<NodeHashRepresentation<Bytes<M>, Key, Hash>>
75-
for Node<TreeId, M>
76-
where
77-
TreeId: From<Hash>,
78-
{
79-
fn from(node_repr: NodeHashRepresentation<Bytes<M>, Key, Hash>) -> Self {
80-
Node {
81-
meta: Atom::new(Meta {
82-
key: node_repr.meta.key,
83-
balance_factor: node_repr.meta.balance_factor,
84-
}),
85-
data: node_repr.data,
86-
left: TreeId::from(node_repr.left),
87-
right: TreeId::from(node_repr.right),
88-
hash: OnceLock::new(),
89-
}
90-
}
91-
}
92-
9372
impl<TreeId, M: Mode> Node<TreeId, M> {
9473
/// Mark the hash of this node as dirty.
9574
fn invalidate_hash(&mut self) {
@@ -153,31 +132,12 @@ impl<TreeId, M: BytesMode + AtomMode> Node<TreeId, M> {
153132
}
154133
}
155134

156-
/// Converts the [`Node`] to an encoded, serialisable representation,
157-
/// [`NodeHashRepresentation`], potentially re-hashing uncached [`Node`]s.
158-
pub(crate) fn to_encode<'a>(&'a self) -> impl Encode + 'a
159-
where
160-
Bytes<M>: Encode,
161-
TreeId: Foldable<HashFold>,
162-
{
163-
NodeHashRepresentation {
164-
meta: MetaHashRepresentation {
165-
key: &self.meta.key,
166-
balance_factor: self.meta.balance_factor,
167-
},
168-
data: &self.data,
169-
left: Hash::from_foldable(&self.left),
170-
right: Hash::from_foldable(&self.right),
171-
}
172-
}
173-
174135
/// Returns the hash of this node.
175136
///
176137
/// If the hash has been cached, the memo is returned. Otherwise, the hash is calculated and
177138
/// cached.
178139
pub(crate) fn hash(&self) -> &Hash
179140
where
180-
TreeId: Foldable<HashFold>,
181141
Atom<Meta, M>: Foldable<HashFold>,
182142
Bytes<M>: Foldable<HashFold>,
183143
TreeId: Foldable<HashFold>,
@@ -187,7 +147,7 @@ impl<TreeId, M: BytesMode + AtomMode> Node<TreeId, M> {
187147

188148
#[inline]
189149
/// The difference in heights between child branches.
190-
pub(super) fn balance_factor(&self) -> i64 {
150+
pub(crate) fn balance_factor(&self) -> i64 {
191151
self.meta.balance_factor
192152
}
193153

@@ -199,10 +159,16 @@ impl<TreeId, M: BytesMode + AtomMode> Node<TreeId, M> {
199159

200160
#[inline]
201161
/// The [`Key`] used for determining the [`Node`].
202-
pub(super) fn key(&self) -> &Key {
162+
pub(crate) fn key(&self) -> &Key {
203163
&self.meta.key
204164
}
205165

166+
/// Retrieve the value associated with this node.
167+
#[cfg(test)]
168+
pub(crate) fn value(&self) -> &Bytes<M> {
169+
&self.data
170+
}
171+
206172
/// Rebalance the subtree of the [`Node`] so that the difference in height between child
207173
/// branches is in the range of -1..=1.
208174
///
@@ -815,6 +781,80 @@ impl<TreeId, M: BytesMode + AtomMode> Node<TreeId, M> {
815781
}
816782
}
817783

784+
impl<TreeId: Storable> Storable for Node<TreeId, Normal> {
785+
fn store(
786+
&self,
787+
store: &impl KeyValueStore,
788+
options: &StoreOptions,
789+
) -> Result<(), OperationalError> {
790+
// The stored representation is more compact. We don't include the `data` field, as that
791+
// should be written to the KV store separately.
792+
let repr = StoredNode {
793+
meta: self.meta.deref().clone(),
794+
left: Hash::from_foldable(&self.left),
795+
right: Hash::from_foldable(&self.right),
796+
};
797+
798+
let &id = self.hash();
799+
let bytes = serialise(repr)?;
800+
store.blob_set(id, bytes)?;
801+
802+
// Are we in charge of writing the value data to the KV store?
803+
if options.node_data() {
804+
let key: &[u8] = self.meta.key.as_ref();
805+
let value: &[u8] = self.data.borrow();
806+
store.set(key, value)?;
807+
}
808+
809+
if options.deep() {
810+
self.left.store(store, options)?;
811+
self.right.store(store, options)?;
812+
}
813+
814+
Ok(())
815+
}
816+
}
817+
818+
impl<TreeId: Loadable> Loadable for Node<TreeId, Normal> {
819+
fn load(id: Hash, store: &impl KeyValueStore) -> Result<Self, OperationalError> {
820+
let StoredNode { meta, left, right } = {
821+
let bytes =
822+
store
823+
.blob_get(id)
824+
.map_err(|error| OperationalError::CommitDataMissing {
825+
root: id,
826+
source: Box::new(error),
827+
})?;
828+
deserialise(bytes.as_ref())?
829+
};
830+
831+
let meta = Atom::new(meta);
832+
833+
// The stored representation does not include the `data` field, so we need to load it
834+
// separately from the KV store.
835+
let data = {
836+
let bytes = store.get(meta.key.as_ref()).map_err(|error| {
837+
OperationalError::CommitValueMissing {
838+
key: meta.key.clone(),
839+
source: Box::new(error),
840+
}
841+
})?;
842+
Bytes::from(bytes.as_ref())
843+
};
844+
845+
let left = TreeId::load(left, store)?;
846+
let right = TreeId::load(right, store)?;
847+
848+
Ok(Self {
849+
meta,
850+
data,
851+
left,
852+
right,
853+
hash: OnceLock::new(),
854+
})
855+
}
856+
}
857+
818858
impl<F, TreeId, M> Foldable<F> for Node<TreeId, M>
819859
where
820860
F: Fold,

0 commit comments

Comments
 (0)