tidaldb/tidal-stress
jordan c97aaa8e5b fleet remediation: make the workspace gate runnable, then fix what it caught
`cargo test --workspace` could not run at all: dependency resolution failed with
"aws-types@1.3.16 requires rustc 1.91.1" on the 1.91.0 default toolchain, so the
gate the project documents was dead. Making it run exposed a compile break and
two wrong tests that had been invisible for months. Now green end to end:
143 suites, 3155 tests, exit 0.

Toolchain
- rust-toolchain.toml pins the DEV toolchain to 1.91.1. The published MSRV stays
  `rust-version = "1.91"` (the engine builds on 1.91.0); only tidalctl's AWS SDK
  chain needs the patch release, and it now declares that itself.

Consumer crates migrated to the current engine API (clean cutover)
- iknowyou-engine: `AgentPolicy` gained five m10 read/profile-override fields;
  the literal now spreads `..AgentPolicy::default()` as the engine's own doc
  example does, so future fields do not break it again.
- forage-engine: `RetrieveResult` gained p1 `reasons`. The app builds its own
  candidate pool, so it now tags what it knows: PreferenceMatch for the
  preference-vector blend, SemanticMatch (with the seed item) for
  similar-to-saved, ExplorationBudget for pinned discoveries.
- forage-engine: `url_to_item_id` folded into the u32 item universe. The engine
  narrows item IDs to a u32 slot in durable per-user state and rejects anything
  above u32::MAX rather than alias two items forever, so every add_item with a
  64-bit FNV hash failed. 9 of 28 smoke tests were failing on this alone.
- forage-engine: bridge items read the top-2 preference CLUSTERS via
  `query_vectors`, not the single centroid from `preference_vectors().get()`.
  Since m12 that accessor returns only the strongest cluster, so a tech+jazz user
  whose interests split into two clusters looked single-interest and never
  bridged. Falls back to top-2 dimensions when a user has one cluster.

Reconcile tests corrected to the shipped contract
- tidal/tests/m8p3_reconcile_production.rs asserted `3 + 5 == 8` for a windowed
  count after heal. `take_crdt_snapshot` deliberately keys signal contributions
  to ONE canonical contributor (ShardId::SINGLE) because signals are relayed from
  a single writer, so per-node attribution double-counted every replicated event
  on every reconcile. Merge is therefore LWW on (last_update_ns, score) plus
  PN-counter per-node max: nodes converge on the more complete accumulator. The
  old expectation was asserting the bug that fix removed.
- Rewrote to assert convergence, count survival (not 0), and no inflation, and
  added `repeated_reconcile_of_converged_nodes_does_not_creep` - the regression
  guard for the creep itself, which nothing covered.

Pre-commit hook unified
- hooks/pre-commit dropped `-D warnings`: each crate's `[lints]` table is the
  source of truth (`clippy::all`/`unwrap_used` deny, `pedantic` warn), and the
  flag promoted ~58 deliberate pedantic warnings in integration tests to errors,
  making every Rust commit impossible.
- It now lints all five tidal crates instead of path-matching `tidal/`, which
  silently skipped tidal-server, tidal-net, tidal-stress, tidalctl and
  applications/ - the rot above lived in exactly those crates. Ported the
  CODING_GUIDELINES file-length, println, and unsafe-SAFETY checks from the
  divergent untracked copy that this replaces.
- CONTRIBUTING.md now documents the real commands and the toolchain/MSRV split.

Fleet recovery and soak
- scripts/restore-fleet.sh: the fail-closed selective restore, promoted out of an
  ignored tmp/ directory into the repository. Preflights retained storage,
  digest-pinned images, parked state, and aggregate plus per-PV-node scheduler
  headroom before the first scale; writes a durable transcript under
  tmp/restore-logs/ with structured start/error/rollback/complete events.
- k8s manifests park the standalone store, the RF3 cluster, and the soak monitor
  at zero replicas with restore-fleet.sh as the only supported scale-up path.
- soak-eval/soak-watch and the nightly CronJob fail closed on stale or missing
  restart evidence instead of silently skipping the restart-aware half of the gate.
- docs/ops/capacity-planning.md corrects the RAM envelope to the real hot-tier
  formula and separates analytic totals from the measured process envelope.
2026-08-16 12:38:14 -06:00
..
benches feat(m12): vector retrieval G1/G2 — recall harness, ANN in RETRIEVE, index tuning 2026-06-14 11:07:09 -06:00
k8s fleet remediation: make the workspace gate runnable, then fix what it caught 2026-08-16 12:38:14 -06:00
scripts feat(k8s): m11p5 cluster manifest — local-path PVCs, initContainer, T3 tooling 2026-06-12 22:01:37 -06:00
src fleet remediation: make the workspace gate runnable, then fix what it caught 2026-08-16 12:38:14 -06:00
Cargo.toml fleet remediation: make the workspace gate runnable, then fix what it caught 2026-08-16 12:38:14 -06:00
PROCESS.md docs(tidal-stress): README + worklog + continue-the-process flow 2026-06-13 09:23:29 -06:00
README.md docs(tidal-stress): README + worklog + continue-the-process flow 2026-06-13 09:23:29 -06:00
WORKLOG.md docs(tidal-stress): README + worklog + continue-the-process flow 2026-06-13 09:23:29 -06:00

tidal-stress

Open-loop capacity ramp + chaos harness for the tidalDB cluster. Drives the thepeach feed workload (signals + vector embeddings) against a live cluster and reports a per-stage capacity verdict.

  • Worklog (what's been run, what we learned): WORKLOG.md
  • Process (how to run the next checkpoint): PROCESS.md

Current target cluster (as of 2026-06-13)

The cluster moved to the m11p5 single-StatefulSet architecture. The old 3-StatefulSet / static-ClusterIP model (namespace tidaldb, IPs 10.43.99.11-13) is retired — any manifest or doc still naming those IPs is stale.

Fact Value
Namespace tidaldb-cluster
Pods tidaldb-{0,1,2} (one StatefulSet, 3 replicas)
Peer DNS tidaldb-N.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9500 (HTTP), :9601 (gRPC)
Client VIP tidaldb.tidaldb-cluster.svc.cluster.local:9500 (readiness-gated)
Server image registry.threesix.ai/tidal/server@sha256:173e803… (:m11p5)
Stress image registry.threesix.ai/tidal/stress@sha256:3a75c311… (:m11p3)
Storage local-path 5Gi/pod (on-node NVMe) — NOT Longhorn (see WORKLOG)
CPU/pod limit 2 (the write pool is ~2 workers on the leader)

Targets for any new Job manifest — use pod DNS for --target (so status-polling reaches survivors during a kill window) and the VIP for --leader-url:

args:
  - --target
  - http://tidaldb-0.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9500
  - --target
  - http://tidaldb-1.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9500
  - --target
  - http://tidaldb-2.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9500
  - --leader-url
  - http://tidaldb.tidaldb-cluster.svc.cluster.local:9500

Run pattern

Every run deploys the generator as an in-cluster Job (port-forward adds API-server serialization latency — never use it for capacity numbers; only the kill loop port-forwards, and only to read /cluster/status).

export KUBECONFIG=~/.kube/orchard9-k3sf.yaml
kubectl apply -f k8s/<job>.yaml
kubectl logs -f job/<job-name> -n tidaldb-cluster
kubectl delete job <job-name> -n tidaldb-cluster    # re-arm before re-running

CLI flags (authoritative — from src/main.rs)

Flag Default Notes
--target <url> (required, repeatable) Region gateway; reads round-robin across all
--leader-url <url> none Pin leader-path writes here to skip the forward hop
--api-key $TIDAL_API_KEY Bearer; the cluster requires it
--ack <leader|quorum> topology default Sent as x-tidal-ack per write
--ramp <preset|rps:secs,…> peach-100k Presets: smoke, quick, peach-100k, max
--stage-secs <n> 45 Hold per preset stage; 300600 for soak
--mix <preset|op=w,…> peach Presets: peach, reads, writes. Ops: feed,search,view,like,skip,item,embed
--write-path <leader|sharded> leader sharded removes the single-leader funnel (not replicated)
--corpus <n> 10000 Items+embeddings to seed; 20k for gate runs
--users <n> 50000 Virtual user id space
--skip-seed false Set after the first run of a session (corpus persists on PVC)
--embedding-dim <n> 128 Deployed schema = 128; thepeach real = 1536
--hot-skew <f> 1.3 Power-law concentration onto hot items
--poll-status false Poll /cluster/status between stages for lag — always set when measuring lag
--stop-on-knee false Stop at first SLO-breaching stage
--dau <n> 100000 DAU the verdict translates the ceiling against

SLO: feed p99 ≤ 150ms (network-hop allowance over the in-process 50ms SLA); error rate ≥ 1% (429/408/503/5xx/transport) = the knee.

Layout

src/            generator (scheduler, workload model, client, metrics)
k8s/            Job manifests — one per checkpoint
  stress-job.yaml       generic ramp
  stress-job-t2a.yaml   T2-A quorum throughput
  stress-job-t2b.yaml   T2-B acked-loss under kills
  stress-job-t3.yaml    T3 automatic-failover gate
scripts/
  t3-kill-loop-v3.sh    HTTP-polling leader-kill loop (no exec into pods)

Checkpoint status

ID Gate Status
T0 baseline (~90/s replicated, 3669/s sharded) ✓ done
T2-A ≥1000 quorum writes/s ✓ 2980/s
T2-B 0 acked loss across kills
T3 leader-kill failover <10s p99 ×10 ✓ max 6157ms (m11p5)
T4 scale 3→5→3 under load, joiner ≤5min next
T-read vector-search recall@k + query QPS/p99 not built (see PROCESS)
T5 sharded ≥5000 quorum writes/s blocked on p6