linkrs_query/optimizer/stats/
edge.rs1#[derive(Debug, Clone)]
7pub struct HotVertexInfo {
8 pub vertex_id: i64,
10 pub out_degree: u64,
12 pub in_degree: u64,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SkewnessLevel {
19 None,
21 Mild,
23 Moderate,
25 Severe,
27}
28
29#[derive(Debug, Clone)]
31pub struct EdgeTypeStatistics {
32 pub edge_type: String,
34 pub edge_count: u64,
36 pub allocated_slots: Option<u64>,
40 pub avg_out_degree: f64,
42 pub avg_in_degree: f64,
44 pub max_out_degree: u64,
46 pub max_in_degree: u64,
48 pub unique_src_vertices: u64,
50 pub out_degree_std_dev: f64,
52 pub in_degree_std_dev: f64,
54 pub degree_gini_coefficient: f64,
56 pub hot_vertices: Vec<HotVertexInfo>,
58 pub space: Option<String>,
60 pub schema_version: Option<u64>,
62}
63
64impl EdgeTypeStatistics {
65 pub fn new(edge_type: String) -> Self {
67 Self {
68 edge_type,
69 edge_count: 0,
70 allocated_slots: None,
71 avg_out_degree: 0.0,
72 avg_in_degree: 0.0,
73 max_out_degree: 0,
74 max_in_degree: 0,
75 unique_src_vertices: 0,
76 out_degree_std_dev: 0.0,
77 in_degree_std_dev: 0.0,
78 degree_gini_coefficient: 0.0,
79 hot_vertices: Vec::new(),
80 space: None,
81 schema_version: None,
82 }
83 }
84
85 pub fn with_version(mut self, space: String, schema_version: u64) -> Self {
87 self.space = Some(space);
88 self.schema_version = Some(schema_version);
89 self
90 }
91
92 pub fn estimate_expand_cost(&self, start_nodes: u64) -> f64 {
94 start_nodes as f64 * self.avg_out_degree
95 }
96
97 pub fn hole_rate(&self) -> f64 {
102 match self.allocated_slots {
103 Some(allocated)
104 if allocated > 0 && allocated > self.edge_count && self.edge_count > 0 =>
105 {
106 1.0 - (self.edge_count as f64 / allocated as f64)
107 }
108 _ => 0.0,
109 }
110 }
111
112 pub fn apply_table_snapshot(
115 &mut self,
116 snapshot: Option<&crate::storage::stats_reader::TableCardinalitySnapshot>,
117 ) {
118 let Some(snapshot) = snapshot else {
119 return;
120 };
121 if snapshot.allocated_slots > 0 {
122 self.allocated_slots = Some(snapshot.allocated_slots);
123 }
124 }
125
126 pub fn is_heavily_skewed(&self) -> bool {
128 use crate::optimizer::cost::config::{SKEW_MAX_DEGREE_RATIO, SKEW_SEVERE_GINI};
129 self.degree_gini_coefficient > SKEW_SEVERE_GINI
130 || self.max_out_degree as f64 > self.avg_out_degree * SKEW_MAX_DEGREE_RATIO
131 }
132
133 pub fn skewness_level(&self) -> SkewnessLevel {
135 use crate::optimizer::cost::config::{
136 SKEW_MILD_GINI, SKEW_MODERATE_GINI, SKEW_SEVERE_GINI,
137 };
138 match self.degree_gini_coefficient {
139 g if g > SKEW_SEVERE_GINI => SkewnessLevel::Severe,
140 g if g > SKEW_MODERATE_GINI => SkewnessLevel::Moderate,
141 g if g > SKEW_MILD_GINI => SkewnessLevel::Mild,
142 _ => SkewnessLevel::None,
143 }
144 }
145
146 pub fn skew_penalty(&self) -> f64 {
148 use crate::optimizer::cost::config::{
149 SKEW_MILD_PENALTY, SKEW_MODERATE_PENALTY, SKEW_SEVERE_PENALTY,
150 };
151 match self.skewness_level() {
152 SkewnessLevel::Severe => SKEW_SEVERE_PENALTY,
153 SkewnessLevel::Moderate => SKEW_MODERATE_PENALTY,
154 SkewnessLevel::Mild => SKEW_MILD_PENALTY,
155 SkewnessLevel::None => 1.0,
156 }
157 }
158
159 pub fn skew_row_factor(&self) -> f64 {
161 use crate::optimizer::cost::config::{
162 SKEW_MILD_ROW_FACTOR, SKEW_MODERATE_ROW_FACTOR, SKEW_SEVERE_ROW_FACTOR,
163 };
164 match self.skewness_level() {
165 SkewnessLevel::Severe => SKEW_SEVERE_ROW_FACTOR,
166 SkewnessLevel::Moderate => SKEW_MODERATE_ROW_FACTOR,
167 SkewnessLevel::Mild => SKEW_MILD_ROW_FACTOR,
168 SkewnessLevel::None => 1.0,
169 }
170 }
171
172 pub fn calculate_skewed_expand_cost(&self, start_nodes: u64) -> f64 {
174 let base_cost = self.estimate_expand_cost(start_nodes);
175 base_cost * self.skew_penalty()
176 }
177
178 pub fn has_hot_vertices(&self) -> bool {
180 !self.hot_vertices.is_empty()
181 }
182
183 pub fn hot_vertex_count(&self) -> usize {
185 self.hot_vertices.len()
186 }
187}
188
189impl Default for EdgeTypeStatistics {
190 fn default() -> Self {
191 Self::new(String::new())
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn hole_rate_clamps_unknown_and_inconsistent() {
201 let mut stats = EdgeTypeStatistics::new("knows".to_string());
202 assert_eq!(stats.hole_rate(), 0.0);
203
204 stats.edge_count = 1000;
205 stats.allocated_slots = Some(4000);
206 assert!((stats.hole_rate() - 0.75).abs() < 1e-9);
207
208 stats.allocated_slots = Some(10);
209 assert_eq!(stats.hole_rate(), 0.0);
210 }
211}