pub struct MetadataStore { /* private fields */ }Expand description
High-performance metadata store with indexes
Implementations§
Source§impl MetadataStore
impl MetadataStore
Sourcepub fn with_capacity(max_capacity: usize) -> Self
pub fn with_capacity(max_capacity: usize) -> Self
Create store with capacity limit
Sourcepub fn upsert(&self, metadata: NodeMetadata) -> Result<(), MetadataError>
pub fn upsert(&self, metadata: NodeMetadata) -> Result<(), MetadataError>
Insert or update node metadata
The entire (read-old, remove-from-indexes,
add-to-indexes, insert) sequence runs inside
DashMap::entry’s shard write lock, serializing all
concurrent upserts on the same node_id. Splitting this into
a 5-step sequence without an overarching lock — (1) capacity
check, (2) nodes.get(&id), (3) remove_from_indexes(&old),
(4) add_to_indexes(&new), (5) nodes.insert — would let
two concurrent upserts on the same node both observe the
same old at step 2, both remove its index entries at step
3 (second a no-op), and both add to indexes at step 4 into
DIFFERENT buckets if the metadata differed. Whichever
nodes.insert landed second would win, but the loser’s
index entries would never be removed, producing permanent
index drift (queries return the node under the wrong
filter; stats over-count).
Sourcepub fn update_versioned(
&self,
metadata: NodeMetadata,
expected_version: u64,
) -> Result<(), MetadataError>
pub fn update_versioned( &self, metadata: NodeMetadata, expected_version: u64, ) -> Result<(), MetadataError>
Update with version check (optimistic locking)
Sourcepub fn query(&self, query: &MetadataQuery) -> Vec<Arc<NodeMetadata>>
pub fn query(&self, query: &MetadataQuery) -> Vec<Arc<NodeMetadata>>
Query nodes
Sourcepub fn find_nearby(
&self,
location: &LocationInfo,
max_distance_km: f64,
limit: usize,
) -> Vec<(Arc<NodeMetadata>, f64)>
pub fn find_nearby( &self, location: &LocationInfo, max_distance_km: f64, limit: usize, ) -> Vec<(Arc<NodeMetadata>, f64)>
Find nodes near a location
Sourcepub fn find_best_for_routing(&self, limit: usize) -> Vec<Arc<NodeMetadata>>
pub fn find_best_for_routing(&self, limit: usize) -> Vec<Arc<NodeMetadata>>
Find best nodes for routing
Sourcepub fn find_relays(&self) -> Vec<Arc<NodeMetadata>>
pub fn find_relays(&self) -> Vec<Arc<NodeMetadata>>
Find relay nodes
Sourcepub fn stats(&self) -> MetadataStoreStats
pub fn stats(&self) -> MetadataStoreStats
Get statistics
Sourcepub fn clear(&self)
pub fn clear(&self)
Clear all nodes
Drains nodes FIRST and routes every drained metadata
through remove_from_indexes — making the intermediate
state consistent (nodes exist alongside their indexes
throughout the drain). A naive nodes.clear() followed by
six index clear()s in sequence would let a concurrent
upsert landing between any two of those clears observe
nodes.get(&id) → None (skipping remove_from_indexes),
then add_to_indexes (writing into the SAME index maps
clear is about to wipe), then nodes.insert(...) — the
final state would be a node in nodes with NO index
entries, invisible to every indexed query and only
retrievable via the full-scan branch.
With the drain-first ordering, any concurrent upsert
landing during the drain either races BEFORE this function
reads its key (the upsert wins; we drain its entry
afterward) or AFTER (the upsert observes a cleared nodes
and proceeds normally — no index drift, since
remove_from_indexes only touches keys that exist in
nodes). The final clears on the index maps catch any
residual entries the per-key path missed
(defense-in-depth; should be no-ops on the happy path).