tidaldb/tidal-stress/PROCESS.md
jx12n 46741a3a8c docs(tidal-stress): README + worklog + continue-the-process flow
README.md — tool reference pinned to the m11p5 cluster (tidaldb-cluster ns,
pod-DNS targets, local-path storage); retires the stale 10.43.99.11-13 ClusterIPs.

WORKLOG.md — m11p5 migration + T3 gate (10/10 kills, max 6157ms), the three
run-costing traps (Longhorn storage, exec leader-detection, kubectl wait hang),
reseed behavior, and the vector-DB positioning note. Records a m11p5 finding:
a reseeded node stays NotReady forever on an idle cluster because the
first-converged readiness check needs observed replication traffic — flagged as
an upstream fix, not a manifest bandaid.

PROCESS.md — the repeatable five-beat checkpoint loop, the operational-traps
table, the T4 elasticity flow (now unblocked on m11p5, with the idle-readiness
watch-item), and the T-read vector-search gate that still needs a recall oracle
built before any read-path/vector-DB claim.
2026-06-13 09:23:29 -06:00

146 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# tidal-stress process — running the next checkpoint
How to take the cluster from "deployed" to "gated" without re-learning the
operational traps. Read [README.md](README.md) for the tool, [WORKLOG.md](WORKLOG.md)
for what's been run. This doc is the **repeatable flow**.
The legacy plan in `../tmp/stress-test-plan.md` is correct on *gates and intent*
but **stale on addressing** — it names the old 3-StatefulSet ClusterIPs
(`10.43.99.11-13`) and namespace `tidaldb`. Use the m11p5 DNS targets from the
README instead. Migrating that plan in place is step 0 below.
---
## The standard checkpoint loop
Every checkpoint, old or new, follows the same five beats:
1. **Pre-flight.** Context + cluster healthy before anything else.
```bash
export KUBECONFIG=~/.kube/orchard9-k3sf.yaml
kubectl config current-context # must be the k3s one
kubectl get pods -n tidaldb-cluster # all 3 → 1/1 Running, 0 restarts
kubectl port-forward -n tidaldb-cluster svc/tidaldb 19500:9500 &
curl -sf localhost:19500/cluster/status | jq . # leader set, every region lag=0
```
Do **not** start a run with any pod restarting or mid-reseed (`0/1 Running`).
A reseed cycle from a prior run takes ~120150s to settle — wait it out.
2. **Arm the Job.** Copy the nearest `k8s/stress-job-*.yaml`, set the m11p5 DNS
targets (README), pin `--ack quorum`, `--corpus 20000`, `--poll-status`.
For a fresh PVC the first run seeds; add `--skip-seed` on subsequent runs in
the same session.
3. **Run + chaos.** Apply the Job; if the checkpoint has a chaos arm (kill /
scale), drive it from a script that talks to the **VIP over a port-forward**,
never `kubectl exec` into a tidaldb pod (see traps below).
4. **Capture.** Paste the `CAPACITY VERDICT` block + `[pre/post-stage]` lag lines
into `docs/ops/stress-test-<phase>.md`. Record the one number the next
checkpoint must not regress.
5. **Record + re-arm.** Update `WORKLOG.md` (newest first) and the checkpoint
table in `README.md`. `kubectl delete job <name> -n tidaldb-cluster` to re-arm.
---
## Operational traps (each cost a run — do not relearn)
| Trap | Symptom | Rule |
|------|---------|------|
| **Longhorn storage** | 7485% quorum-write error rate | PVCs MUST be `local-path` (5Gi). Networked storage on the fsync commit path is fatal to quorum throughput. |
| **`kubectl exec` for leader detection** | `container not found`, bogus 1719s "elections" | Read leader via `curl <VIP>/cluster/status`, never exec into a tidaldb pod (it may be the one dying). |
| **`"none (election in progress)"`** | loop tries to delete a pod by that literal string | Filter leader name with `^tidaldb-[0-9]+$`; retry until a real name. |
| **`kubectl wait` across force-delete** | hangs past its own `--timeout` | Don't gate the loop on `kubectl wait`. Poll `kubectl get pod -o jsonpath` for `Ready=True`, or bound the wait and move on. |
| **Port-forward dies on VIP kill** | curl to `:19500` refuses mid-run | The VIP routed to the killed pod. Re-establish the port-forward before resuming; for failover timing, fall back to the Raft log timestamp. |
---
## Next: T4 — elasticity under load (unblocked on m11p5)
m11p5 added the membership + snapshot-install path T4 needs. **Gate:** scale
3→5→3 online under steady load; each joiner reaches `lag=0` within 5 min on a
100k-item corpus; p99 impact < 2× baseline for < 60s; zero acked loss.
m11p5 makes scale-up a plain `kubectl scale` pod ordinal 3 boots with
`--seed` (the headless Service resolves to a live peer), joins as a **learner**,
and **auto-promotes to a voter** once caught up (statefulset.yaml §ORD3 branch).
```bash
# 1. Seed a large corpus first (100k items so catch-up is non-trivial):
# one run with --corpus 100000 --ramp smoke, let it seed, then stop.
# 2. Start steady-state load (leader path, quorum), let it reach a stable stage.
kubectl apply -f k8s/stress-job.yaml # peach-100k or a pinned rps:secs
# 3. Scale up under load and watch the joiners catch up:
kubectl scale statefulset tidaldb -n tidaldb-cluster --replicas=5
watch 'curl -s localhost:19500/cluster/status | jq ".regions[] | {name, lag_events, partitioned}"'
# 4. Pass when tidaldb-3 and tidaldb-4 show lag_events=0 within 5 min,
# and the stress verdict shows p99 < 2× baseline for < 60s during the join.
# 5. Scale back down, then run a full ramp to confirm no residual impact:
kubectl scale statefulset tidaldb -n tidaldb-cluster --replicas=3
```
Watch for: a joiner on a fresh PVC will snapshot-install (not stream) for a 100k
corpus that's the path under test. Confirm it promotes learnervoter (the
region appears in `/cluster/status` as reachable, lag=0) before declaring pass.
**T4 watch-item — keep load running through the whole join window.** A reseeded
node's K8s readiness only flips when it *observes* replication traffic
(WORKLOG 2026-06-13 finding: an idle cluster left a fully-caught-up node `0/1`
for 11.5h). So T4 must hold write load continuously from before the scale-up
until after both joiners are Ready never let the cluster go idle mid-join, or
the readiness gate stalls on a node that is actually caught up. If you see a node
with `/cluster/status` lag=0 but pod `0/1`, that's this bug, not a catch-up
failure and it's an **upstream fix**, not a manifest workaround.
---
## Then: T-read — the vector-search gate (NOT YET BUILT)
This is the gap that blocks any honest "competes with vector DBs" claim. Every
number to date is write-path. The tool can already drive reads (`--mix reads`,
ops `feed`/`search`) and report **latency/throughput/error**, but it has **no
recall oracle** it cannot tell you whether the results are *correct*.
**What exists today** (latency/throughput only):
```bash
# Read-latency under load — runnable now, but only answers "how fast", not "how right":
# --mix reads --ramp peach-100k --skip-seed --poll-status
kubectl apply -f k8s/stress-job.yaml # with mix=reads
```
**What must be built** for a real gate (new work in `src/`):
1. **Ground-truth oracle.** For a sample of query vectors, compute exact
top-k by brute-force cosine over the seeded corpus (offline or a `--verify`
subcommand). Store as `(query_id → [item_id…])`.
2. **Recall@k metric.** During the read ramp, issue the same queries against the
cluster, compare returned ids to the oracle, emit **recall@10** alongside the
existing latency histogram.
3. **Query-QPS verdict.** Extend the capacity verdict with a read knee: QPS at
which query p99 breaches SLO *while recall@10 stays ≥ target*.
**Proposed gate:** recall@10 0.95 at X query QPS with query p99 50ms, on a
128-dim / 20k corpus then re-run at **1536-dim** (thepeach's real width;
~2.3× RAM/item per capacity-planning.md) since that's the production shape.
Until T-read exists, scope every performance claim to the **write path** and say
so explicitly.
---
## Checkpoint order from here
```
T3 (done) ─→ T4 elasticity ─→ T-read recall ─┐
├─→ T5 sharded throughput (needs p6)
build recall oracle first ───────┘
```
T4 is pure ops (no tool change) do it next. T-read needs `src/` work (the
oracle + recall metric) schedule that build before claiming any read number.
T5 stays blocked until p6 lands the shard groups.