-
Notifications
You must be signed in to change notification settings - Fork 0
perf: Merkle traits focus on hash, not full contents #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This is a first step towards implement more aggressive caching of Lockable values, which will reduce overall memory usage for most Kolme applications. This commit does _not_ compile or pass tests, further commits will continue refining this by adding the actual cache implementation to MerkleLockable, and then fixing compilation in the rest of the repo.
Deploying kolme with
|
Latest commit: |
f911e61
|
Status: | ✅ Deploy successful! |
Preview URL: | https://2e1cae1d.kolme.pages.dev |
Branch Preview URL: | https://merkle-map-value-caching.kolme.pages.dev |
This code took me a while to get working, and I finished it up on a plane, so I don't know how reliable it is. In theory it should reduce memory needs for Kolme apps by pushing more sharing of merkle map data. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the Merkle traits to focus on hash-based caching instead of full content caching, as a first step toward implementing more aggressive caching of Lockable values to reduce memory usage. The changes introduce a new hash-based caching system for MerkleLockable values while maintaining backward compatibility.
Key changes:
- Replace
get_merkle_contents()
andset_merkle_contents()
methods with hash-based equivalents - Introduce a new global cache system for MerkleLockable values using TypeId and hash as keys
- Update API functions to return and work with hashes instead of full MerkleContents objects
Reviewed Changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 6 comments.
Show a summary per file
File | Description |
---|---|
packages/merkle-map/src/traits.rs | Replace content-based trait methods with hash-based equivalents |
packages/merkle-map/src/impls/lockable/locked.rs | New cache implementation with TypeId-based key system |
packages/merkle-map/src/impls/lockable.rs | Update MerkleLockable to use new hash-based caching |
packages/merkle-map/src/api.rs | Modify save/load functions to work with hashes instead of contents |
packages/merkle-map/tests/*.rs | Update tests to work with hash return values |
packages/kolme-store/src/*.rs | Update trait signatures and implementations |
value: LockValue, | ||
} | ||
|
||
/// The cache itself |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation for the global cache. This static cache will persist for the entire application lifetime and could grow unbounded. Consider adding documentation about cache eviction policies and memory management.
/// The cache itself | |
/// The global cache for locked values. | |
/// | |
/// This static cache persists for the entire application lifetime. | |
/// Entries are evicted when their associated [`CleanupLockKey`] is dropped, | |
/// which happens when there are no more strong references to the key. | |
/// As a result, memory usage is tied to the lifetime of these keys and their values. | |
/// There is no explicit upper bound on cache size; if keys are never dropped, | |
/// the cache may grow unbounded. Care should be taken to avoid memory leaks | |
/// by ensuring keys are released when no longer needed. |
Copilot uses AI. Check for mistakes.
} | ||
|
||
impl<T: Send + Sync + 'static> Locked<T> { | ||
pub(super) fn new(lock_key: LockKey, inner: Arc<T>) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition in the double-checked locking pattern. After acquiring the write lock, the code should check again if the value exists before inserting to avoid duplicate insertions.
Copilot uses AI. Check for mistakes.
let once_locked = OnceLock::new(); | ||
assert!(once_locked.set(locked).is_ok()); | ||
Some(Self { | ||
locked: Arc::new(once_locked), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The variable name 'once_locked' is confusing since it's not locked yet. Consider renaming to 'once_cell' or 'lock_cell' to better reflect its purpose as a container.
locked: Arc::new(once_locked), | |
let once_cell = OnceLock::new(); | |
assert!(once_cell.set(locked).is_ok()); | |
Some(Self { | |
locked: Arc::new(once_cell), |
Copilot uses AI. Check for mistakes.
let locked = Locked::<T>::get(Self::lock_key_for(hash))?; | ||
let inner = locked.value().clone(); | ||
let once_locked = OnceLock::new(); | ||
assert!(once_locked.set(locked).is_ok()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using assert! for error handling in production code is problematic. If the OnceLock is already set, this will panic. Consider using expect() with a descriptive message or proper error handling.
assert!(once_locked.set(locked).is_ok()); | |
once_locked.set(locked).expect("OnceLock was already set when loading MerkleLockable by hash"); |
Copilot uses AI. Check for mistakes.
if let Some(leaf) = MerkleDeserializeRaw::load_merkle_by_hash(hash) { | ||
Some(Node::Leaf(leaf)) | ||
} else { | ||
MerkleDeserializeRaw::load_merkle_by_hash(hash).map(Node::Tree) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The type inference for the first branch is unclear. Consider explicitly specifying the type or adding a comment to clarify that this attempts to load as a leaf first, then as a tree.
MerkleDeserializeRaw::load_merkle_by_hash(hash).map(Node::Tree) | |
// Try to load as a leaf first; if that fails, try to load as a tree. | |
if let Some(leaf) = <MerkleLockable<LeafContents<K, V>> as MerkleDeserializeRaw>::load_merkle_by_hash(hash) { | |
Some(Node::Leaf(leaf)) | |
} else { | |
<MerkleLockable<TreeContents<K, V>> as MerkleDeserializeRaw>::load_merkle_by_hash(hash).map(Node::Tree) |
Copilot uses AI. Check for mistakes.
let hash = save(&mut store, &m).await.unwrap(); | ||
let contents = merkle_map::api::load_merkle_contents(&mut store, hash) | ||
.await | ||
.unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test modification introduces a dependency on an internal API function 'load_merkle_contents'. Consider using public APIs or extracting this functionality into a test helper to avoid tight coupling with internals.
.unwrap(); | |
let contents = load_merkle_contents_helper(&mut store, hash).await.unwrap(); | |
Copilot uses AI. Check for mistakes.
This is a first step towards implement more aggressive caching of Lockable values, which will reduce overall memory usage for most Kolme applications. This commit does not compile or pass tests, further commits will continue refining this by adding the actual cache implementation to MerkleLockable, and then fixing compilation in the rest of the repo.
I'll continue pushing more commits to this PR to implement remaining functionality.