@feltdb/core 0.4.12 → 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/dist/cli/commands.js +42 -25
  2. package/dist/cli/index.js +1 -1
  3. package/dist/create/cli.js +39 -4
  4. package/dist/create/create.js +30 -18
  5. package/dist/create/docker-compose-generator.js +68 -11
  6. package/dist/create/package-versions.js +1 -1
  7. package/dist/create/server-source/Cargo.lock +2238 -0
  8. package/dist/create/server-source/Cargo.toml +7 -0
  9. package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
  10. package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
  11. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
  12. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
  13. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
  14. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
  15. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
  19. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
  20. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
  21. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
  22. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
  23. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
  24. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
  25. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
  26. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
  27. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
  28. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
  29. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
  30. package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
  31. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
  32. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
  33. package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
  34. package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
  35. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
  36. package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
  37. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
  38. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
  39. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
  40. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
  41. package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
  42. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
  43. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
  44. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
  45. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
  46. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
  47. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
  48. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
  49. package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
  50. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
  51. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
  52. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
  53. package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
  54. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
  55. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
  56. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
  57. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
  58. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
  59. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
  60. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
  61. package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
  62. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
  63. package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
  64. package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
  65. package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
  66. package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
  67. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
  68. package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
  69. package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
  70. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
  71. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
  72. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
  73. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
  74. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
  75. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
  76. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
  77. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
  78. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
  79. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
  80. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
  81. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
  82. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
  83. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
  84. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
  85. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
  86. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
  87. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
  88. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
  89. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
  90. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
  91. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
  92. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
  93. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
  94. package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
  95. package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
  96. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
  97. package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
  98. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
  99. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
  100. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
  101. package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
  102. package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
  103. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
  104. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
  105. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
  106. package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
  107. package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
  108. package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
  109. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
  110. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
  111. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
  112. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
  113. package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
  114. package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
  115. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
  116. package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
  117. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
  118. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
  119. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
  120. package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
  121. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
  122. package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
  123. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
  124. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
  125. package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
  126. package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
  127. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
  128. package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
  129. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
  130. package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
  131. package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
  132. package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
  133. package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
  134. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
  135. package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
  136. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
  137. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
  138. package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
  139. package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
  140. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
  141. package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
  142. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
  143. package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
  144. package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
  145. package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
  146. package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
  147. package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
  149. package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
  150. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
  151. package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
  152. package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
  153. package/dist/flowspec.d.ts +2 -0
  154. package/dist/flowspec.d.ts.map +1 -1
  155. package/dist/flowspec.js +28 -4
  156. package/dist/state-contract.d.ts +7 -1
  157. package/dist/state-contract.d.ts.map +1 -1
  158. package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
  159. package/dist/studio/components/ConflictExplorer.d.ts.map +1 -1
  160. package/dist/studio/components/index.js +1 -1
  161. package/dist/studio/{components-Q0Nx6gLE.js → components-9kDSWiGL.js} +163 -47
  162. package/dist/studio/index.js +31 -28
  163. package/dist/studio-app/assets/{index-BHOmLZF-.js → index-DVdvmf69.js} +9 -9
  164. package/dist/studio-app/index.html +1 -1
  165. package/package.json +1 -1
@@ -0,0 +1,475 @@
1
+ use serde::{Deserialize, Serialize};
2
+ use std::collections::HashMap;
3
+
4
+ #[derive(Debug, Clone, Serialize, Deserialize)]
5
+ pub struct QueryTrace {
6
+ pub timestamp: u64,
7
+ pub fields: Vec<String>,
8
+ pub operator: String,
9
+ pub index_used: Option<String>,
10
+ pub execution_time_ms: f64,
11
+ pub rows_scanned: u64,
12
+ pub result_count: u64,
13
+ }
14
+
15
+ #[derive(Debug, Clone, Serialize, Deserialize)]
16
+ pub struct IndexMetrics {
17
+ pub name: String,
18
+ pub reads: u64,
19
+ pub writes: u64,
20
+ pub hit_rate: f64,
21
+ pub avg_query_time_ms: f64,
22
+ pub avg_write_time_ms: f64,
23
+ pub entries: usize,
24
+ pub last_used_at: u64,
25
+ pub created_at: u64,
26
+ }
27
+
28
+ #[derive(Debug, Clone, Serialize, Deserialize)]
29
+ pub struct TuningRecommendation {
30
+ pub rec_type: String, // "create_index", "drop_index", "optimize_index"
31
+ pub priority: String, // "high", "medium", "low"
32
+ pub index_name: Option<String>,
33
+ pub reason: String,
34
+ pub expected_speedup: Option<f64>,
35
+ pub estimated_roi: Option<f64>,
36
+ }
37
+
38
+ #[derive(Debug, Clone, Serialize, Deserialize)]
39
+ pub struct PerformanceReport {
40
+ pub collection_name: String,
41
+ pub period: String, // "hourly", "daily", "weekly"
42
+ pub total_queries: usize,
43
+ pub indexed_queries: usize,
44
+ pub full_scans: usize,
45
+ pub avg_query_time_ms: f64,
46
+ pub slowest_query: Option<QueryTrace>,
47
+ pub index_metrics: Vec<IndexMetrics>,
48
+ pub recommendations: Vec<TuningRecommendation>,
49
+ pub timestamp: u64,
50
+ }
51
+
52
+ pub struct IndexAnalytics {
53
+ metrics: HashMap<String, IndexMetrics>,
54
+ query_traces: Vec<QueryTrace>,
55
+ field_query_count: HashMap<String, u64>,
56
+ max_traces: usize,
57
+ }
58
+
59
+ impl IndexAnalytics {
60
+ pub fn new() -> Self {
61
+ IndexAnalytics {
62
+ metrics: HashMap::new(),
63
+ query_traces: Vec::new(),
64
+ field_query_count: HashMap::new(),
65
+ max_traces: 10000,
66
+ }
67
+ }
68
+
69
+ pub fn record_index_usage(&mut self, index_name: &str, operation: &str, time_ms: f64) {
70
+ let metric = self
71
+ .metrics
72
+ .entry(index_name.to_string())
73
+ .or_insert_with(|| IndexMetrics {
74
+ name: index_name.to_string(),
75
+ reads: 0,
76
+ writes: 0,
77
+ hit_rate: 0.0,
78
+ avg_query_time_ms: 0.0,
79
+ avg_write_time_ms: 0.0,
80
+ entries: 0,
81
+ last_used_at: current_time_ms(),
82
+ created_at: current_time_ms(),
83
+ });
84
+
85
+ if operation == "read" {
86
+ let old_avg = metric.avg_query_time_ms;
87
+ metric.reads += 1;
88
+ metric.avg_query_time_ms = (old_avg * (metric.reads - 1) as f64 + time_ms) / metric.reads as f64;
89
+ } else {
90
+ let old_avg = metric.avg_write_time_ms;
91
+ metric.writes += 1;
92
+ metric.avg_write_time_ms = (old_avg * (metric.writes - 1) as f64 + time_ms) / metric.writes as f64;
93
+ }
94
+
95
+ metric.last_used_at = current_time_ms();
96
+ }
97
+
98
+ pub fn record_query(
99
+ &mut self,
100
+ fields: Vec<String>,
101
+ operator: String,
102
+ execution_time_ms: f64,
103
+ rows_scanned: u64,
104
+ result_count: u64,
105
+ index_used: Option<String>,
106
+ ) {
107
+ let trace = QueryTrace {
108
+ timestamp: current_time_ms(),
109
+ fields: fields.clone(),
110
+ operator,
111
+ index_used: index_used.clone(),
112
+ execution_time_ms,
113
+ rows_scanned,
114
+ result_count,
115
+ };
116
+
117
+ self.query_traces.push(trace);
118
+ if self.query_traces.len() > self.max_traces {
119
+ self.query_traces.remove(0);
120
+ }
121
+
122
+ // Track field access patterns
123
+ for field in &fields {
124
+ *self.field_query_count.entry(field.clone()).or_insert(0) += 1;
125
+ }
126
+
127
+ // Update hit rate for used index
128
+ if let Some(ref index_name) = index_used {
129
+ if let Some(metric) = self.metrics.get_mut(index_name) {
130
+ metric.reads += 1;
131
+ }
132
+ }
133
+ }
134
+
135
+ pub fn get_index_metrics(&self, index_name: &str) -> Option<IndexMetrics> {
136
+ self.metrics.get(index_name).cloned()
137
+ }
138
+
139
+ pub fn get_all_metrics(&self) -> Vec<IndexMetrics> {
140
+ self.metrics.values().cloned().collect()
141
+ }
142
+
143
+ pub fn get_slowest_queries(&self, limit: usize) -> Vec<QueryTrace> {
144
+ let mut traces = self.query_traces.clone();
145
+ traces.sort_by(|a, b| b.execution_time_ms.partial_cmp(&a.execution_time_ms).unwrap_or(std::cmp::Ordering::Equal));
146
+ traces.truncate(limit);
147
+ traces
148
+ }
149
+
150
+ pub fn get_frequent_fields(&self, limit: usize) -> Vec<(String, u64)> {
151
+ let mut fields: Vec<_> = self.field_query_count.iter().map(|(k, v)| (k.clone(), *v)).collect();
152
+ fields.sort_by(|a, b| b.1.cmp(&a.1));
153
+ fields.truncate(limit);
154
+ fields
155
+ }
156
+
157
+ fn calculate_hit_rate(&self, index_name: &str) -> f64 {
158
+ let metric = match self.metrics.get(index_name) {
159
+ Some(m) => m,
160
+ None => return 0.0,
161
+ };
162
+
163
+ if metric.reads == 0 {
164
+ return 0.0;
165
+ }
166
+
167
+ if self.query_traces.is_empty() {
168
+ return 0.0;
169
+ }
170
+
171
+ let queries_using_index = self
172
+ .query_traces
173
+ .iter()
174
+ .filter(|q| q.index_used.as_ref().map_or(false, |u| u == index_name))
175
+ .count();
176
+
177
+ let total_queries = self.query_traces.len();
178
+ if total_queries == 0 {
179
+ 0.0
180
+ } else {
181
+ queries_using_index as f64 / total_queries as f64
182
+ }
183
+ }
184
+
185
+ pub fn generate_report(
186
+ &self,
187
+ collection_name: &str,
188
+ period: &str,
189
+ ) -> PerformanceReport {
190
+ let now = current_time_ms();
191
+ let cutoff = match period {
192
+ "hourly" => now - 3_600_000,
193
+ "weekly" => now - 604_800_000,
194
+ _ => now - 86_400_000, // daily
195
+ };
196
+
197
+ let recent_traces: Vec<_> = self
198
+ .query_traces
199
+ .iter()
200
+ .filter(|t| t.timestamp > cutoff)
201
+ .cloned()
202
+ .collect();
203
+
204
+ let indexed_queries = recent_traces.iter().filter(|t| t.index_used.is_some()).count();
205
+ let full_scans = recent_traces.len() - indexed_queries;
206
+
207
+ let avg_query_time = if recent_traces.is_empty() {
208
+ 0.0
209
+ } else {
210
+ recent_traces.iter().map(|t| t.execution_time_ms).sum::<f64>() / recent_traces.len() as f64
211
+ };
212
+
213
+ let slowest_query = if recent_traces.is_empty() {
214
+ None
215
+ } else {
216
+ let mut traces = recent_traces.clone();
217
+ traces.sort_by(|a, b| b.execution_time_ms.partial_cmp(&a.execution_time_ms).unwrap_or(std::cmp::Ordering::Equal));
218
+ traces.first().cloned()
219
+ };
220
+
221
+ let mut metrics = self.get_all_metrics();
222
+ for metric in &mut metrics {
223
+ metric.hit_rate = self.calculate_hit_rate(&metric.name);
224
+ }
225
+
226
+ let recommendations = self.generate_recommendations(&recent_traces, &metrics);
227
+
228
+ PerformanceReport {
229
+ collection_name: collection_name.to_string(),
230
+ period: period.to_string(),
231
+ total_queries: recent_traces.len(),
232
+ indexed_queries,
233
+ full_scans,
234
+ avg_query_time_ms: avg_query_time,
235
+ slowest_query,
236
+ index_metrics: metrics,
237
+ recommendations,
238
+ timestamp: now,
239
+ }
240
+ }
241
+
242
+ fn generate_recommendations(
243
+ &self,
244
+ traces: &[QueryTrace],
245
+ metrics: &[IndexMetrics],
246
+ ) -> Vec<TuningRecommendation> {
247
+ let mut recommendations = Vec::new();
248
+
249
+ if traces.is_empty() {
250
+ return recommendations;
251
+ }
252
+
253
+ // Recommend indexes for frequently scanned fields
254
+ let mut field_scans: HashMap<String, u64> = HashMap::new();
255
+ for trace in traces {
256
+ if trace.index_used.is_none() && trace.rows_scanned > 1000 {
257
+ for field in &trace.fields {
258
+ *field_scans.entry(field.clone()).or_insert(0) += trace.rows_scanned;
259
+ }
260
+ }
261
+ }
262
+
263
+ for (field, total_rows_scanned) in field_scans {
264
+ let avg_scanned_per_query = total_rows_scanned as f64 / traces.len() as f64;
265
+ if avg_scanned_per_query > 1000.0 {
266
+ recommendations.push(TuningRecommendation {
267
+ rec_type: "create_index".to_string(),
268
+ priority: "high".to_string(),
269
+ index_name: None,
270
+ reason: format!("Field '{}' scanned {:.0} rows per query", field, avg_scanned_per_query),
271
+ expected_speedup: Some(avg_scanned_per_query / 10.0),
272
+ estimated_roi: None,
273
+ });
274
+ }
275
+ }
276
+
277
+ // Recommend dropping unused indexes
278
+ let unused_threshold = 0.01;
279
+ let now = current_time_ms();
280
+ for metric in metrics {
281
+ if metric.hit_rate < unused_threshold && (now - metric.last_used_at) > 86_400_000 {
282
+ recommendations.push(TuningRecommendation {
283
+ rec_type: "drop_index".to_string(),
284
+ priority: "low".to_string(),
285
+ index_name: Some(metric.name.clone()),
286
+ reason: format!("Index has {:.1}% hit rate and unused for 24h", metric.hit_rate * 100.0),
287
+ expected_speedup: None,
288
+ estimated_roi: Some(-0.01),
289
+ });
290
+ }
291
+ }
292
+
293
+ // Recommend compound indexes
294
+ let mut field_pairs: HashMap<String, u64> = HashMap::new();
295
+ for trace in traces {
296
+ if trace.fields.len() == 2 {
297
+ let mut sorted = trace.fields.clone();
298
+ sorted.sort();
299
+ let key = sorted.join("|");
300
+ *field_pairs.entry(key).or_insert(0) += 1;
301
+ }
302
+ }
303
+
304
+ for (fields, count) in field_pairs {
305
+ if (count as f64) > traces.len() as f64 * 0.1 {
306
+ recommendations.push(TuningRecommendation {
307
+ rec_type: "create_index".to_string(),
308
+ priority: "medium".to_string(),
309
+ index_name: None,
310
+ reason: format!("Fields '{}' queried together in {} queries", fields.replace('|', ", "), count),
311
+ expected_speedup: Some(5.0),
312
+ estimated_roi: None,
313
+ });
314
+ }
315
+ }
316
+
317
+ // Sort by priority
318
+ let priority_order = |p: &str| match p {
319
+ "high" => 0,
320
+ "medium" => 1,
321
+ _ => 2,
322
+ };
323
+
324
+ recommendations.sort_by(|a, b| {
325
+ let priority_cmp = priority_order(&a.priority).cmp(&priority_order(&b.priority));
326
+ if priority_cmp == std::cmp::Ordering::Equal {
327
+ let speedup_a = a.expected_speedup.unwrap_or(0.0);
328
+ let speedup_b = b.expected_speedup.unwrap_or(0.0);
329
+ speedup_b.partial_cmp(&speedup_a).unwrap_or(std::cmp::Ordering::Equal)
330
+ } else {
331
+ priority_cmp
332
+ }
333
+ });
334
+
335
+ recommendations.truncate(5);
336
+ recommendations
337
+ }
338
+
339
+ pub fn reset(&mut self) {
340
+ self.metrics.clear();
341
+ self.query_traces.clear();
342
+ self.field_query_count.clear();
343
+ }
344
+ }
345
+
346
+ fn current_time_ms() -> u64 {
347
+ std::time::SystemTime::now()
348
+ .duration_since(std::time::UNIX_EPOCH)
349
+ .map(|d| d.as_millis() as u64)
350
+ .unwrap_or(0)
351
+ }
352
+
353
+ /// Automatic tuning engine - applies recommendations automatically.
354
+ pub struct AutoTuner {
355
+ applied_recommendations: std::collections::HashSet<String>,
356
+ }
357
+
358
+ impl AutoTuner {
359
+ pub fn new(_analytics: IndexAnalytics) -> Self {
360
+ AutoTuner {
361
+ applied_recommendations: std::collections::HashSet::new(),
362
+ }
363
+ }
364
+
365
+ /// Check if a recommendation should be auto-applied.
366
+ pub fn should_auto_apply(&self, recommendation: &TuningRecommendation) -> bool {
367
+ if recommendation.priority != "high" {
368
+ return false;
369
+ }
370
+
371
+ let key = format!("{}:{}", recommendation.rec_type, recommendation.index_name.as_ref().unwrap_or(&recommendation.reason));
372
+ if self.applied_recommendations.contains(&key) {
373
+ return false;
374
+ }
375
+
376
+ if (recommendation.expected_speedup.unwrap_or(0.0)) < 5.0 {
377
+ return false;
378
+ }
379
+
380
+ true
381
+ }
382
+
383
+ /// Apply a recommendation.
384
+ pub fn apply_recommendation(&mut self, recommendation: &TuningRecommendation) -> bool {
385
+ if !self.should_auto_apply(recommendation) {
386
+ return false;
387
+ }
388
+
389
+ let key = format!("{}:{}", recommendation.rec_type, recommendation.index_name.as_ref().unwrap_or(&recommendation.reason));
390
+ self.applied_recommendations.insert(key);
391
+ true
392
+ }
393
+
394
+ /// Get recommendations that are ready for auto-apply.
395
+ pub fn get_auto_applicable_recommendations(&self, report: &PerformanceReport) -> Vec<TuningRecommendation> {
396
+ report
397
+ .recommendations
398
+ .iter()
399
+ .filter(|r| self.should_auto_apply(r))
400
+ .cloned()
401
+ .collect()
402
+ }
403
+ }
404
+
405
+ #[cfg(test)]
406
+ mod tests {
407
+ use super::*;
408
+
409
+ #[test]
410
+ fn test_record_index_usage() {
411
+ let mut analytics = IndexAnalytics::new();
412
+ analytics.record_index_usage("test_idx", "read", 2.5);
413
+
414
+ let metrics = analytics.get_index_metrics("test_idx");
415
+ assert!(metrics.is_some());
416
+ assert_eq!(metrics.unwrap().reads, 1);
417
+ }
418
+
419
+ #[test]
420
+ fn test_record_query() {
421
+ let mut analytics = IndexAnalytics::new();
422
+ analytics.record_query(
423
+ vec!["status".to_string()],
424
+ "eq".to_string(),
425
+ 1.5,
426
+ 100,
427
+ 50,
428
+ Some("status_idx".to_string()),
429
+ );
430
+
431
+ assert_eq!(analytics.query_traces.len(), 1);
432
+ assert_eq!(analytics.field_query_count.get("status"), Some(&1));
433
+ }
434
+
435
+ #[test]
436
+ fn test_slowest_queries() {
437
+ let mut analytics = IndexAnalytics::new();
438
+ analytics.record_query(vec!["a".to_string()], "eq".to_string(), 1.0, 10, 5, None);
439
+ analytics.record_query(vec!["b".to_string()], "eq".to_string(), 5.0, 20, 10, None);
440
+ analytics.record_query(vec!["c".to_string()], "eq".to_string(), 3.0, 15, 8, None);
441
+
442
+ let slowest = analytics.get_slowest_queries(2);
443
+ assert_eq!(slowest.len(), 2);
444
+ assert_eq!(slowest[0].execution_time_ms, 5.0);
445
+ assert_eq!(slowest[1].execution_time_ms, 3.0);
446
+ }
447
+
448
+ #[test]
449
+ fn test_generate_report() {
450
+ let mut analytics = IndexAnalytics::new();
451
+ analytics.record_query(
452
+ vec!["status".to_string()],
453
+ "eq".to_string(),
454
+ 2.5,
455
+ 100,
456
+ 50,
457
+ Some("status_idx".to_string()),
458
+ );
459
+ analytics.record_query(
460
+ vec!["created".to_string()],
461
+ "range".to_string(),
462
+ 50.0,
463
+ 10000,
464
+ 100,
465
+ None,
466
+ );
467
+
468
+ let report = analytics.generate_report("orders", "daily");
469
+ assert_eq!(report.collection_name, "orders");
470
+ assert_eq!(report.total_queries, 2);
471
+ assert_eq!(report.indexed_queries, 1);
472
+ assert_eq!(report.full_scans, 1);
473
+ assert!(report.recommendations.len() > 0);
474
+ }
475
+ }