@feltdb/core 0.8.4 → 0.8.5
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.
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +165 -0
- package/dist/create/server-source/Cargo.toml +9 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
- package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
- package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
- package/dist/create/server-source/crates/feltdb/src/lib.rs +507 -6
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
- package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1514 -537
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +288 -0
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1032 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +2 -1
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +326 -6
- package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-DaNwCLRX.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
- package/dist/studio-app/assets/{index-DwgNAIIX.js → index-j8IlhNqJ.js} +1 -1
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workload.d.ts +2 -1
- package/dist/workload.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
/// - Frontier advances: 1, 10, 100
|
|
9
9
|
///
|
|
10
10
|
/// Output: CSV table suitable for crossover curve analysis
|
|
11
|
-
|
|
12
|
-
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
|
|
11
|
+
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
|
13
12
|
use feltdb::causal_dependency_barrier::CausalDependencyBarrier;
|
|
14
13
|
use feltdb::convergence::VectorClock;
|
|
15
14
|
use std::fs::File;
|
|
@@ -65,7 +64,11 @@ impl CrossoverMetrics {
|
|
|
65
64
|
self.ix_index_entries,
|
|
66
65
|
self.speedup,
|
|
67
66
|
self.winner,
|
|
68
|
-
if self.winner == 'I' {
|
|
67
|
+
if self.winner == 'I' {
|
|
68
|
+
"INDEXED"
|
|
69
|
+
} else {
|
|
70
|
+
"FULL_SCAN"
|
|
71
|
+
}
|
|
69
72
|
)
|
|
70
73
|
}
|
|
71
74
|
}
|
|
@@ -86,7 +89,8 @@ fn generate_workload(
|
|
|
86
89
|
let mut operations = Vec::new();
|
|
87
90
|
for i in 0..workload.pending_size {
|
|
88
91
|
let mut required = VectorClock::new();
|
|
89
|
-
let release_threshold =
|
|
92
|
+
let release_threshold =
|
|
93
|
+
(workload.pending_size as f64 * workload.release_selectivity) as usize;
|
|
90
94
|
let ts = if i < release_threshold {
|
|
91
95
|
10 // Will be released when frontier advances to 10
|
|
92
96
|
} else {
|
|
@@ -185,7 +189,13 @@ fn measure_indexed(
|
|
|
185
189
|
candidates_discovered = barrier.get_dependency_index().waiters.len() as u64;
|
|
186
190
|
}
|
|
187
191
|
|
|
188
|
-
(
|
|
192
|
+
(
|
|
193
|
+
latency_ns,
|
|
194
|
+
candidates_discovered,
|
|
195
|
+
candidates_examined,
|
|
196
|
+
ops_released,
|
|
197
|
+
candidates_discovered,
|
|
198
|
+
)
|
|
189
199
|
}
|
|
190
200
|
|
|
191
201
|
/// Phase 7.1.3: Crossover Matrix Benchmark
|
|
@@ -201,8 +211,10 @@ fn benchmark_crossover_matrix(c: &mut Criterion) {
|
|
|
201
211
|
let mut results = Vec::new();
|
|
202
212
|
|
|
203
213
|
println!("\n=== Phase 7.1.3: Crossover Analysis ===");
|
|
204
|
-
println!(
|
|
205
|
-
"
|
|
214
|
+
println!(
|
|
215
|
+
"{:>7} {:>3} {:>5} {:>10} {:>12} {:>12} {:>7}",
|
|
216
|
+
"Pending", "Adv", "Sel%", "FS (µs)", "IX (µs)", "Speedup", "Winner"
|
|
217
|
+
);
|
|
206
218
|
|
|
207
219
|
for pending in &pending_sizes {
|
|
208
220
|
for advances in &advance_counts {
|
|
@@ -216,7 +228,8 @@ fn benchmark_crossover_matrix(c: &mut Criterion) {
|
|
|
216
228
|
let (ops, advs) = generate_workload(&workload, 42);
|
|
217
229
|
|
|
218
230
|
// Measure both paths
|
|
219
|
-
let (fs_latency, fs_examined, fs_released) =
|
|
231
|
+
let (fs_latency, fs_examined, fs_released) =
|
|
232
|
+
measure_full_scan(&workload, &ops, &advs);
|
|
220
233
|
let (ix_latency, ix_discovered, ix_examined, ix_released, ix_entries) =
|
|
221
234
|
measure_indexed(&workload, &ops, &advs);
|
|
222
235
|
|
|
@@ -231,9 +244,20 @@ fn benchmark_crossover_matrix(c: &mut Criterion) {
|
|
|
231
244
|
let fs_us = fs_latency as f64 / 1000.0;
|
|
232
245
|
let ix_us = ix_latency as f64 / 1000.0;
|
|
233
246
|
|
|
234
|
-
println!(
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
println!(
|
|
248
|
+
"{:>7} {:>3} {:>5.0} {:>10.1} {:>12.1} {:>12.2}x {}",
|
|
249
|
+
pending,
|
|
250
|
+
advances,
|
|
251
|
+
selectivity * 100.0,
|
|
252
|
+
fs_us,
|
|
253
|
+
ix_us,
|
|
254
|
+
speedup,
|
|
255
|
+
if winner == 'I' {
|
|
256
|
+
"INDEXED"
|
|
257
|
+
} else {
|
|
258
|
+
"FULL_SCAN"
|
|
259
|
+
}
|
|
260
|
+
);
|
|
237
261
|
|
|
238
262
|
let metrics = CrossoverMetrics {
|
|
239
263
|
pending: *pending,
|
|
@@ -254,15 +278,21 @@ fn benchmark_crossover_matrix(c: &mut Criterion) {
|
|
|
254
278
|
results.push(metrics);
|
|
255
279
|
|
|
256
280
|
// Criterion benchmarks for regression gates
|
|
257
|
-
let bench_name = format!(
|
|
258
|
-
|
|
281
|
+
let bench_name = format!(
|
|
282
|
+
"pending_{}_adv_{}_sel_{:.0}pct",
|
|
283
|
+
pending,
|
|
284
|
+
advances,
|
|
285
|
+
selectivity * 100.0
|
|
286
|
+
);
|
|
259
287
|
|
|
260
288
|
group.bench_with_input(
|
|
261
289
|
BenchmarkId::new("full_scan", &bench_name),
|
|
262
290
|
&workload,
|
|
263
291
|
|b, w| {
|
|
264
292
|
let (ops, advs) = generate_workload(w, 42);
|
|
265
|
-
b.iter(||
|
|
293
|
+
b.iter(|| {
|
|
294
|
+
measure_full_scan(black_box(w), black_box(&ops), black_box(&advs))
|
|
295
|
+
});
|
|
266
296
|
},
|
|
267
297
|
);
|
|
268
298
|
|
|
@@ -282,7 +312,8 @@ fn benchmark_crossover_matrix(c: &mut Criterion) {
|
|
|
282
312
|
|
|
283
313
|
// Write results to CSV for analysis
|
|
284
314
|
if let Ok(mut file) = File::create("target/phase_7_1_3_crossover.csv") {
|
|
285
|
-
let _ =
|
|
315
|
+
let _ =
|
|
316
|
+
writeln!(file,
|
|
286
317
|
"pending,advances,selectivity_pct,fs_latency_ns,fs_entries_examined,fs_ops_released,\
|
|
287
318
|
ix_latency_ns,ix_candidates_discovered,ix_candidates_examined,ix_ops_released,\
|
|
288
319
|
ix_index_entries,speedup,winner,winner_name");
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
///
|
|
3
3
|
/// Defines explicit, machine-readable submission semantics.
|
|
4
4
|
/// Never silently drops operations. Rejection is explicit and retryable.
|
|
5
|
-
|
|
6
5
|
use serde::{Deserialize, Serialize};
|
|
7
6
|
use std::fmt;
|
|
8
7
|
|
|
@@ -12,9 +11,7 @@ pub enum SubmissionOutcome {
|
|
|
12
11
|
/// Operation accepted for processing
|
|
13
12
|
/// Operations are accepted in order but may not yet be committed.
|
|
14
13
|
/// Caller should wait for CommitAck before assuming durability.
|
|
15
|
-
Accepted {
|
|
16
|
-
operation_id: u64,
|
|
17
|
-
},
|
|
14
|
+
Accepted { operation_id: u64 },
|
|
18
15
|
|
|
19
16
|
/// Operation rejected due to backpressure
|
|
20
17
|
/// Caller should retry with backoff
|
|
@@ -103,11 +100,11 @@ impl ProductionRetryPolicy {
|
|
|
103
100
|
ProductionRetryPolicy {
|
|
104
101
|
max_attempts: 4,
|
|
105
102
|
backoff_sequence: vec![
|
|
106
|
-
0,
|
|
107
|
-
100,
|
|
108
|
-
200,
|
|
109
|
-
400,
|
|
110
|
-
|
|
103
|
+
0, // Attempt 1: no backoff (immediate)
|
|
104
|
+
100, // Attempt 2: 100µs
|
|
105
|
+
200, // Attempt 3: 200µs
|
|
106
|
+
400, // Attempt 4: 400µs
|
|
107
|
+
// Total: 700µs of bounded admission attempts
|
|
111
108
|
],
|
|
112
109
|
}
|
|
113
110
|
}
|
|
@@ -117,9 +114,7 @@ impl ProductionRetryPolicy {
|
|
|
117
114
|
pub fn stress_test() -> Self {
|
|
118
115
|
ProductionRetryPolicy {
|
|
119
116
|
max_attempts: 10,
|
|
120
|
-
backoff_sequence: vec![
|
|
121
|
-
0, 50, 100, 150, 200, 250, 300, 350, 400, 450,
|
|
122
|
-
],
|
|
117
|
+
backoff_sequence: vec![0, 50, 100, 150, 200, 250, 300, 350, 400, 450],
|
|
123
118
|
}
|
|
124
119
|
}
|
|
125
120
|
|
|
@@ -168,9 +163,7 @@ mod tests {
|
|
|
168
163
|
|
|
169
164
|
#[test]
|
|
170
165
|
fn outcome_accessors_work() {
|
|
171
|
-
let accepted = SubmissionOutcome::Accepted {
|
|
172
|
-
operation_id: 123,
|
|
173
|
-
};
|
|
166
|
+
let accepted = SubmissionOutcome::Accepted { operation_id: 123 };
|
|
174
167
|
assert!(accepted.is_accepted());
|
|
175
168
|
assert!(!accepted.is_rejected());
|
|
176
169
|
|
|
@@ -38,7 +38,10 @@ mod tests {
|
|
|
38
38
|
record_id: "rec1".to_string(),
|
|
39
39
|
fields: {
|
|
40
40
|
let mut m = HashMap::new();
|
|
41
|
-
m.insert(
|
|
41
|
+
m.insert(
|
|
42
|
+
"field".to_string(),
|
|
43
|
+
serde_json::json!(format!("value_{}", seq)),
|
|
44
|
+
);
|
|
42
45
|
m
|
|
43
46
|
},
|
|
44
47
|
},
|
|
@@ -62,7 +65,11 @@ mod tests {
|
|
|
62
65
|
assert!(true, "Operation explicitly accepted");
|
|
63
66
|
}
|
|
64
67
|
SubmissionOutcome::Rejected(ref reason) => {
|
|
65
|
-
assert!(
|
|
68
|
+
assert!(
|
|
69
|
+
true,
|
|
70
|
+
"Operation explicitly rejected with reason: {:?}",
|
|
71
|
+
reason
|
|
72
|
+
);
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
}
|
|
@@ -101,7 +108,10 @@ mod tests {
|
|
|
101
108
|
|
|
102
109
|
let op1 = create_operation("node1", 1);
|
|
103
110
|
let outcome1 = submitter.submit(op1.clone());
|
|
104
|
-
assert!(
|
|
111
|
+
assert!(
|
|
112
|
+
outcome1.is_accepted(),
|
|
113
|
+
"First submission should be accepted"
|
|
114
|
+
);
|
|
105
115
|
|
|
106
116
|
let outcome2 = submitter.submit(op1);
|
|
107
117
|
assert!(outcome2.is_rejected(), "Duplicate should be rejected");
|
|
@@ -110,7 +120,10 @@ mod tests {
|
|
|
110
120
|
SubmissionOutcome::Rejected(RejectionReason::DuplicateDetected {
|
|
111
121
|
original_operation_id,
|
|
112
122
|
}) => {
|
|
113
|
-
assert_eq!(
|
|
123
|
+
assert_eq!(
|
|
124
|
+
original_operation_id, 1,
|
|
125
|
+
"Should identify original operation ID"
|
|
126
|
+
);
|
|
114
127
|
}
|
|
115
128
|
_ => panic!("Expected DuplicateDetected rejection reason"),
|
|
116
129
|
}
|
|
@@ -130,8 +143,14 @@ mod tests {
|
|
|
130
143
|
retry_after_us,
|
|
131
144
|
suggested_backoff_ms,
|
|
132
145
|
} => {
|
|
133
|
-
assert!(
|
|
134
|
-
|
|
146
|
+
assert!(
|
|
147
|
+
retry_after_us > 0,
|
|
148
|
+
"retry_after_us should guide backoff strategy"
|
|
149
|
+
);
|
|
150
|
+
assert!(
|
|
151
|
+
suggested_backoff_ms > 0,
|
|
152
|
+
"suggested_backoff_ms should guide backoff"
|
|
153
|
+
);
|
|
135
154
|
}
|
|
136
155
|
_ => panic!("Expected AdmissionBackpressure"),
|
|
137
156
|
}
|
|
@@ -159,7 +178,10 @@ mod tests {
|
|
|
159
178
|
RejectionReason::DuplicateDetected {
|
|
160
179
|
original_operation_id,
|
|
161
180
|
} => {
|
|
162
|
-
assert_eq!(
|
|
181
|
+
assert_eq!(
|
|
182
|
+
original_operation_id, 42,
|
|
183
|
+
"Should identify original operation"
|
|
184
|
+
);
|
|
163
185
|
}
|
|
164
186
|
_ => panic!("Expected DuplicateDetected"),
|
|
165
187
|
}
|
|
@@ -279,7 +301,10 @@ mod tests {
|
|
|
279
301
|
let policy = ProductionRetryPolicy::standard();
|
|
280
302
|
|
|
281
303
|
// Verify bounded policy constraints
|
|
282
|
-
assert!(
|
|
304
|
+
assert!(
|
|
305
|
+
policy.max_attempts < 100,
|
|
306
|
+
"max_attempts should be reasonable"
|
|
307
|
+
);
|
|
283
308
|
assert!(
|
|
284
309
|
policy.backoff_sequence.len() <= policy.max_attempts,
|
|
285
310
|
"backoff sequence length should not exceed max_attempts"
|
|
@@ -353,7 +378,11 @@ mod tests {
|
|
|
353
378
|
|
|
354
379
|
let elapsed = start.elapsed();
|
|
355
380
|
|
|
356
|
-
assert_eq!(
|
|
381
|
+
assert_eq!(
|
|
382
|
+
accepted + rejected,
|
|
383
|
+
1000,
|
|
384
|
+
"Every operation must get an outcome"
|
|
385
|
+
);
|
|
357
386
|
assert_eq!(rejected, 0, "Under normal load, no rejections expected");
|
|
358
387
|
println!(
|
|
359
388
|
"Processed 1000 operations in {:.2}ms ({:.0} ops/sec)",
|
|
@@ -398,7 +427,10 @@ mod tests {
|
|
|
398
427
|
|
|
399
428
|
// First cycle: all should be accepted
|
|
400
429
|
assert_eq!(accepted_by_cycle[0], 20, "First cycle should accept all");
|
|
401
|
-
assert_eq!(
|
|
430
|
+
assert_eq!(
|
|
431
|
+
duplicates_by_cycle[0], 0,
|
|
432
|
+
"First cycle should have no duplicates"
|
|
433
|
+
);
|
|
402
434
|
|
|
403
435
|
// Subsequent cycles: all should be duplicates
|
|
404
436
|
for cycle in 1..5 {
|
|
@@ -464,9 +496,7 @@ mod tests {
|
|
|
464
496
|
|
|
465
497
|
#[test]
|
|
466
498
|
fn contract_outcome_accessors_are_correct() {
|
|
467
|
-
let accepted = SubmissionOutcome::Accepted {
|
|
468
|
-
operation_id: 42,
|
|
469
|
-
};
|
|
499
|
+
let accepted = SubmissionOutcome::Accepted { operation_id: 42 };
|
|
470
500
|
assert!(accepted.is_accepted());
|
|
471
501
|
assert!(!accepted.is_rejected());
|
|
472
502
|
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
/// send → maybe delay → maybe duplicate → maybe drop → eventually deliver
|
|
10
10
|
///
|
|
11
11
|
/// FeltDB must converge itself. Simulator cannot help.
|
|
12
|
-
|
|
13
12
|
use std::collections::{HashMap, VecDeque};
|
|
14
13
|
|
|
15
14
|
/// Reproducible fault seed
|
|
@@ -44,32 +43,17 @@ pub enum LinkState {
|
|
|
44
43
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
45
44
|
pub enum NetworkEvent {
|
|
46
45
|
/// Delay a message by N ticks
|
|
47
|
-
Delay {
|
|
48
|
-
message_id: MessageId,
|
|
49
|
-
ticks: u64,
|
|
50
|
-
},
|
|
46
|
+
Delay { message_id: MessageId, ticks: u64 },
|
|
51
47
|
/// Drop a message permanently
|
|
52
|
-
Drop {
|
|
53
|
-
message_id: MessageId,
|
|
54
|
-
},
|
|
48
|
+
Drop { message_id: MessageId },
|
|
55
49
|
/// Duplicate a message
|
|
56
|
-
Duplicate {
|
|
57
|
-
message_id: MessageId,
|
|
58
|
-
},
|
|
50
|
+
Duplicate { message_id: MessageId },
|
|
59
51
|
/// Partition a directional link
|
|
60
|
-
Partition {
|
|
61
|
-
from: NodeId,
|
|
62
|
-
to: NodeId,
|
|
63
|
-
},
|
|
52
|
+
Partition { from: NodeId, to: NodeId },
|
|
64
53
|
/// Heal a directional link
|
|
65
|
-
Heal {
|
|
66
|
-
from: NodeId,
|
|
67
|
-
to: NodeId,
|
|
68
|
-
},
|
|
54
|
+
Heal { from: NodeId, to: NodeId },
|
|
69
55
|
/// Reorder messages on a connection
|
|
70
|
-
Reorder {
|
|
71
|
-
connection: ConnectionId,
|
|
72
|
-
},
|
|
56
|
+
Reorder { connection: ConnectionId },
|
|
73
57
|
}
|
|
74
58
|
|
|
75
59
|
/// Deterministic network schedule (fully reproducible from seed)
|
|
@@ -125,11 +109,13 @@ impl ConnectivityMatrix {
|
|
|
125
109
|
}
|
|
126
110
|
|
|
127
111
|
pub fn partition(&mut self, from: NodeId, to: NodeId) {
|
|
128
|
-
self.links
|
|
112
|
+
self.links
|
|
113
|
+
.insert(ConnectionId { from, to }, LinkState::Partitioned);
|
|
129
114
|
}
|
|
130
115
|
|
|
131
116
|
pub fn heal(&mut self, from: NodeId, to: NodeId) {
|
|
132
|
-
self.links
|
|
117
|
+
self.links
|
|
118
|
+
.insert(ConnectionId { from, to }, LinkState::Connected);
|
|
133
119
|
}
|
|
134
120
|
|
|
135
121
|
pub fn get_state(&self, from: NodeId, to: NodeId) -> LinkState {
|
|
@@ -326,10 +312,7 @@ impl ReplicationTransport for AdversarialTransport {
|
|
|
326
312
|
|
|
327
313
|
fn apply_fault(&mut self, event: NetworkEvent) {
|
|
328
314
|
match event {
|
|
329
|
-
NetworkEvent::Delay {
|
|
330
|
-
message_id,
|
|
331
|
-
ticks,
|
|
332
|
-
} => {
|
|
315
|
+
NetworkEvent::Delay { message_id, ticks } => {
|
|
333
316
|
// Find message and delay delivery
|
|
334
317
|
for msg in &mut self.in_flight {
|
|
335
318
|
if msg.id == message_id {
|
|
@@ -438,10 +421,7 @@ mod tests {
|
|
|
438
421
|
let n2 = NodeId(2);
|
|
439
422
|
|
|
440
423
|
// Partition n1 → n2
|
|
441
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
442
|
-
from: n1,
|
|
443
|
-
to: n2,
|
|
444
|
-
});
|
|
424
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n2 });
|
|
445
425
|
|
|
446
426
|
// Message should be dropped
|
|
447
427
|
let _msg_id = transport.send(n1, n2, vec![1, 2, 3]);
|
|
@@ -489,9 +469,7 @@ mod tests {
|
|
|
489
469
|
let msg_id = transport.send(n1, n2, vec![1, 2, 3]);
|
|
490
470
|
|
|
491
471
|
// Duplicate the message
|
|
492
|
-
transport.apply_fault(NetworkEvent::Duplicate {
|
|
493
|
-
message_id: msg_id,
|
|
494
|
-
});
|
|
472
|
+
transport.apply_fault(NetworkEvent::Duplicate { message_id: msg_id });
|
|
495
473
|
|
|
496
474
|
transport.tick();
|
|
497
475
|
let messages = transport.receive(n2);
|
|
@@ -510,9 +488,7 @@ mod tests {
|
|
|
510
488
|
|
|
511
489
|
let msg_id = transport.send(n1, n2, vec![1, 2, 3]);
|
|
512
490
|
|
|
513
|
-
transport.apply_fault(NetworkEvent::Drop {
|
|
514
|
-
message_id: msg_id,
|
|
515
|
-
});
|
|
491
|
+
transport.apply_fault(NetworkEvent::Drop { message_id: msg_id });
|
|
516
492
|
|
|
517
493
|
transport.tick();
|
|
518
494
|
let messages = transport.receive(n2);
|
|
@@ -528,10 +504,7 @@ mod tests {
|
|
|
528
504
|
let n2 = NodeId(2);
|
|
529
505
|
|
|
530
506
|
// Partition n1 → n2 only (asymmetric)
|
|
531
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
532
|
-
from: n1,
|
|
533
|
-
to: n2,
|
|
534
|
-
});
|
|
507
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n2 });
|
|
535
508
|
|
|
536
509
|
// n1 → n2 blocked
|
|
537
510
|
let _msg_id = transport.send(n1, n2, vec![1, 2, 3]);
|
|
@@ -85,11 +85,13 @@ impl IndexAnalytics {
|
|
|
85
85
|
if operation == "read" {
|
|
86
86
|
let old_avg = metric.avg_query_time_ms;
|
|
87
87
|
metric.reads += 1;
|
|
88
|
-
metric.avg_query_time_ms =
|
|
88
|
+
metric.avg_query_time_ms =
|
|
89
|
+
(old_avg * (metric.reads - 1) as f64 + time_ms) / metric.reads as f64;
|
|
89
90
|
} else {
|
|
90
91
|
let old_avg = metric.avg_write_time_ms;
|
|
91
92
|
metric.writes += 1;
|
|
92
|
-
metric.avg_write_time_ms =
|
|
93
|
+
metric.avg_write_time_ms =
|
|
94
|
+
(old_avg * (metric.writes - 1) as f64 + time_ms) / metric.writes as f64;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
metric.last_used_at = current_time_ms();
|
|
@@ -142,13 +144,21 @@ impl IndexAnalytics {
|
|
|
142
144
|
|
|
143
145
|
pub fn get_slowest_queries(&self, limit: usize) -> Vec<QueryTrace> {
|
|
144
146
|
let mut traces = self.query_traces.clone();
|
|
145
|
-
traces.sort_by(|a, b|
|
|
147
|
+
traces.sort_by(|a, b| {
|
|
148
|
+
b.execution_time_ms
|
|
149
|
+
.partial_cmp(&a.execution_time_ms)
|
|
150
|
+
.unwrap_or(std::cmp::Ordering::Equal)
|
|
151
|
+
});
|
|
146
152
|
traces.truncate(limit);
|
|
147
153
|
traces
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
pub fn get_frequent_fields(&self, limit: usize) -> Vec<(String, u64)> {
|
|
151
|
-
let mut fields: Vec<_> = self
|
|
157
|
+
let mut fields: Vec<_> = self
|
|
158
|
+
.field_query_count
|
|
159
|
+
.iter()
|
|
160
|
+
.map(|(k, v)| (k.clone(), *v))
|
|
161
|
+
.collect();
|
|
152
162
|
fields.sort_by(|a, b| b.1.cmp(&a.1));
|
|
153
163
|
fields.truncate(limit);
|
|
154
164
|
fields
|
|
@@ -182,11 +192,7 @@ impl IndexAnalytics {
|
|
|
182
192
|
}
|
|
183
193
|
}
|
|
184
194
|
|
|
185
|
-
pub fn generate_report(
|
|
186
|
-
&self,
|
|
187
|
-
collection_name: &str,
|
|
188
|
-
period: &str,
|
|
189
|
-
) -> PerformanceReport {
|
|
195
|
+
pub fn generate_report(&self, collection_name: &str, period: &str) -> PerformanceReport {
|
|
190
196
|
let now = current_time_ms();
|
|
191
197
|
let cutoff = match period {
|
|
192
198
|
"hourly" => now - 3_600_000,
|
|
@@ -201,20 +207,31 @@ impl IndexAnalytics {
|
|
|
201
207
|
.cloned()
|
|
202
208
|
.collect();
|
|
203
209
|
|
|
204
|
-
let indexed_queries = recent_traces
|
|
210
|
+
let indexed_queries = recent_traces
|
|
211
|
+
.iter()
|
|
212
|
+
.filter(|t| t.index_used.is_some())
|
|
213
|
+
.count();
|
|
205
214
|
let full_scans = recent_traces.len() - indexed_queries;
|
|
206
215
|
|
|
207
216
|
let avg_query_time = if recent_traces.is_empty() {
|
|
208
217
|
0.0
|
|
209
218
|
} else {
|
|
210
|
-
recent_traces
|
|
219
|
+
recent_traces
|
|
220
|
+
.iter()
|
|
221
|
+
.map(|t| t.execution_time_ms)
|
|
222
|
+
.sum::<f64>()
|
|
223
|
+
/ recent_traces.len() as f64
|
|
211
224
|
};
|
|
212
225
|
|
|
213
226
|
let slowest_query = if recent_traces.is_empty() {
|
|
214
227
|
None
|
|
215
228
|
} else {
|
|
216
229
|
let mut traces = recent_traces.clone();
|
|
217
|
-
traces.sort_by(|a, b|
|
|
230
|
+
traces.sort_by(|a, b| {
|
|
231
|
+
b.execution_time_ms
|
|
232
|
+
.partial_cmp(&a.execution_time_ms)
|
|
233
|
+
.unwrap_or(std::cmp::Ordering::Equal)
|
|
234
|
+
});
|
|
218
235
|
traces.first().cloned()
|
|
219
236
|
};
|
|
220
237
|
|
|
@@ -267,7 +284,10 @@ impl IndexAnalytics {
|
|
|
267
284
|
rec_type: "create_index".to_string(),
|
|
268
285
|
priority: "high".to_string(),
|
|
269
286
|
index_name: None,
|
|
270
|
-
reason: format!(
|
|
287
|
+
reason: format!(
|
|
288
|
+
"Field '{}' scanned {:.0} rows per query",
|
|
289
|
+
field, avg_scanned_per_query
|
|
290
|
+
),
|
|
271
291
|
expected_speedup: Some(avg_scanned_per_query / 10.0),
|
|
272
292
|
estimated_roi: None,
|
|
273
293
|
});
|
|
@@ -283,7 +303,10 @@ impl IndexAnalytics {
|
|
|
283
303
|
rec_type: "drop_index".to_string(),
|
|
284
304
|
priority: "low".to_string(),
|
|
285
305
|
index_name: Some(metric.name.clone()),
|
|
286
|
-
reason: format!(
|
|
306
|
+
reason: format!(
|
|
307
|
+
"Index has {:.1}% hit rate and unused for 24h",
|
|
308
|
+
metric.hit_rate * 100.0
|
|
309
|
+
),
|
|
287
310
|
expected_speedup: None,
|
|
288
311
|
estimated_roi: Some(-0.01),
|
|
289
312
|
});
|
|
@@ -307,7 +330,11 @@ impl IndexAnalytics {
|
|
|
307
330
|
rec_type: "create_index".to_string(),
|
|
308
331
|
priority: "medium".to_string(),
|
|
309
332
|
index_name: None,
|
|
310
|
-
reason: format!(
|
|
333
|
+
reason: format!(
|
|
334
|
+
"Fields '{}' queried together in {} queries",
|
|
335
|
+
fields.replace('|', ", "),
|
|
336
|
+
count
|
|
337
|
+
),
|
|
311
338
|
expected_speedup: Some(5.0),
|
|
312
339
|
estimated_roi: None,
|
|
313
340
|
});
|
|
@@ -326,7 +353,9 @@ impl IndexAnalytics {
|
|
|
326
353
|
if priority_cmp == std::cmp::Ordering::Equal {
|
|
327
354
|
let speedup_a = a.expected_speedup.unwrap_or(0.0);
|
|
328
355
|
let speedup_b = b.expected_speedup.unwrap_or(0.0);
|
|
329
|
-
speedup_b
|
|
356
|
+
speedup_b
|
|
357
|
+
.partial_cmp(&speedup_a)
|
|
358
|
+
.unwrap_or(std::cmp::Ordering::Equal)
|
|
330
359
|
} else {
|
|
331
360
|
priority_cmp
|
|
332
361
|
}
|
|
@@ -368,7 +397,14 @@ impl AutoTuner {
|
|
|
368
397
|
return false;
|
|
369
398
|
}
|
|
370
399
|
|
|
371
|
-
let key = format!(
|
|
400
|
+
let key = format!(
|
|
401
|
+
"{}:{}",
|
|
402
|
+
recommendation.rec_type,
|
|
403
|
+
recommendation
|
|
404
|
+
.index_name
|
|
405
|
+
.as_ref()
|
|
406
|
+
.unwrap_or(&recommendation.reason)
|
|
407
|
+
);
|
|
372
408
|
if self.applied_recommendations.contains(&key) {
|
|
373
409
|
return false;
|
|
374
410
|
}
|
|
@@ -386,13 +422,23 @@ impl AutoTuner {
|
|
|
386
422
|
return false;
|
|
387
423
|
}
|
|
388
424
|
|
|
389
|
-
let key = format!(
|
|
425
|
+
let key = format!(
|
|
426
|
+
"{}:{}",
|
|
427
|
+
recommendation.rec_type,
|
|
428
|
+
recommendation
|
|
429
|
+
.index_name
|
|
430
|
+
.as_ref()
|
|
431
|
+
.unwrap_or(&recommendation.reason)
|
|
432
|
+
);
|
|
390
433
|
self.applied_recommendations.insert(key);
|
|
391
434
|
true
|
|
392
435
|
}
|
|
393
436
|
|
|
394
437
|
/// Get recommendations that are ready for auto-apply.
|
|
395
|
-
pub fn get_auto_applicable_recommendations(
|
|
438
|
+
pub fn get_auto_applicable_recommendations(
|
|
439
|
+
&self,
|
|
440
|
+
report: &PerformanceReport,
|
|
441
|
+
) -> Vec<TuningRecommendation> {
|
|
396
442
|
report
|
|
397
443
|
.recommendations
|
|
398
444
|
.iter()
|