pub struct ConcurrentEdgeStore { /* private fields */ }Expand description
A thread-safe edge store using sharded locking.
Distributes edges across multiple shards based on source node ID to reduce lock contention in multi-threaded scenarios.
§Cross-Shard Edge Storage Pattern
Edges that span different shards (source and target in different shards) are stored in BOTH shards:
- Source shard: Full edge with outgoing + label indices (
add_edge) - Target shard: Edge copy with incoming index only (
add_edge_incoming_only)
§Lock Ordering
When acquiring multiple shard locks, always acquire in ascending shard index order to prevent deadlocks.
Implementations§
Source§impl ConcurrentEdgeStore
impl ConcurrentEdgeStore
Sourcepub fn from_edge_store(store: &EdgeStore) -> Self
pub fn from_edge_store(store: &EdgeStore) -> Self
Builds a ConcurrentEdgeStore from a persisted EdgeStore.
Re-distributes edges across shards based on source node ID.
Issue #905: uses add_edges_batch (one
edge_ids write-lock acquisition for the whole set, snapshot
invalidated once) instead of a per-edge add_edge loop (one lock
cycle + one snapshot-dirty flip per edge). The single
build_read_snapshot at the end performs
exactly one O(N+E) CSR build for the whole reconstruction.
Sourcepub fn save_to_file(&self, path: &Path) -> Result<()>
pub fn save_to_file(&self, path: &Path) -> Result<()>
Saves the concurrent edge store to a file.
§Errors
Returns an error if serialization or file I/O fails.
Sourcepub fn load_from_file(path: &Path) -> Result<Self>
pub fn load_from_file(path: &Path) -> Result<Self>
Loads a concurrent edge store from a persisted file.
§Errors
Returns an error if file I/O or deserialization fails.
Source§impl ConcurrentEdgeStore
impl ConcurrentEdgeStore
Sourcepub fn get_outgoing(&self, node_id: u64) -> Vec<GraphEdge>
pub fn get_outgoing(&self, node_id: u64) -> Vec<GraphEdge>
Gets all outgoing edges from a node (thread-safe).
Sourcepub fn get_incoming(&self, node_id: u64) -> Vec<GraphEdge>
pub fn get_incoming(&self, node_id: u64) -> Vec<GraphEdge>
Gets all incoming edges to a node (thread-safe).
Sourcepub fn get_neighbors(&self, node_id: u64) -> Vec<u64>
pub fn get_neighbors(&self, node_id: u64) -> Vec<u64>
Gets neighbors (target nodes) of a given node.
When a CSR read snapshot is available (see
build_read_snapshot()), this returns
a copy from contiguous memory without resolving individual edges.
Falls back to per-shard edge lookup otherwise.
Sourcepub fn with_neighbors<F, R>(&self, node_id: u64, f: F) -> R
pub fn with_neighbors<F, R>(&self, node_id: u64, f: F) -> R
Invokes f with a borrowed slice of outgoing neighbor IDs.
When the CSR snapshot is available, f receives a zero-copy &[u64]
from contiguous memory. Otherwise, a temporary Vec<u64> is built
from per-shard edge lookup.
Prefer this over get_neighbors in tight
loops (BFS frontiers) where the caller processes IDs inline.
Sourcepub fn get_outgoing_by_label(&self, node_id: u64, label: &str) -> Vec<GraphEdge>
pub fn get_outgoing_by_label(&self, node_id: u64, label: &str) -> Vec<GraphEdge>
Gets outgoing edges filtered by label (thread-safe).
§Performance Note
This method delegates to the underlying EdgeStore::get_outgoing_by_label
which uses the composite index (source_id, label) -> edge_ids for O(1) lookup
when available (EPIC-019 US-003). Falls back to filtering if index not populated.
Sourcepub fn get_incoming_by_label(&self, node_id: u64, label: &str) -> Vec<GraphEdge>
pub fn get_incoming_by_label(&self, node_id: u64, label: &str) -> Vec<GraphEdge>
Gets incoming edges filtered by label (thread-safe).
Sourcepub fn get_edges_by_label(&self, label: &str) -> Vec<GraphEdge>
pub fn get_edges_by_label(&self, label: &str) -> Vec<GraphEdge>
Gets all edges with a specific label across all shards.
§Performance Warning
This method iterates through ALL shards and aggregates results.
For large graphs with many shards, this can be expensive.
Consider using get_outgoing_by_label(node_id, label) if you know
the source node, which is O(k) instead of O(shards × edges_per_label).
Sourcepub fn max_edge_id(&self) -> Option<u64>
pub fn max_edge_id(&self) -> Option<u64>
Checks if an edge with the given ID exists. Returns the highest edge id in the store, if any.
O(edges) over the id registry — no edge cloning.
Sourcepub fn contains_edge(&self, edge_id: u64) -> bool
pub fn contains_edge(&self, edge_id: u64) -> bool
Returns true when an edge with edge_id exists.
Sourcepub fn get_edge(&self, edge_id: u64) -> Option<GraphEdge>
pub fn get_edge(&self, edge_id: u64) -> Option<GraphEdge>
Gets an edge by ID using optimized source shard lookup.
Returns None if the edge doesn’t exist.
Sourcepub fn traverse_bfs(&self, start: u64, max_depth: u32) -> Vec<u64>
pub fn traverse_bfs(&self, start: u64, max_depth: u32) -> Vec<u64>
Traverses the graph using BFS from a starting node.
Returns all nodes reachable within max_depth hops.
When a CSR read snapshot is available, neighbor lookups are zero-copy slices from contiguous memory. Otherwise uses Read-Copy-Drop pattern with per-shard locks.
Sourcepub fn edge_count(&self) -> usize
pub fn edge_count(&self) -> usize
Returns the total edge count across all shards.
Uses outgoing edge count to avoid double-counting edges that span shards.
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Returns len() — alias for edge_count() for API parity with EdgeStore.
Sourcepub fn label_count(&self) -> usize
pub fn label_count(&self) -> usize
Returns the number of distinct edge labels in the graph.
Reads from the CSR snapshot’s interned label table, triggering a lazy rebuild if dirty. Returns 0 when the store has no edges.
Sourcepub fn all_edges(&self) -> Vec<GraphEdge>
pub fn all_edges(&self) -> Vec<GraphEdge>
Returns all edges across all shards (cloned).
Uses the edge_ids registry to look up each edge exactly once in its
source shard, avoiding double-counting for cross-shard edges.
§Performance Warning
Iterates all edges and clones each one. For large graphs, prefer
targeted queries (get_outgoing, get_edges_by_label).
Sourcepub fn outgoing_degree(&self, node_id: u64) -> usize
pub fn outgoing_degree(&self, node_id: u64) -> usize
Returns the out-degree of a node without materializing edge vectors.
Uses CSR snapshot when available for O(1) lookup without shard locking.
Sourcepub fn incoming_degree(&self, node_id: u64) -> usize
pub fn incoming_degree(&self, node_id: u64) -> usize
Returns the in-degree of a node without materializing edge vectors.
Sourcepub fn get_csr_snapshot(&self) -> Guard<Arc<CsrSnapshot>>
pub fn get_csr_snapshot(&self) -> Guard<Arc<CsrSnapshot>>
Returns the current CSR snapshot (lock-free read).
The returned Guard dereferences to Arc<CsrSnapshot> and keeps
the snapshot alive for the duration of the borrow. No locks are
acquired — this is a single atomic load.
If the snapshot is dirty (mutation occurred since last rebuild), triggers a lazy rebuild before returning.
Sourcepub fn traverse_bfs_csr(
&self,
source: u64,
config: &TraversalConfig,
) -> Vec<TraversalResult>
pub fn traverse_bfs_csr( &self, source: u64, config: &TraversalConfig, ) -> Vec<TraversalResult>
BFS traversal on the CSR snapshot (lock-free, zero-copy).
Loads the current snapshot atomically and delegates to
bfs_traverse_csr for the actual traversal.
Triggers a lazy CSR rebuild if dirty.
Sourcepub fn traverse_bfs_filtered<P: EdgePredicate>(
&self,
source: u64,
config: &TraversalConfig,
predicate: &P,
) -> Vec<TraversalResult>
pub fn traverse_bfs_filtered<P: EdgePredicate>( &self, source: u64, config: &TraversalConfig, predicate: &P, ) -> Vec<TraversalResult>
BFS traversal with predicate pushdown on the CSR snapshot.
Loads the current snapshot atomically and delegates to
bfs_traverse_csr_filtered which applies the predicate at the
CSR level, avoiding materialisation of non-matching edges.
Triggers a lazy CSR rebuild if dirty.
Source§impl ConcurrentEdgeStore
impl ConcurrentEdgeStore
Sourcepub fn build_read_snapshot(&self)
pub fn build_read_snapshot(&self)
Builds a CSR-like read snapshot from current shard state.
The snapshot stores only outgoing neighbor target node IDs per source
node in contiguous memory, enabling with_neighbors()
to provide zero-copy &[u64] access without shard locking.
§Limitation — target IDs only
The snapshot does not store edge IDs, labels, or properties.
It is optimized for BFS neighbor expansion where only connectivity
matters. To retrieve full edge metadata (edge ID, label, properties),
use get_outgoing() which reads from the
authoritative shard data.
Call this after bulk inserts, after flush(), or after loading
from disk. The snapshot is automatically invalidated on any write.
Sourcepub fn has_read_snapshot(&self) -> bool
pub fn has_read_snapshot(&self) -> bool
Returns true if a CSR read snapshot is currently available.
Source§impl ConcurrentEdgeStore
impl ConcurrentEdgeStore
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a new concurrent edge store with the default number of shards.
Uses DEFAULT_NUM_SHARDS (compile-time constant > 0), so this
constructor cannot fail in practice.
Sourcepub fn with_shards(num_shards: usize) -> Result<Self>
pub fn with_shards(num_shards: usize) -> Result<Self>
Creates a new concurrent edge store with a specific number of shards.
§Errors
Returns Error::Config if num_shards is 0 (would cause
division-by-zero in shard_index).
Sourcepub fn metrics(&self) -> &GraphMetrics
pub fn metrics(&self) -> &GraphMetrics
Returns the operational metrics for this edge store.
Counters/histograms cover edge inserts, deletes, and traversals.
Sourcepub fn with_estimated_edges(estimated_edges: usize) -> Self
pub fn with_estimated_edges(estimated_edges: usize) -> Self
Creates a concurrent edge store with optimal shard count for estimated edge count.
FLAG-6: Uses integer bit manipulation for ceiling log2.
Sourcepub fn add_edge(&self, edge: GraphEdge) -> Result<()>
pub fn add_edge(&self, edge: GraphEdge) -> Result<()>
Adds an edge to the store (thread-safe).
Edges are stored in BOTH source and target shards:
- Source shard: for outgoing index lookups
- Target shard: for incoming index lookups
When source and target are in different shards, locks are acquired in ascending shard index order to prevent deadlocks.
§Errors
Returns Error::EdgeExists if an edge with the same ID already exists.
Sourcepub fn add_edges_batch(&self, edges: Vec<GraphEdge>) -> usize
pub fn add_edges_batch(&self, edges: Vec<GraphEdge>) -> usize
Adds multiple edges in batch with a single lock acquisition cycle.
Acquires the edge_ids write lock once for the entire batch,
inserts all edges into their respective shards, then invalidates
the CSR snapshot once at the end. This is 10-50x faster than
calling add_edge in a loop for large batches.
Edges that already exist (duplicate IDs) are silently skipped.
§Returns
Number of edges successfully added.
Sourcepub fn remove_edge(&self, edge_id: u64) -> bool
pub fn remove_edge(&self, edge_id: u64) -> bool
Removes an edge by ID using optimized 2-shard lookup.
§Concurrency Safety
Lock ordering: edge_ids FIRST, then shards in ascending order.
Sourcepub fn remove_node_edges(&self, node_id: u64)
pub fn remove_node_edges(&self, node_id: u64)
Removes all edges connected to a node (cascade delete, thread-safe).
§Concurrency Safety
Lock ordering: edge_ids FIRST, then shards in ascending order.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for ConcurrentEdgeStore
impl !RefUnwindSafe for ConcurrentEdgeStore
impl Send for ConcurrentEdgeStore
impl Sync for ConcurrentEdgeStore
impl Unpin for ConcurrentEdgeStore
impl UnsafeUnpin for ConcurrentEdgeStore
impl UnwindSafe for ConcurrentEdgeStore
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);