linkrs_query/optimizer/engine/
feedback.rs1use super::OptimizerEngine;
2use crate::optimizer::stats::feedback::query::OperatorFeedback;
3impl OptimizerEngine {
4 pub fn maybe_apply_feedback(&self) {
20 if !self.enable_feedback {
21 return;
22 }
23 let history = self.feedback_history();
24 let mut applied = 0usize;
25 let mut skipped_truncated = 0usize;
26 let mut skipped_unpaired = 0usize;
27 let mut decision_runs = 0usize;
28 for fingerprint in history.get_all_fingerprints() {
29 let Some(avg_error) = history.get_avg_row_error(&fingerprint) else {
30 continue;
31 };
32 if !self.feedback_trigger.should_trigger(avg_error) {
33 continue;
34 }
35 let watermark = self
36 .feedback_applied_seq
37 .lock()
38 .get(&fingerprint)
39 .copied()
40 .unwrap_or(0);
41 let mut max_seq = watermark;
42 let mut folded_runs = 0usize;
43 for feedback in history.get_feedback_for_query(&fingerprint) {
44 if feedback.sequence <= watermark {
45 continue;
46 }
47 max_seq = max_seq.max(feedback.sequence);
48 if feedback.apply_rows > 0 {
49 self.decision_feedback.record_apply_run(
50 space_of(&feedback),
51 feedback.apply_rows,
52 feedback.apply_time_us,
53 );
54 decision_runs += 1;
55 }
56 if feedback.join_rows > 0 {
57 self.decision_feedback.record_join_run(
58 space_of(&feedback),
59 feedback.join_rows,
60 feedback.join_time_us,
61 );
62 decision_runs += 1;
63 }
64 for op in &feedback.operator_feedbacks {
65 if !op.exhausted {
66 skipped_truncated += 1;
69 continue;
70 }
71 if let Some(key) = &op.condition_key {
72 match self.measured_selectivity_ratio(key, op) {
73 Some((ratio, input_rows)) => {
74 if self
75 .selectivity_feedback
76 .update_measured_ratio(key, ratio, input_rows)
77 {
78 applied += 1;
79 }
80 }
81 None => {
82 skipped_unpaired += 1;
83 }
84 }
85 } else if let Some(key) = &op.shape_key {
86 let ratio = op.actual_rows as f64 / op.estimated_rows.max(1) as f64;
87 if self.cardinality_feedback.update_feedback_ratio(key, ratio) {
88 applied += 1;
89 }
90 }
91 }
92 self.feedback_trigger.record_feedback();
93 folded_runs += 1;
94 }
95 if folded_runs > 0 {
96 self.feedback_applied_seq
97 .lock()
98 .insert(fingerprint, max_seq);
99 self.feedback_trigger.mark_updated();
100 }
101 }
102 if applied > 0 {
103 log::debug!(
104 "Feedback loop: applied {} selectivity/cardinality corrections from {} fingerprints ({} truncated, {} unpaired)",
105 applied,
106 history.query_count(),
107 skipped_truncated,
108 skipped_unpaired,
109 );
110 }
111 if decision_runs > 0 {
112 log::debug!(
113 "Feedback loop: folded {} Apply/Join decision runs from {} fingerprints",
114 decision_runs,
115 history.query_count()
116 );
117 }
118 }
119
120 fn measured_selectivity_ratio(&self, key: &str, op: &OperatorFeedback) -> Option<(f64, u64)> {
128 let baseline = self
129 .selectivity_feedback
130 .get_feedback(key)?
131 .estimated_selectivity();
132 if baseline <= 0.0 {
133 return None;
134 }
135 let actual_in = op.actual_input_rows?;
136 if actual_in == 0 {
137 return None;
138 }
139 let observed = op.actual_rows as f64 / actual_in as f64;
140 Some((observed / baseline, actual_in))
141 }
142
143 pub fn invalidate_space_feedback(&self, space: Option<&str>) {
149 let removed = match space {
150 Some(space) => {
151 let removed = self
152 .selectivity_feedback
153 .remove_feedback_by_space(&format!("{}:", space));
154 removed
155 + self
156 .cardinality_feedback
157 .remove_feedback_by_space(&format!("{}:", space))
158 }
159 None => {
160 let keys = self.selectivity_feedback.get_all_keys();
161 for key in &keys {
162 self.selectivity_feedback.remove_feedback(key);
163 }
164 let cardinality_keys = self.cardinality_feedback.get_all_keys();
165 for key in &cardinality_keys {
166 self.cardinality_feedback.remove_feedback(key);
167 }
168 keys.len() + cardinality_keys.len()
169 }
170 };
171 self.decision_feedback.invalidate_space(space);
172 if removed > 0 {
173 log::info!(
174 "Invalidated {} feedback corrections for space {:?}",
175 removed,
176 space
177 );
178 }
179 }
180}
181
182fn space_of(feedback: &crate::optimizer::stats::feedback::query::QueryExecutionFeedback) -> &str {
184 feedback.space.as_deref().unwrap_or("")
185}