Dev multi-worker Trino benchmark lifecycle (fail-closed) - #1049
Conversation
Test Impact PlanDeterministic summary of how this PR changes tests, CI runners, and coverage-risk signals. Summary
Signals
Coverage risk: likely reduced Warnings
|
|
Fixed in 00a232e — and the finding turned out to have a sibling. Confirmed. A field-by-field diff of the two literals found a second drift, pre-existing and unrelated to this PR: FixRather than add two lines and leave the bug class in place, the two ~56-field literals are collapsed into one assembly site: Tripwire
Both exemption maps carry stated structural reasons rather than being a dumping ground:
|
Compare one Duckgres worker over PGWire against a multi-worker Trino cluster reading the same per-run DuckLake snapshot, driven by the `posthog_frozen_trino_perf` scenario. Control plane: - Authenticated provision/status/deprovision routes on the existing admin `/api/v1` router. Responses carry only cluster ID, state, endpoint, worker counts, and the pinned image; error bodies are fixed strings and infrastructure detail is logged, never returned. - A Kubernetes lifecycle manager rendering one coordinator, exactly the requested workers (default 4), a ClusterIP Service selecting only the coordinator, coordinator/worker/catalog ConfigMaps, and a short-lived Secret holding only the charts-created metadata reader password. requests == limits (Guaranteed QoS) so Trino does not contend with the Duckgres worker it is compared against. Provision is idempotent and 409s on different ownership, image, or worker count; readiness needs the coordinator AND every requested worker; cleanup selects on the ownership labels, so it is safe after a partial provision and cannot reach a worker pod, another cluster, or the charts Secret. - A reader-identity resolver over the config-store warehouse row and the Duckling CR's charts-published `status.benchmarkReader` block, read through a new credential-free status accessor. It fails closed on any missing field and refuses a configuration whose reader S3 role or reader database user equals the warehouse writer identity. There is no writer-credential fallback path. S3 access is an assumed read-only IAM role (renewable), never static keys; both engines are pinned to UTC. Scenario and artifacts: - The lifecycle client now polls (interval, timeout, max attempts) with `pending` a polling state and `failed` terminal, instead of issuing a single status request. Trino teardown is always_run and ordered before warehouse teardown. - `summary.json` records per-protocol comparison metadata: engine and version (Trino's from the coordinator's /v1/info), connector version, image reference/digest, requested/ready workers, catalog/schema, UTC. No latency thresholds and no CI gating. Configuration is env-only and DISABLED by default; enabling it without a pinned image leaves the lifecycle unbuilt, and a built lifecycle still refuses to provision until the companion charts reader resources exist. The PGWire-only frozen perf scenario is unchanged, with a test asserting it contains no Trino steps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both control-plane entry points hand-maintained their own ~56-field ControlPlaneConfig literal with nothing forcing them to agree, and two knobs had drifted out of the PRODUCTION one — cmd/duckgres-controlplane (Dockerfile.controlplane, its own CD pipeline): - UserSecretKey (pre-existing, unrelated to this PR): resolved and then discarded, so DUCKGRES_USER_SECRET_KEY never reached the control plane and CREATE PERSISTENT SECRET was rejected with 0A000 in production even when the chart supplied a key. - TrinoBenchmark (added earlier in this PR): every DUCKGRES_TRINO_BENCHMARK_* variable parsed and then dropped, so the lifecycle stayed unbuilt in production and its routes answered 503. Neither is visible to the mw-dev e2e or scenario runs: those build the root Dockerfile's all-in-one binary, whose literal wires both fields. configresolve.ControlPlaneConfig is now the single assembly site; the four per-binary values (the binary's own TLS/ACME-adjusted server.Config, its socket-dir and config-path flags, its already-running metrics server) arrive through ControlPlaneOverrides. Adding a knob to Resolved now reaches both binaries by construction, and 124 lines of duplicated literal are replaced by 12. The tripwire (configresolve/controlplane_test.go) checks MAPPING COVERAGE in both directions and deliberately asserts nothing about runtime values — zero is legitimate for an unset TTL, an empty PriorityClass meaning "headroom disabled", or a false feature gate. It requires that every destination field be movable by some input, and that every Resolved field change the output. Its two exemption maps carry stated structural reasons (Server arrives via overrides; SessionInitTimeout reaches the control plane inside the embedded server.Config; HealthCheckInterval has no configuration source at all and is defaulted in RunControlPlane). Also fixes two stale CLAUDE.md pointers to the long-renamed config_resolution.go. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
00a232e to
64a9ffe
Compare
What
Compare one Duckgres worker over PGWire against a multi-worker Trino cluster reading the same per-run DuckLake snapshot, driven by the
posthog_frozen_trino_perfscenario. #1048 established the pinned Trino + Brikk connector locally; this makes it a real dev-cluster comparison.Control plane
/api/v1router: provision / status / deprovision. Responses carry only cluster ID, state, endpoint, requested/ready worker counts, and the pinned image.202create vs200idempotent repeat,409conflicting ownership/config,503disabled-or-unconfigured. Error bodies are fixed strings; infrastructure detail is logged, never returned.requests == limits(Guaranteed QoS) so Trino does not contend with the Duckgres worker it is being compared against. Readiness requires the coordinator and every requested worker replica. Cleanup selects on ownership labels — safe after a partial provision, and structurally unable to reach a worker pod, another benchmark cluster, or the charts-created Secret.status.benchmarkReaderblock, read through a new credential-free status accessor so resolving a reader never pulls the tenant writer password into memory.Security posture
SecretReference, written into the cluster-owned Secret — and is never logged, returned, or stored on a struct. Tests assert no secret values in HTTP responses, logs, or artifacts.Scenario and artifacts
pendingis a polling state,failedis terminal. Trino teardown isalways_runand ordered before warehouse teardown.summary.jsonrecords per-protocol comparison metadata: engine and version (Trino's from the coordinator's own/v1/info), connector version, image reference/digest, requested/ready worker counts, catalog/schema identity, and the UTC session time zone.query_results.csvgets a row per protocol per query.Testing
Written test-first. New coverage: API auth/validation/status/error mapping, fail-closed missing reader identity, rendered coordinator/worker topology, image pin and explicit resources, read-only role and Secret wiring, absence of writer credentials, readiness requiring all workers, idempotent provision, partial-provision cleanup, ownership-safe cleanup, lifecycle-client polling and terminal failure, mixed PGWire/Trino catalog applicability, scenario DAG and teardown ordering, and no secret values in responses/logs/artifacts. Fake Kubernetes clients and fake resolvers throughout — no live cluster needed for unit tests.
Passing:
go test ./tests/perf/... ./tests/mw-dev/...,go test ./controlplane/... ./configresolve/..., the-tags kuberneteslane,just lint(0 issues),git diff --check.Blocked on the companion charts PR
The feature is disabled and fail-closed until the charts reader resources are deployed. Charts must provide:
s3:ListBucket+s3:GetObjectonly), assumable by the control-plane role;status.benchmarkReader.{metadataUser, credentialSecretRef{name,namespace,key}, s3ReadOnlyRoleArn}— the exact contract this resolver reads.The dev benchmark has not been run against dev; it cannot be until those exist. That plan item is left unchecked.
Docs
Scenario runbook (config table, API table, lifecycle, artifacts, failure recovery), a load-bearing-contract section in
CLAUDE.md, and updated plan checkboxes.🤖 Generated with Claude Code