tidaldb/tidal-stress/k8s/stress-job.yaml
jx12n f640764d89 feat(tidal-stress): open-loop capacity load generator (thepeach feed workload)
New workspace crate: an open-loop, coordinated-omission-corrected HTTP load
generator + capacity ramp for the standalone and multi-process cluster surfaces,
modeling a thepeach feed session (feed reads + view/like/skip signals + search,
signal-dominated per their user-graph spec). Throttleable target rate, ramp
presets (smoke/quick/peach-100k/max) or rps:secs specs, peach/reads/writes/custom
mixes, leader vs sharded write paths, per-op p50/p90/p99/p999/max latency, a
backpressure-aware status breakdown (429/408/503/4xx/5xx/transport), and a verdict
translated to supported DAU. Runs in-cluster as a k8s Job (tidal-stress/k8s/).

Open-loop scheduler (scheduler.rs) fires at a fixed arrival rate and measures
latency from each request's intended send time, so a server stall inflates the
percentiles a closed-loop test hides; it shed-and-counts rather than blocking when
the in-flight cap is reached. Pure-Rust (tokio + reqwest/rustls), no engine deps.

Findings on the live 3-region k3s cluster (docs/ops/stress-test-thepeach.md):
reads scale to thousands/s at <15ms p99; the replicated /signals path saturates at
~90 signals/s (single-leader funnel + 2-worker write pool + synchronous gRPC ship);
the sharded path sustains 3,669 signals/s at 0 errors and ~27% cluster CPU (≈ the
100k-DAU peak, knee not reached). Overload degrades gracefully (429; 0 pod
restarts). thepeach's planned in-process embedding sidesteps all of it (write ≈82ns).
2026-06-10 21:54:21 -06:00

99 lines
3.5 KiB
YAML

# In-cluster capacity ramp for the tidalDB multi-process cluster.
#
# Runs the load generator AS A POD so requests take the real cluster network path
# (a `kubectl port-forward` serializes everything through the API server and adds
# tens of ms — useless for capacity numbers). Targets the three region ClusterIPs
# directly; reads round-robin across them (each serves locally), leader-path
# writes are pinned to us-east (10.43.99.11) to measure the single-leader funnel.
#
# Apply: kubectl apply -f tidal-stress/k8s/stress-job.yaml
# Watch: kubectl logs -f job/tidal-stress -n tidaldb
# Re-run: kubectl delete job tidal-stress -n tidaldb; kubectl apply -f ...
#
# To compare the horizontally-scaled path, change `--write-path leader` to
# `--write-path sharded` (hash-partitions writes across all 3 regions, no funnel).
apiVersion: batch/v1
kind: Job
metadata:
name: tidal-stress
namespace: tidaldb
labels:
app.kubernetes.io/name: tidal-stress
app.kubernetes.io/part-of: tidaldb
spec:
backoffLimit: 0 # a load run is not retried — read the logs
ttlSecondsAfterFinished: 7200
template:
metadata:
labels:
app.kubernetes.io/name: tidal-stress
app.kubernetes.io/part-of: tidaldb
spec:
restartPolicy: Never
automountServiceAccountToken: false
affinity:
podAntiAffinity:
# Keep the generator OFF the leader's node so it never steals CPU from
# the write bottleneck we are measuring.
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
tidaldb.region: us-east
topologyKey: kubernetes.io/hostname
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: stress
image: registry.threesix.ai/tidal/stress@sha256:77395cc2857e2adabaa37607b198316d6f1cd0ec04f749caf2de959e6bade55b # :v1
imagePullPolicy: IfNotPresent
args:
- --target
- http://10.43.99.11:9500 # us-east (leader)
- --target
- http://10.43.99.12:9500 # eu-west
- --target
- http://10.43.99.13:9500 # ap-south
- --leader-url
- http://10.43.99.11:9500
- --ramp
- peach-100k
- --stage-secs
- "45"
- --mix
- peach
- --write-path
- leader
- --corpus
- "20000"
- --users
- "100000"
- --poll-status
env:
- name: TIDAL_API_KEY
valueFrom:
secretKeyRef:
name: tidaldb-credentials
key: TIDAL_API_KEY
- name: TIDAL_STRESS_LOG
value: warn
resources:
# Small REQUEST (the cluster is request-saturated though ~15% utilised)
# with a high LIMIT: the generator bursts to the cycles it needs on the
# idle node. If it ever CPU-saturates, the report's schedule-lag /
# client-shed will say so — then split the load across multiple Jobs.
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "3"
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]