`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.
144 lines
5.5 KiB
YAML
144 lines
5.5 KiB
YAML
# tidalDB standalone server as a StatefulSet.
|
|
#
|
|
# WHY A STATEFULSET (not a Deployment): tidalDB is single-node-first and
|
|
# embeddable — the server wraps ONE engine instance whose state (WAL +
|
|
# checkpoints + indexes) lives on a durable data dir. It scales VERTICALLY
|
|
# (bigger node), not by adding active replicas. The parked source state keeps
|
|
# `replicas: 0`; `scripts/restore-fleet.sh --standalone` restores cardinality 1.
|
|
# For HA, use the DIFFERENT multi-process `cluster` deployment: one process per
|
|
# region, quorum-acked writes, and automatic failover. It ships as its own
|
|
# StatefulSet under k8s/cluster/ and does not change this manifest.
|
|
# See docs/runbooks/kubernetes.md and docs/runbooks/cluster.md.
|
|
apiVersion: apps/v1
|
|
kind: StatefulSet
|
|
metadata:
|
|
name: tidaldb
|
|
namespace: tidaldb
|
|
labels:
|
|
app.kubernetes.io/name: tidaldb
|
|
app.kubernetes.io/component: server
|
|
spec:
|
|
serviceName: tidaldb # the headless Service in service.yaml — stable network id
|
|
replicas: 0 # parked; scripts/restore-fleet.sh is the only supported scale-up path
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: tidaldb
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: tidaldb
|
|
app.kubernetes.io/component: server
|
|
annotations:
|
|
# Plain-Prometheus scrape hints (the PodMonitor/ServiceMonitor in
|
|
# servicemonitor.yaml is the Operator-native alternative). Metrics are
|
|
# unauthenticated — keep :9091 cluster-internal (see ops/monitoring.md).
|
|
prometheus.io/scrape: "true"
|
|
prometheus.io/port: "9091"
|
|
prometheus.io/path: "/metrics"
|
|
spec:
|
|
# SIGTERM flips readiness to 503 (pod leaves Endpoints), drains in-flight
|
|
# requests, then checkpoints + fsyncs the WAL before exit. Give that room.
|
|
terminationGracePeriodSeconds: 60
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 10001 # the `tidal` user baked into docker/deploy/Dockerfile
|
|
runAsGroup: 10001
|
|
fsGroup: 10001 # makes the mounted PVC group-writable by the runtime user
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
containers:
|
|
- name: tidaldb
|
|
# For a real cluster, replace with your registry image pinned by digest
|
|
# (e.g. registry.example.com/tidaldb@sha256:...) and set
|
|
# imagePullPolicy: IfNotPresent. `tidaldb:deploy` is the local image
|
|
# built from docker/deploy/Dockerfile and loaded via `kind load`.
|
|
image: tidaldb:deploy
|
|
imagePullPolicy: IfNotPresent
|
|
# ENTRYPOINT is the bare binary; these args override the image CMD so
|
|
# the schema comes from the mounted ConfigMap, not the baked default.
|
|
args:
|
|
- standalone
|
|
- --listen
|
|
- 0.0.0.0:9400
|
|
- --schema
|
|
- /etc/tidaldb/schema/schema.yaml
|
|
- --data-dir
|
|
- /data
|
|
- --metrics
|
|
- 0.0.0.0:9091
|
|
env:
|
|
- name: TIDAL_API_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: tidaldb-api-key
|
|
key: api-key
|
|
- name: TIDAL_SERVER_LOG
|
|
value: info
|
|
ports:
|
|
- name: http
|
|
containerPort: 9400
|
|
- name: metrics
|
|
containerPort: 9091
|
|
# Three distinct probes map to the three health endpoints:
|
|
# - /health/startup : always 200 once the HTTP listener is up
|
|
# - /health/live : always 200 while the process is alive
|
|
# - /health : 200 ready / 503 while draining on SIGTERM
|
|
# All are unauthenticated by design, so the probes need no token.
|
|
startupProbe:
|
|
httpGet:
|
|
path: /health/startup
|
|
port: http
|
|
periodSeconds: 5
|
|
failureThreshold: 60 # up to ~5 min for large-DB WAL replay / index load (capacity-planning.md)
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health/live
|
|
port: http
|
|
periodSeconds: 10
|
|
timeoutSeconds: 3
|
|
failureThreshold: 3
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /health # 503 during drain -> removed from Service Endpoints
|
|
port: http
|
|
periodSeconds: 10
|
|
timeoutSeconds: 3
|
|
failureThreshold: 3
|
|
resources:
|
|
requests:
|
|
cpu: "250m"
|
|
memory: 256Mi
|
|
limits:
|
|
cpu: "2"
|
|
memory: 2Gi # size from docs/ops/capacity-planning.md for your item/embedding count
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true # the server only writes /data (PVC) and /tmp (emptyDir)
|
|
capabilities:
|
|
drop: ["ALL"]
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: /data
|
|
- name: schema
|
|
mountPath: /etc/tidaldb/schema
|
|
readOnly: true
|
|
- name: tmp
|
|
mountPath: /tmp
|
|
volumes:
|
|
- name: schema
|
|
configMap:
|
|
name: tidaldb-schema
|
|
- name: tmp
|
|
emptyDir: {}
|
|
volumeClaimTemplates:
|
|
- metadata:
|
|
name: data
|
|
labels:
|
|
app.kubernetes.io/name: tidaldb
|
|
spec:
|
|
accessModes: ["ReadWriteOnce"]
|
|
# storageClassName: "" # uncomment + set to pin a class; omitted = cluster default
|
|
resources:
|
|
requests:
|
|
storage: 10Gi
|