tidaldb/k8s/cluster/topology-configmap.yaml
jx12n 31ee612f27 feat(m12p4): sharded ingestion — scatter-gather pool + cross-shard unified reads (L4)
Scale write throughput across data-shard groups while keeping a single unified
read surface:

- scatter_gather.rs: pooled fan-out across shard groups (replaces per-request
  client construction); cross-shard query results merged on one node
- cluster/node.rs: cross-shard read routing — a read on any node gathers from
  every shard group's leader and unions results
- cluster/forward.rs: fix h2 204 forward-relay bug (relay_forwarded skips body
  for 1xx/204/304 — synthesized JSON body on a 204 triggered HTTP/2 RST_STREAM
  on the real mTLS plane)
- dto.rs: cross-shard query/result DTOs
- k8s/cluster/: enable 3-group `shards:` topology (statefulset, service-peers,
  topology-configmap)
- k8s/cluster-local-kind/: local-kind overlay to run the T5 gate without Ref-A
- tidal-stress/k8s/stress-job-t5.yaml: 2-generator sharded throughput job
- tests: cluster_cross_shard_reads.rs + multiproc support; ran real on kind
- docs/profiling/m12p4-t5-sharded-throughput.md: T5 throughput findings
2026-06-14 15:17:35 -06:00

125 lines
7.1 KiB
YAML
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.

# The ONE bootstrap topology file, shared by EVERY pod (m11p5 §4).
#
# Before m11p5, k8s cluster mode needed a per-pod ConfigMap variant: a node bound
# its own `grpc_addr` LITERALLY and a pod cannot bind a Service ClusterIP, so each
# pod's self-entry had to be a bindable address while peers' entries stayed the
# routable ones — defeating the "every process parses the SAME file" contract.
#
# The m11p5 bind/advertise split kills that hack:
# - `grpc_addr` is the ADVERTISED address siblings DIAL (a per-pod DNS name);
# with a DNS name tonic re-resolves on every reconnect, so a rescheduled pod
# on a new IP is reachable again with NO peer restart.
# - `grpc_bind` is the LOCAL socket — `0.0.0.0:9601` on every pod. The local
# socket never tries to bind the DNS name.
# So one file names all three initial regions by their stable headless-Service
# DNS names, and every pod mounts THIS file unmodified.
#
# Region declaration order == RegionId (topology.rs:24-28). The three regions are
# named after the StatefulSet's stable pod identities tidaldb-{0,1,2}. The pod's
# `--region` comes from POD_NAME (statefulset.yaml fieldRef), so pod `tidaldb-0`
# is region `tidaldb-0`, etc. — the names line up by construction.
#
# SCALING PAST 3 DOES NOT EDIT THIS FILE: pod N>=3 boots with `--seed` and learns
# its roster/id/term from a seed (membership is data, on the replicated log). It
# still mounts THIS file for the behavioral knob blocks only — `--seed` boots
# REQUIRE a local config for `replication`/`wal`/`election`/`timeouts`/`grpc_tls`
# (m11p5 §3.5); the `regions:` list below is IGNORED for the roster of a seed
# joiner (the join response is authoritative), it supplies only those knobs.
#
# DNS form: <pod>.<headless-service>.<namespace>.svc.cluster.local
# tidaldb-0.tidaldb-peers.tidaldb-cluster.svc.cluster.local
apiVersion: v1
kind: ConfigMap
metadata:
name: tidaldb-cluster-topology
namespace: tidaldb-cluster
labels:
app.kubernetes.io/name: tidaldb
app.kubernetes.io/part-of: tidaldb
data:
cluster-topology.yaml: |
# Three initial regions, named by their per-pod headless-Service DNS.
# grpc_addr = ADVERTISED gRPC (DNS, port 9601) — what peers dial.
# grpc_bind = LOCAL gRPC bind (0.0.0.0:9601) — the pod can't bind the DNS.
# http_addr = ADVERTISED HTTP (DNS, port 9500) — peers forward writes/status.
# metrics_addr = per-region Prometheus listener (0.0.0.0:9091).
# grpc_tls (m11p7): inter-node TLS material, mounted from the cert-manager
# Secret `tidaldb-cluster-tls` at /etc/tidaldb/tls (see certs.yaml +
# statefulset.yaml). The SAME shared node cert is mounted into every pod, so
# all three blocks point at identical paths; each node reads its OWN block.
# The cert doubles as the gRPC mTLS client identity (client_cert/client_key)
# and the gRPC + inter-node-HTTP server identity. Removing these blocks reverts
# the cluster to PLAINTEXT inter-node links (a loud startup WARN), acceptable
# only on a fully trusted network.
regions:
- name: tidaldb-0
grpc_addr: tidaldb-0.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9601
grpc_bind: 0.0.0.0:9601
http_addr: tidaldb-0.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9500
metrics_addr: 0.0.0.0:9091
grpc_tls: &grpc_tls
ca_cert: /etc/tidaldb/tls/ca.crt
server_cert: /etc/tidaldb/tls/tls.crt
server_key: /etc/tidaldb/tls/tls.key
client_cert: /etc/tidaldb/tls/tls.crt
client_key: /etc/tidaldb/tls/tls.key
- name: tidaldb-1
grpc_addr: tidaldb-1.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9601
grpc_bind: 0.0.0.0:9601
http_addr: tidaldb-1.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9500
metrics_addr: 0.0.0.0:9091
grpc_tls: *grpc_tls
- name: tidaldb-2
grpc_addr: tidaldb-2.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9601
grpc_bind: 0.0.0.0:9601
http_addr: tidaldb-2.tidaldb-peers.tidaldb-cluster.svc.cluster.local:9500
metrics_addr: 0.0.0.0:9091
grpc_tls: *grpc_tls
# Term-0 bootstrap leader only — post-election this field is dead config
# (durable election_state governs; a restart always boots a follower).
leader: tidaldb-0
# ── Sharding × replication (m11p6 / m12p4 T5) ────────────────────────────
# Absent `shards:` ⇒ ONE group, RF = all pods. We ENABLE 3 groups × RF=3 so
# the write path scales horizontally: each group is an independent
# replication group with its OWN elected leader, and the three leaders are
# balanced one-per-pod (tidaldb-0 leads shard 0, tidaldb-1 shard 1,
# tidaldb-2 shard 2). The entity space is hash-partitioned across the three
# groups, so three leaders absorb writes in parallel instead of one — the
# ~S× write-throughput claim m12p4/T5 proves.
#
# Full placement: every pod replicates EVERY group, so any pod can serve a
# corpus-wide read locally (no cross-node read fan-out needed at this shape)
# and a single pod loss never loses a group's quorum (2 of 3 survive per
# group). A pod hosting all three groups binds one gRPC port per group:
# `replicas[].grpc_addr` is omitted, so each is DERIVED as
# `node base port + shard id` (tidaldb-N binds 9601 for shard 0, 9602 for
# shard 1, 9603 for shard 2 — matching the extra containerPorts in
# statefulset.yaml). Each group's data lives under
# <data_dir>/shard-{id:05}/ in the one PVC. Operators rebalance with
# `POST /cluster/shards/{id}/transfer` and `/replicas` (see runbook §6a).
shards:
- { id: 0, leader: tidaldb-0, replicas: [ {node: tidaldb-0}, {node: tidaldb-1}, {node: tidaldb-2} ] }
- { id: 1, leader: tidaldb-1, replicas: [ {node: tidaldb-0}, {node: tidaldb-1}, {node: tidaldb-2} ] }
- { id: 2, leader: tidaldb-2, replicas: [ {node: tidaldb-0}, {node: tidaldb-1}, {node: tidaldb-2} ] }
replication:
# ack=quorum: a write succeeds once a MAJORITY of the replica set durably
# holds it (m11p3). Callers can still override per-request with x-tidal-ack.
ack: quorum
# reseed_self_restart=true (m11p5 §2.4): a node that latches the durable
# `reseed_required` marker drains and exits(0) so the StatefulSet restarts
# it and the boot-time install re-runs. The exit is REFUSED (loudly, in
# status + gauge) when the remaining voters can't sustain quorum without
# this node — exiting during a 2-voter window would be a total write outage.
reseed_self_restart: true
election:
# Defaults (300ms heartbeats, 1500-3000ms election timeout, 900ms lease,
# auto-election on) are right for an in-cluster (low-RTT) deployment. The
# C2 safety invariant lease+heartbeat < election_timeout_min holds for the
# defaults; spell the block out so an operator widening WAN budgets edits
# here, not the compiled-in defaults.
heartbeat_interval_ms: 300
election_timeout_min_ms: 1500
election_timeout_max_ms: 3000
leader_lease_ms: 900
auto_election: true