@feltdb/core 0.8.4 → 0.8.6

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 (160) hide show
  1. package/dist/create/package-versions.js +1 -1
  2. package/dist/create/server-source/Cargo.lock +165 -0
  3. package/dist/create/server-source/Cargo.toml +9 -0
  4. package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
  5. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
  6. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
  7. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
  8. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
  9. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
  10. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
  11. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
  12. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
  13. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
  14. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
  15. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
  19. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
  20. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
  21. package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
  22. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
  23. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
  24. package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
  25. package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
  26. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
  27. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
  28. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
  29. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
  30. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
  31. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
  32. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
  33. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
  34. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
  35. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
  36. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
  37. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
  38. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
  39. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
  40. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
  41. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
  42. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
  43. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
  44. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
  45. package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
  46. package/dist/create/server-source/crates/feltdb/src/lib.rs +1648 -46
  47. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
  48. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
  49. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
  50. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
  51. package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
  52. package/dist/create/server-source/crates/feltdb/src/operation.rs +39 -0
  53. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
  54. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
  55. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
  56. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
  57. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
  58. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
  59. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
  60. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
  61. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
  62. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
  63. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
  64. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
  65. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
  66. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
  67. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
  68. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
  69. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
  70. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
  71. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
  72. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
  73. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
  74. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
  75. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
  76. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
  77. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
  78. package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
  79. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
  80. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
  81. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
  82. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
  83. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
  84. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
  85. package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
  86. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
  87. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
  88. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
  89. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
  90. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
  91. package/dist/create/server-source/crates/feltdb/src/state_model.rs +1565 -536
  92. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
  93. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
  94. package/dist/create/server-source/crates/feltdb/src/storage.rs +9 -3
  95. package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
  96. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
  97. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
  98. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
  99. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
  100. package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
  101. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
  102. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
  103. package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
  104. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
  105. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +132 -0
  106. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
  107. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +272 -0
  108. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +467 -0
  109. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +309 -0
  110. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +445 -0
  111. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +518 -0
  112. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
  113. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
  114. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
  115. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
  116. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
  117. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +278 -0
  118. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
  119. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
  120. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
  121. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
  122. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
  123. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
  124. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
  125. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
  126. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
  127. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
  128. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
  129. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1365 -0
  130. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
  131. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
  132. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +417 -0
  133. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
  134. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
  135. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
  136. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
  137. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
  138. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
  139. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
  140. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
  141. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
  142. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
  143. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
  144. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
  145. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
  146. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
  147. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +442 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +16 -1
  149. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
  150. package/dist/create/server-source/crates/feltdb-server/src/main.rs +695 -47
  151. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +21 -0
  152. package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-C1VhI-U5.js} +1 -1
  153. package/dist/studio-app/assets/feltdb_wasm_bg-C8HXbAXb.wasm +0 -0
  154. package/dist/studio-app/assets/{index-DwgNAIIX.js → index-Bbos1m2U.js} +1 -1
  155. package/dist/studio-app/index.html +1 -1
  156. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  157. package/dist/workload.d.ts +2 -1
  158. package/dist/workload.d.ts.map +1 -1
  159. package/package.json +1 -1
  160. package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
@@ -0,0 +1,272 @@
1
+ //! Compaction must not stop the world on a timer.
2
+ //!
3
+ //! An incident on the managed deployment showed `/health` returning `200` while
4
+ //! every application-scoped operation timed out or returned `502`, across
5
+ //! several minutes, on four unrelated collections at once.
6
+ //!
7
+ //! Four collections failing together is the signature of a **global stall**,
8
+ //! not of four collection-specific faults. The mechanism was here:
9
+ //!
10
+ //! > `compact_operation_log` takes the one lock that serializes every read and
11
+ //! > every write, then serializes **every row in the database** to a temp file
12
+ //! > and `fsync`s it — and a background task called it **every two seconds**.
13
+ //!
14
+ //! Pruning acknowledged operations from memory is cheap. Rewriting the log is
15
+ //! not. This file holds them apart: pruning still happens whenever it can, and
16
+ //! the rewrite waits until enough has accumulated to be worth the stall.
17
+ //!
18
+ //! See `docs/architecture/compaction-stall.md`.
19
+
20
+ use feltdb::state_model::StateStore;
21
+ use feltdb::{CompactionOutcome, CompactionPolicy, FeltDb};
22
+ use serde_json::json;
23
+ use std::sync::Arc;
24
+ use std::time::Instant;
25
+ use tempfile::TempDir;
26
+
27
+ /// Acknowledge everything a database has produced, so a compaction has work.
28
+ fn acknowledge_all(db: &Arc<FeltDb>) {
29
+ let versions = db.operation_versions().unwrap();
30
+ db.acknowledge_peer_versions("peer-1".to_string(), versions)
31
+ .unwrap();
32
+ }
33
+
34
+ fn write_n(db: &Arc<FeltDb>, n: usize) {
35
+ for index in 0..n {
36
+ db.insert(&format!("tasks:{index}"), json!({ "n": index }))
37
+ .unwrap();
38
+ }
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // The fix
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /// **A timer-driven compaction prunes without rewriting the log.**
46
+ ///
47
+ /// This is the change that stops the stall. The in-memory operation log still
48
+ /// shrinks on every tick; the durable rewrite — the expensive half — waits.
49
+ #[test]
50
+ fn a_policy_driven_compaction_defers_the_rewrite() {
51
+ let directory = TempDir::new().unwrap();
52
+ let path = directory.path().join("defer.log");
53
+ let db = Arc::new(FeltDb::open(&path).unwrap());
54
+ db.set_compaction_policy(CompactionPolicy {
55
+ rewrite_after_pruned: 1000,
56
+ });
57
+
58
+ write_n(&db, 20);
59
+ acknowledge_all(&db);
60
+ let before = std::fs::metadata(&path).unwrap().len();
61
+
62
+ let outcome = db
63
+ .maybe_compact_operation_log(&["peer-1".to_string()])
64
+ .unwrap();
65
+
66
+ match outcome {
67
+ CompactionOutcome::Deferred {
68
+ pruned,
69
+ pending_since_rewrite,
70
+ } => {
71
+ assert!(pruned > 0, "the cheap half still happened");
72
+ assert_eq!(pending_since_rewrite, pruned);
73
+ }
74
+ other => panic!("expected the rewrite to be deferred, got {other:?}"),
75
+ }
76
+ assert_eq!(
77
+ std::fs::metadata(&path).unwrap().len(),
78
+ before,
79
+ "the durable log was not rewritten"
80
+ );
81
+
82
+ // And the database is entirely usable.
83
+ let value: serde_json::Value = db.get("tasks:0").unwrap().unwrap();
84
+ assert_eq!(value["n"], json!(0));
85
+ }
86
+
87
+ /// **The rewrite happens once the accumulated gain earns it**, and the log
88
+ /// actually shrinks when it does.
89
+ #[test]
90
+ fn the_rewrite_happens_once_the_threshold_is_reached() {
91
+ let directory = TempDir::new().unwrap();
92
+ let path = directory.path().join("threshold.log");
93
+ let db = Arc::new(FeltDb::open(&path).unwrap());
94
+ db.set_compaction_policy(CompactionPolicy {
95
+ rewrite_after_pruned: 25,
96
+ });
97
+
98
+ let mut rewrites = 0;
99
+ let mut defers = 0;
100
+ for round in 0..6 {
101
+ for index in 0..10 {
102
+ db.update(
103
+ &format!("tasks:{}", round * 10 + index),
104
+ json!({ "n": index }),
105
+ )
106
+ .unwrap();
107
+ }
108
+ acknowledge_all(&db);
109
+ match db
110
+ .maybe_compact_operation_log(&["peer-1".to_string()])
111
+ .unwrap()
112
+ {
113
+ CompactionOutcome::Rewritten { .. } => rewrites += 1,
114
+ CompactionOutcome::Deferred { .. } => defers += 1,
115
+ CompactionOutcome::Idle => {}
116
+ }
117
+ }
118
+
119
+ assert!(defers > 0, "most ticks defer");
120
+ assert!(rewrites > 0, "and the rewrite still happens");
121
+ assert!(
122
+ defers > rewrites,
123
+ "the point is that deferring is the common case: {defers} deferred, {rewrites} rewritten"
124
+ );
125
+ }
126
+
127
+ /// **An explicit compaction still rewrites immediately.**
128
+ ///
129
+ /// The policy governs the timer, not a caller who is asking for a rewrite now.
130
+ /// Every existing caller of `compact_operation_log` keeps its behaviour.
131
+ #[test]
132
+ fn an_explicit_compaction_still_rewrites_now() {
133
+ let directory = TempDir::new().unwrap();
134
+ let path = directory.path().join("explicit.log");
135
+ let db = Arc::new(FeltDb::open(&path).unwrap());
136
+ db.set_compaction_policy(CompactionPolicy {
137
+ rewrite_after_pruned: usize::MAX,
138
+ });
139
+
140
+ write_n(&db, 20);
141
+ for index in 0..20 {
142
+ db.update(&format!("tasks:{index}"), json!({"n": "updated"}))
143
+ .unwrap();
144
+ }
145
+ acknowledge_all(&db);
146
+ let before = std::fs::metadata(&path).unwrap().len();
147
+
148
+ let pruned = db.compact_operation_log(&["peer-1".to_string()]).unwrap();
149
+ assert!(pruned > 0);
150
+ assert!(
151
+ std::fs::metadata(&path).unwrap().len() < before,
152
+ "an explicit compaction rewrote and shrank the log"
153
+ );
154
+ }
155
+
156
+ /// **The deferred rewrite costs nothing in correctness.**
157
+ ///
158
+ /// Pruning without rewriting leaves the durable log longer than it needs to be,
159
+ /// and nothing else: the database reopens to the same state and the same
160
+ /// history.
161
+ #[test]
162
+ fn deferring_the_rewrite_preserves_state_and_history() {
163
+ let directory = TempDir::new().unwrap();
164
+ let path = directory.path().join("preserve.log");
165
+ let db = Arc::new(FeltDb::open(&path).unwrap());
166
+ db.set_compaction_policy(CompactionPolicy {
167
+ rewrite_after_pruned: usize::MAX,
168
+ });
169
+
170
+ db.insert("tasks:1", json!({"n": 0})).unwrap();
171
+ for n in 1..10 {
172
+ db.update("tasks:1", json!({ "n": n })).unwrap();
173
+ }
174
+ acknowledge_all(&db);
175
+ assert!(matches!(
176
+ db.maybe_compact_operation_log(&["peer-1".to_string()])
177
+ .unwrap(),
178
+ CompactionOutcome::Deferred { .. }
179
+ ));
180
+
181
+ let store = StateStore::with_feltdb(db.clone()).unwrap();
182
+ let history_before = store.history_digest();
183
+ let state_before = db.state_digest().unwrap();
184
+ drop(store);
185
+ drop(db);
186
+
187
+ let reopened = Arc::new(FeltDb::open(&path).unwrap());
188
+ let store = StateStore::with_feltdb(reopened.clone()).unwrap();
189
+ assert_eq!(reopened.state_digest().unwrap(), state_before);
190
+ assert_eq!(store.history_digest(), history_before);
191
+ }
192
+
193
+ /// Nothing to prune is not a rewrite either — the pre-existing early return.
194
+ #[test]
195
+ fn an_idle_compaction_does_no_work() {
196
+ let directory = TempDir::new().unwrap();
197
+ let path = directory.path().join("idle.log");
198
+ let db = Arc::new(FeltDb::open(&path).unwrap());
199
+ write_n(&db, 5);
200
+ let before = std::fs::read(&path).unwrap();
201
+
202
+ // No peer has acknowledged anything.
203
+ let outcome = db.maybe_compact_operation_log(&[]).unwrap();
204
+ assert_eq!(outcome, CompactionOutcome::Idle);
205
+ assert_eq!(outcome.pruned(), 0);
206
+ assert!(!outcome.rewrote_log());
207
+ assert_eq!(std::fs::read(&path).unwrap(), before);
208
+ }
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // The stall itself
212
+ // ---------------------------------------------------------------------------
213
+
214
+ /// **The rewrite is what stalls, and it grows with the database.**
215
+ ///
216
+ /// Measured as an ordering rather than a number: a rewrite over a larger
217
+ /// database holds the lock longer. That is why running it on a two-second timer
218
+ /// degraded as the deployment grew, and why the default policy now waits.
219
+ #[test]
220
+ fn the_rewrite_cost_grows_with_the_database() {
221
+ let cost = |rows: usize| {
222
+ let directory = TempDir::new().unwrap();
223
+ let path = directory.path().join("cost.log");
224
+ let db = Arc::new(FeltDb::open(&path).unwrap());
225
+ write_n(&db, rows);
226
+ for index in 0..rows {
227
+ db.update(&format!("tasks:{index}"), json!({"n": "updated"}))
228
+ .unwrap();
229
+ }
230
+ acknowledge_all(&db);
231
+ let started = Instant::now();
232
+ db.compact_operation_log(&["peer-1".to_string()]).unwrap();
233
+ started.elapsed()
234
+ };
235
+
236
+ let small = cost(20);
237
+ let large = cost(400);
238
+ assert!(
239
+ large > small,
240
+ "a rewrite over more rows holds the lock longer: {large:?} vs {small:?}"
241
+ );
242
+ }
243
+
244
+ /// **The default policy does not rewrite on every tick.**
245
+ ///
246
+ /// The regression this whole file exists to prevent. With the shipped default,
247
+ /// a handful of acknowledged operations must not trigger a full log rewrite.
248
+ #[test]
249
+ fn the_default_policy_does_not_rewrite_on_a_handful_of_operations() {
250
+ assert_eq!(CompactionPolicy::default().rewrite_after_pruned, 1000);
251
+
252
+ let directory = TempDir::new().unwrap();
253
+ let path = directory.path().join("default.log");
254
+ let db = Arc::new(FeltDb::open(&path).unwrap());
255
+ assert_eq!(db.compaction_policy(), CompactionPolicy::default());
256
+
257
+ write_n(&db, 30);
258
+ acknowledge_all(&db);
259
+ let before = std::fs::metadata(&path).unwrap().len();
260
+
261
+ // Ten ticks of the timer.
262
+ for _ in 0..10 {
263
+ db.maybe_compact_operation_log(&["peer-1".to_string()])
264
+ .unwrap();
265
+ }
266
+
267
+ assert_eq!(
268
+ std::fs::metadata(&path).unwrap().len(),
269
+ before,
270
+ "ten timer ticks over a small database rewrote the log zero times"
271
+ );
272
+ }