@feltdb/core 0.8.5 → 0.8.7

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 (29) hide show
  1. package/dist/create/package-versions.js +1 -1
  2. package/dist/create/server-source/crates/feltdb/src/lib.rs +1173 -72
  3. package/dist/create/server-source/crates/feltdb/src/operation.rs +39 -0
  4. package/dist/create/server-source/crates/feltdb/src/state_model.rs +52 -0
  5. package/dist/create/server-source/crates/feltdb/src/storage.rs +9 -3
  6. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +132 -0
  7. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +96 -0
  8. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +272 -0
  9. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +467 -0
  10. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +26 -5
  11. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +445 -0
  12. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +518 -0
  13. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +191 -0
  14. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +278 -0
  15. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +1153 -0
  16. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +771 -0
  17. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +490 -157
  18. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +417 -0
  19. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +442 -0
  20. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +14 -0
  21. package/dist/create/server-source/crates/feltdb-server/src/main.rs +369 -41
  22. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +21 -0
  23. package/dist/studio-app/assets/{feltdb_wasm-DaNwCLRX.js → feltdb_wasm-C1VhI-U5.js} +1 -1
  24. package/dist/studio-app/assets/feltdb_wasm_bg-C8HXbAXb.wasm +0 -0
  25. package/dist/studio-app/assets/{index-j8IlhNqJ.js → index-Bbos1m2U.js} +1 -1
  26. package/dist/studio-app/index.html +1 -1
  27. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  28. package/package.json +1 -1
  29. package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
@@ -6883,7 +6883,8 @@ async fn application_openapi() -> Json<Value> {
6883
6883
  },
6884
6884
  "security": [{ "bearer": [] }],
6885
6885
  "paths": {
6886
- "/health": { "get": { "security": [], "responses": { "200": { "description": "Runtime health" } } } },
6886
+ "/health": { "get": { "security": [], "responses": { "200": { "description": "Process liveness" } } } },
6887
+ "/health/ready": { "get": { "security": [], "responses": { "200": { "description": "Storage and scoped-operation readiness" }, "503": { "description": "Not ready" } } } },
6887
6888
  "/application": { "get": { "responses": { "200": { "description": "Active application contract identity" } } } },
6888
6889
  "/schema": { "get": { "responses": { "200": { "description": "Active application schema" } } } },
6889
6890
  "/query": { "post": { "responses": { "200": { "description": "Bounded authorized query" } } } },
@@ -8091,6 +8092,8 @@ struct Config {
8091
8092
  peers: Vec<String>,
8092
8093
  peer_token_env: String,
8093
8094
  sync_interval: Duration,
8095
+ max_in_flight: usize,
8096
+ request_deadline: Duration,
8094
8097
  audit: PathBuf,
8095
8098
  allowed_origins: Vec<axum::http::HeaderValue>,
8096
8099
  worker_enabled: bool,
@@ -8119,6 +8122,11 @@ impl Config {
8119
8122
  let mut peers = Vec::new();
8120
8123
  let mut peer_token_env = "FELTDB_PEER_TOKEN".to_string();
8121
8124
  let mut sync_interval = Duration::from_secs(2);
8125
+ // Concurrency past this point does not buy throughput: the database
8126
+ // serializes every read and write on one lock. It only decides whether
8127
+ // an overload is shed quickly or queued invisibly.
8128
+ let mut max_in_flight = 64usize;
8129
+ let mut request_deadline = Duration::from_secs(10);
8122
8130
  let mut audit = None;
8123
8131
  let mut allowed_origins = std::env::var("FELTDB_ALLOWED_ORIGINS")
8124
8132
  .ok()
@@ -8165,6 +8173,23 @@ impl Config {
8165
8173
  }
8166
8174
  sync_interval = Duration::from_millis(milliseconds);
8167
8175
  }
8176
+ "--max-in-flight" => {
8177
+ max_in_flight = value()?
8178
+ .parse()
8179
+ .map_err(|_| "invalid --max-in-flight".to_string())?;
8180
+ if max_in_flight == 0 {
8181
+ return Err("--max-in-flight must be at least 1".to_string());
8182
+ }
8183
+ }
8184
+ "--request-deadline-ms" => {
8185
+ let milliseconds: u64 = value()?
8186
+ .parse()
8187
+ .map_err(|_| "invalid --request-deadline-ms".to_string())?;
8188
+ if milliseconds < 100 {
8189
+ return Err("--request-deadline-ms must be at least 100".to_string());
8190
+ }
8191
+ request_deadline = Duration::from_millis(milliseconds);
8192
+ }
8168
8193
  "--audit" => audit = Some(PathBuf::from(value()?)),
8169
8194
  "--allow-origin" => allowed_origins.push(
8170
8195
  value()?
@@ -8204,6 +8229,8 @@ impl Config {
8204
8229
  peers,
8205
8230
  peer_token_env,
8206
8231
  sync_interval,
8232
+ max_in_flight,
8233
+ request_deadline,
8207
8234
  audit,
8208
8235
  allowed_origins,
8209
8236
  worker_enabled,
@@ -8357,10 +8384,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
8357
8384
  let audit_handle = AuditLog::new(config.audit.clone());
8358
8385
  let state = AppState {
8359
8386
  started_at: Instant::now(),
8387
+ admission: Arc::new(tokio::sync::Semaphore::new(config.max_in_flight)),
8388
+ request_deadline: config.request_deadline,
8360
8389
  ids: Arc::new(AtomicU64::new(db.sequence()?)),
8361
8390
  db,
8362
8391
  namespace: Arc::from(config.namespace.clone()),
8363
8392
  auth_enabled: config.auth_enabled,
8393
+ // Argon2 is deliberately memory-hard. Bound concurrent legacy/session
8394
+ // verification so a burst waits asynchronously instead of exhausting
8395
+ // a small production VM with one allocation per request.
8396
+ authentication_workers: Arc::new(tokio::sync::Semaphore::new(2)),
8364
8397
  keys: KeyStore::load(&config.keys)?,
8365
8398
  metrics: Metrics::default(),
8366
8399
  cluster: ClusterStore::load(
@@ -9063,6 +9096,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9063
9096
 
9064
9097
  let mut app = Router::new()
9065
9098
  .route("/health", get(health))
9099
+ .route("/health/ready", get(health_ready))
9066
9100
  .route("/v1/health", get(health))
9067
9101
  .route("/v1/openapi.json", get(application_openapi))
9068
9102
  .route("/v1/auth/signup", axum::routing::post(auth_sign_up))
@@ -9086,6 +9120,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9086
9120
  )
9087
9121
  .merge(protected)
9088
9122
  .merge(query_execution_diagnostics_router())
9123
+ .layer(middleware::from_fn_with_state(state.clone(), admit_request))
9089
9124
  .layer(middleware::from_fn(protocol_version))
9090
9125
  .layer(middleware::from_fn(attribute_request))
9091
9126
  .layer(middleware::from_fn_with_state(state.clone(), count_request))
@@ -9171,16 +9206,52 @@ async fn authenticate(
9171
9206
  .headers()
9172
9207
  .get(AUTHORIZATION)
9173
9208
  .and_then(|header| header.to_str().ok())
9174
- .and_then(|header| header.strip_prefix("Bearer "));
9175
- let machine = token.and_then(|token| state.keys.authenticate(token, &state.namespace));
9209
+ .and_then(|header| header.strip_prefix("Bearer "))
9210
+ .map(str::to_owned);
9211
+ let keys = state.keys.clone();
9212
+ let identities = state.identities.clone();
9213
+ let namespace = state.namespace.clone();
9214
+ let authentication_worker = match state.authentication_workers.clone().acquire_owned().await
9215
+ {
9216
+ Ok(permit) => permit,
9217
+ Err(error) => {
9218
+ tracing::error!(%error, "authentication worker limit closed");
9219
+ return ApiError(
9220
+ StatusCode::SERVICE_UNAVAILABLE,
9221
+ "authentication temporarily unavailable".into(),
9222
+ )
9223
+ .into_response();
9224
+ }
9225
+ };
9226
+ let (machine, actor) = match tokio::task::spawn_blocking(move || {
9227
+ let _authentication_worker = authentication_worker;
9228
+ let machine = token
9229
+ .as_deref()
9230
+ .and_then(|token| keys.authenticate(token, &namespace));
9231
+ let actor = if machine.is_none() {
9232
+ token
9233
+ .as_deref()
9234
+ .and_then(|token| identities.authenticate_session(token))
9235
+ } else {
9236
+ None
9237
+ };
9238
+ (machine, actor)
9239
+ })
9240
+ .await
9241
+ {
9242
+ Ok(authentication) => authentication,
9243
+ Err(error) => {
9244
+ tracing::error!(%error, "bearer authentication worker failed");
9245
+ return ApiError(
9246
+ StatusCode::SERVICE_UNAVAILABLE,
9247
+ "authentication temporarily unavailable".into(),
9248
+ )
9249
+ .into_response();
9250
+ }
9251
+ };
9176
9252
  // A valid service key is already authoritative. Do not feed that
9177
9253
  // high-entropy bearer token through the Argon2-backed human-session
9178
9254
  // verifier as well; doing so blocks the request worker needlessly.
9179
- let actor = if machine.is_none() {
9180
- token.and_then(|token| state.identities.authenticate_session(token))
9181
- } else {
9182
- None
9183
- };
9184
9255
  let human = request
9185
9256
  .headers()
9186
9257
  .get(COOKIE)
@@ -9431,6 +9502,234 @@ async fn attribute_request(request: Request<axum::body::Body>, next: Next) -> Re
9431
9502
  next.run(request).await
9432
9503
  }
9433
9504
 
9505
+ /// The overall status, from the conditions that actually bear on it.
9506
+ ///
9507
+ /// Storage participates. Before it did not, so a database that had discarded an
9508
+ /// incomplete final append reported exactly what a cleanly replayed one did —
9509
+ /// which is the difference between an operator learning their last write was
9510
+ /// lost and never finding out.
9511
+ fn overall_status(
9512
+ membership_healthy: bool,
9513
+ clock_healthy: bool,
9514
+ storage: &feltdb::StorageHealth,
9515
+ ) -> &'static str {
9516
+ if membership_healthy && clock_healthy && storage.is_clean() {
9517
+ "healthy"
9518
+ } else {
9519
+ "degraded"
9520
+ }
9521
+ }
9522
+
9523
+ #[cfg(test)]
9524
+ mod admission_tests {
9525
+ use super::overloaded;
9526
+ use axum::http::StatusCode;
9527
+ use axum::response::IntoResponse;
9528
+
9529
+ /// An overload is refused with a status and a hint, not a hang.
9530
+ ///
9531
+ /// The managed incident produced client-side timeouts and upstream 502s
9532
+ /// because the server had no inbound deadline and no admission bound: a
9533
+ /// request that could not proceed simply waited. A caller cannot act on
9534
+ /// that. It can act on this.
9535
+ #[test]
9536
+ fn an_overload_is_an_explicit_refusal_with_a_retry_hint() {
9537
+ let response = overloaded("server is at its in-flight request limit").into_response();
9538
+ assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
9539
+ assert_eq!(
9540
+ response
9541
+ .headers()
9542
+ .get(axum::http::header::RETRY_AFTER)
9543
+ .and_then(|value| value.to_str().ok()),
9544
+ Some("1"),
9545
+ "a caller is told when to come back"
9546
+ );
9547
+ }
9548
+
9549
+ /// 503 rather than 500: the request was refused, not mishandled.
9550
+ #[test]
9551
+ fn an_overload_is_not_reported_as_a_server_fault() {
9552
+ let response = overloaded("request exceeded the server deadline").into_response();
9553
+ assert_ne!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
9554
+ assert!(response.status().is_server_error());
9555
+ }
9556
+ }
9557
+
9558
+ #[cfg(test)]
9559
+ mod health_status_tests {
9560
+ use super::overall_status;
9561
+ use feltdb::StorageHealth;
9562
+
9563
+ const RECOVERED: StorageHealth = StorageHealth::RecoveredIncompleteWrite {
9564
+ byte_offset: 128,
9565
+ discarded_bytes: 40,
9566
+ };
9567
+
9568
+ /// Storage participates in the overall status.
9569
+ ///
9570
+ /// It did not before: a database that had discarded an incomplete final
9571
+ /// append reported exactly what a cleanly replayed one did, so the
9572
+ /// distinction the durable-log work established never reached an operator.
9573
+ #[test]
9574
+ fn a_recovered_database_is_reported_degraded() {
9575
+ assert_eq!(overall_status(true, true, &StorageHealth::Clean), "healthy");
9576
+ assert_eq!(overall_status(true, true, &RECOVERED), "degraded");
9577
+ }
9578
+
9579
+ /// And the conditions that already counted still do, so this narrows
9580
+ /// nothing.
9581
+ #[test]
9582
+ fn membership_and_clock_still_count() {
9583
+ assert_eq!(
9584
+ overall_status(false, true, &StorageHealth::Clean),
9585
+ "degraded"
9586
+ );
9587
+ assert_eq!(
9588
+ overall_status(true, false, &StorageHealth::Clean),
9589
+ "degraded"
9590
+ );
9591
+ assert_eq!(overall_status(false, false, &RECOVERED), "degraded");
9592
+ }
9593
+
9594
+ /// The reported storage value names an observation rather than asserting
9595
+ /// the property that is still unproven.
9596
+ #[test]
9597
+ fn the_storage_label_is_an_observation_not_a_promise() {
9598
+ assert_eq!(StorageHealth::Clean.label(), "clean");
9599
+ assert_eq!(RECOVERED.label(), "recovered-incomplete-write");
9600
+ for label in [StorageHealth::Clean.label(), RECOVERED.label()] {
9601
+ assert_ne!(label, "durable", "health must not claim durability");
9602
+ }
9603
+ }
9604
+ }
9605
+
9606
+ /// Shed load at the door, and abandon work the caller is no longer waiting for.
9607
+ ///
9608
+ /// Two failures this replaces, both observed in production:
9609
+ ///
9610
+ /// - **No inbound deadline.** A slow operation ran until the *client* gave up,
9611
+ /// and then kept running, so the load that caused the stall outlived every
9612
+ /// request that reported it.
9613
+ /// - **No admission bound.** Requests queued on the database's single lock
9614
+ /// until an upstream proxy returned 502. Concurrency past the bound buys no
9615
+ /// throughput — every read and write serializes on that lock — so all it
9616
+ /// decides is whether an overload is refused quickly or hidden.
9617
+ ///
9618
+ /// Both now produce an explicit `503` with `Retry-After`, which is a signal a
9619
+ /// caller can act on rather than a timeout it has to interpret.
9620
+ async fn admit_request(
9621
+ State(state): State<AppState>,
9622
+ request: axum::extract::Request,
9623
+ next: Next,
9624
+ ) -> axum::response::Response {
9625
+ let Ok(_permit) = state.admission.clone().try_acquire_owned() else {
9626
+ state.metrics.request_shed();
9627
+ return overloaded("server is at its in-flight request limit");
9628
+ };
9629
+
9630
+ match tokio::time::timeout(state.request_deadline, next.run(request)).await {
9631
+ Ok(response) => response,
9632
+ Err(_) => {
9633
+ state.metrics.request_deadline_exceeded();
9634
+ overloaded("request exceeded the server deadline")
9635
+ }
9636
+ }
9637
+ }
9638
+
9639
+ /// A refusal a caller can act on: an explicit status, a stable code, and a hint.
9640
+ fn overloaded(detail: &str) -> axum::response::Response {
9641
+ let mut response = (
9642
+ StatusCode::SERVICE_UNAVAILABLE,
9643
+ Json(json!({
9644
+ "error": detail,
9645
+ "code": "SERVICE_OVERLOADED",
9646
+ })),
9647
+ )
9648
+ .into_response();
9649
+ response.headers_mut().insert(
9650
+ axum::http::header::RETRY_AFTER,
9651
+ axum::http::HeaderValue::from_static("1"),
9652
+ );
9653
+ response
9654
+ }
9655
+
9656
+ /// Readiness: whether this server can actually serve a scoped operation.
9657
+ ///
9658
+ /// `/health` answers "is this process alive". It reads cluster state and the
9659
+ /// lease clock and never touches storage, so it cannot fail for a database
9660
+ /// reason — which is exactly how a deployment reported `200 healthy` while
9661
+ /// every scoped read timed out.
9662
+ ///
9663
+ /// This endpoint takes the database lock and performs a real bounded read, off
9664
+ /// the runtime and under the same deadline a request gets. **A server whose
9665
+ /// scoped operations are stalled reports `503` here.**
9666
+ async fn health_ready(State(state): State<AppState>) -> axum::response::Response {
9667
+ let probe = state.clone();
9668
+ let started = Instant::now();
9669
+ let outcome = tokio::time::timeout(
9670
+ state.request_deadline,
9671
+ tokio::task::spawn_blocking(move || {
9672
+ // A real scoped operation: acquire the state lock and read through
9673
+ // it. Bounded, so a large database does not make readiness slow.
9674
+ let health = probe.db.health();
9675
+ let _ = probe
9676
+ .db
9677
+ .list_collection_page("_flow_capabilities", None, 1)?;
9678
+ Ok::<_, feltdb::FlowError>(health)
9679
+ }),
9680
+ )
9681
+ .await;
9682
+
9683
+ let waited_ms = started.elapsed().as_millis() as u64;
9684
+ match outcome {
9685
+ Ok(Ok(Ok(health))) => {
9686
+ let ready = health.is_nominal();
9687
+ let body = Json(json!({
9688
+ "ready": ready,
9689
+ "storage": health.storage.label(),
9690
+ "durable_format": health.durable_format.to_string(),
9691
+ "probe_ms": waited_ms,
9692
+ }));
9693
+ if ready {
9694
+ (StatusCode::OK, body).into_response()
9695
+ } else {
9696
+ // Usable, but not in the condition a clean open produces.
9697
+ (StatusCode::SERVICE_UNAVAILABLE, body).into_response()
9698
+ }
9699
+ }
9700
+ Ok(Ok(Err(error))) => (
9701
+ StatusCode::SERVICE_UNAVAILABLE,
9702
+ Json(json!({
9703
+ "ready": false,
9704
+ "error": error.to_string(),
9705
+ "code": "STORAGE_UNAVAILABLE",
9706
+ "probe_ms": waited_ms,
9707
+ })),
9708
+ )
9709
+ .into_response(),
9710
+ Ok(Err(error)) => (
9711
+ StatusCode::SERVICE_UNAVAILABLE,
9712
+ Json(json!({
9713
+ "ready": false,
9714
+ "error": error.to_string(),
9715
+ "code": "STORAGE_PROBE_FAILED",
9716
+ "probe_ms": waited_ms,
9717
+ })),
9718
+ )
9719
+ .into_response(),
9720
+ Err(_) => (
9721
+ StatusCode::SERVICE_UNAVAILABLE,
9722
+ Json(json!({
9723
+ "ready": false,
9724
+ "error": "storage probe exceeded the server deadline",
9725
+ "code": "STORAGE_STALLED",
9726
+ "probe_ms": waited_ms,
9727
+ })),
9728
+ )
9729
+ .into_response(),
9730
+ }
9731
+ }
9732
+
9434
9733
  async fn health(State(state): State<AppState>) -> Json<HealthResponse<'static>> {
9435
9734
  let membership = match state.cluster.proposal().map(|value| value.phase) {
9436
9735
  Some(ProposalPhase::Preparing | ProposalPhase::Prepared) => "recovering",
@@ -9438,16 +9737,16 @@ async fn health(State(state): State<AppState>) -> Json<HealthResponse<'static>>
9438
9737
  _ => "healthy",
9439
9738
  };
9440
9739
  let clock = state.lease_clock.is_healthy();
9740
+ // Observed, not asserted. An incompatible or corrupt database never opens,
9741
+ // so a running server has already established its format; what remains to
9742
+ // report is whether replay discarded anything.
9743
+ let database = state.db.health();
9441
9744
  Json(HealthResponse {
9442
- status: if membership == "healthy" && clock {
9443
- "healthy"
9444
- } else {
9445
- "degraded"
9446
- },
9745
+ status: overall_status(membership == "healthy", clock, &database.storage),
9447
9746
  version: env!("CARGO_PKG_VERSION"),
9448
9747
  git_commit: option_env!("FELTDB_GIT_COMMIT").unwrap_or("unknown"),
9449
9748
  runtime: "self-hosted",
9450
- storage: "durable",
9749
+ storage: database.storage.label(),
9451
9750
  fabric: "healthy",
9452
9751
  execution: if state.worker_enabled {
9453
9752
  "autonomous"
@@ -9488,22 +9787,33 @@ async fn managed_diagnostics(State(state): State<AppState>) -> Json<ManagedDiagn
9488
9787
  }
9489
9788
 
9490
9789
  async fn readiness(State(state): State<AppState>) -> Result<Json<Value>, ApiError> {
9491
- if let Some(parent) = state.readiness_probe.parent() {
9492
- std::fs::create_dir_all(parent)
9790
+ let readiness_probe = state.readiness_probe.clone();
9791
+ let db = state.db.clone();
9792
+ let (state_version, storage) = tokio::task::spawn_blocking(move || {
9793
+ if let Some(parent) = readiness_probe.parent() {
9794
+ std::fs::create_dir_all(parent)
9795
+ .map_err(|error| ApiError(StatusCode::SERVICE_UNAVAILABLE, error.to_string()))?;
9796
+ }
9797
+ let mut probe = OpenOptions::new()
9798
+ .create(true)
9799
+ .write(true)
9800
+ .truncate(true)
9801
+ .open(readiness_probe.as_ref())
9493
9802
  .map_err(|error| ApiError(StatusCode::SERVICE_UNAVAILABLE, error.to_string()))?;
9494
- }
9495
- let mut probe = OpenOptions::new()
9496
- .create(true)
9497
- .write(true)
9498
- .truncate(true)
9499
- .open(state.readiness_probe.as_ref())
9500
- .map_err(|error| ApiError(StatusCode::SERVICE_UNAVAILABLE, error.to_string()))?;
9501
- probe
9502
- .write_all(b"feltdb-ready-v1\n")
9503
- .and_then(|_| probe.sync_all())
9504
- .map_err(|error| ApiError(StatusCode::SERVICE_UNAVAILABLE, error.to_string()))?;
9505
- let state_version = state.db.sequence()?;
9506
- let storage = state.db.instance_id()?;
9803
+ probe
9804
+ .write_all(b"feltdb-ready-v1\n")
9805
+ .and_then(|_| probe.sync_all())
9806
+ .map_err(|error| ApiError(StatusCode::SERVICE_UNAVAILABLE, error.to_string()))?;
9807
+ Ok::<_, ApiError>((db.sequence()?, db.instance_id()?))
9808
+ })
9809
+ .await
9810
+ .map_err(|error| {
9811
+ tracing::error!(%error, "readiness worker failed");
9812
+ ApiError(
9813
+ StatusCode::SERVICE_UNAVAILABLE,
9814
+ "readiness check temporarily unavailable".into(),
9815
+ )
9816
+ })??;
9507
9817
  Ok(Json(json!({
9508
9818
  "status": "ready",
9509
9819
  "authority": "rust",
@@ -10072,9 +10382,25 @@ fn start_peer_sessions(
10072
10382
  }
10073
10383
  }
10074
10384
  }
10075
- match state.db.compact_operation_log(&peers) {
10076
- Ok(removed) => state.metrics.operations_compacted(removed as u64),
10077
- Err(error) => tracing::warn!(%error, "operation compaction failed"),
10385
+ // Policy-driven, and off the runtime. Compaction rewrites the log
10386
+ // while holding the lock that serializes every read and write, so
10387
+ // running it on every tick stopped the world on every tick — and
10388
+ // running it inline blocked a runtime worker while it did.
10389
+ let compactor = state.clone();
10390
+ let compaction_peers = peers.clone();
10391
+ match tokio::task::spawn_blocking(move || {
10392
+ compactor.db.maybe_compact_operation_log(&compaction_peers)
10393
+ })
10394
+ .await
10395
+ {
10396
+ Ok(Ok(outcome)) => {
10397
+ state.metrics.operations_compacted(outcome.pruned() as u64);
10398
+ if outcome.rewrote_log() {
10399
+ tracing::debug!(pruned = outcome.pruned(), "durable log rewritten");
10400
+ }
10401
+ }
10402
+ Ok(Err(error)) => tracing::warn!(%error, "operation compaction failed"),
10403
+ Err(error) => tracing::warn!(%error, "operation compaction task failed"),
10078
10404
  }
10079
10405
  tokio::time::sleep(interval).await;
10080
10406
  }
@@ -10395,10 +10721,10 @@ fn execute_builtin_capability(
10395
10721
  Ok(Value::Array(
10396
10722
  state
10397
10723
  .db
10398
- .list_collection(collection)?
10724
+ .query_collection(collection, Some(limit), |row| {
10725
+ row.value.to_string().to_lowercase().contains(&needle)
10726
+ })?
10399
10727
  .into_iter()
10400
- .filter(|row| row.value.to_string().to_lowercase().contains(&needle))
10401
- .take(limit)
10402
10728
  .map(|row| row.value)
10403
10729
  .collect(),
10404
10730
  ))
@@ -11439,11 +11765,11 @@ async fn get_provenance(
11439
11765
  Path((collection, id)): Path<(String, String)>,
11440
11766
  ) -> Result<Json<Value>, ApiError> {
11441
11767
  let key = record_key(&collection, &id)?;
11768
+ // A keyed lookup. This used to materialize the whole collection — cloning
11769
+ // every row under the global state lock — to find one record.
11442
11770
  let row = state
11443
11771
  .db
11444
- .list_collection(&collection)?
11445
- .into_iter()
11446
- .find(|row| row.key == key)
11772
+ .get_collection_record(&collection, &key)?
11447
11773
  .ok_or_else(|| ApiError(StatusCode::NOT_FOUND, "record not found".to_string()))?;
11448
11774
  let operation = row.operation.as_ref();
11449
11775
  Ok(Json(json!({
@@ -11473,12 +11799,14 @@ async fn search_collection(
11473
11799
  ));
11474
11800
  }
11475
11801
  let needle = request.query.to_lowercase();
11802
+ // Borrows each row and stops at the limit, rather than cloning the whole
11803
+ // collection and filtering afterwards. Only matches are cloned.
11476
11804
  let records = state
11477
11805
  .db
11478
- .list_collection(&collection)?
11806
+ .query_collection(&collection, Some(request.limit), |row| {
11807
+ row.value.to_string().to_lowercase().contains(&needle)
11808
+ })?
11479
11809
  .into_iter()
11480
- .filter(|row| row.value.to_string().to_lowercase().contains(&needle))
11481
- .take(request.limit)
11482
11810
  .map(record_response)
11483
11811
  .collect();
11484
11812
  Ok(Json(records))
@@ -21,6 +21,8 @@ struct Inner {
21
21
  peer_sync_successes: AtomicU64,
22
22
  peer_sync_failures: AtomicU64,
23
23
  operations_compacted: AtomicU64,
24
+ requests_shed: AtomicU64,
25
+ request_deadlines_exceeded: AtomicU64,
24
26
  }
25
27
 
26
28
  #[derive(Serialize)]
@@ -35,6 +37,12 @@ pub struct MetricsSnapshot {
35
37
  pub peer_sync_successes: u64,
36
38
  pub peer_sync_failures: u64,
37
39
  pub operations_compacted: u64,
40
+ /// Requests refused at the door because the server was already at its
41
+ /// in-flight limit. Non-zero means load is being shed rather than queued.
42
+ pub requests_shed: u64,
43
+ /// Requests the server abandoned for exceeding its own deadline. Non-zero
44
+ /// means scoped operations are stalling, whatever `/health` says.
45
+ pub request_deadlines_exceeded: u64,
38
46
  }
39
47
 
40
48
  impl Metrics {
@@ -65,6 +73,14 @@ impl Metrics {
65
73
  .sync_operations_received
66
74
  .fetch_add(count, Ordering::Relaxed);
67
75
  }
76
+ pub fn request_shed(&self) {
77
+ self.inner.requests_shed.fetch_add(1, Ordering::Relaxed);
78
+ }
79
+ pub fn request_deadline_exceeded(&self) {
80
+ self.inner
81
+ .request_deadlines_exceeded
82
+ .fetch_add(1, Ordering::Relaxed);
83
+ }
68
84
  pub fn peer_sync_success(&self) {
69
85
  self.inner
70
86
  .peer_sync_successes
@@ -92,6 +108,11 @@ impl Metrics {
92
108
  peer_sync_successes: self.inner.peer_sync_successes.load(Ordering::Relaxed),
93
109
  peer_sync_failures: self.inner.peer_sync_failures.load(Ordering::Relaxed),
94
110
  operations_compacted: self.inner.operations_compacted.load(Ordering::Relaxed),
111
+ requests_shed: self.inner.requests_shed.load(Ordering::Relaxed),
112
+ request_deadlines_exceeded: self
113
+ .inner
114
+ .request_deadlines_exceeded
115
+ .load(Ordering::Relaxed),
95
116
  }
96
117
  }
97
118
  }