8000 feat(metrics): add config, subscription check and health probes to metrics ingester by AntonTyb · Pull Request #1003 · google/xmc · GitHub
[go: up one dir, main page]

Skip to content

feat(metrics): add config, subscription check and health probes to metrics ingester - #1003

Merged
AntonTyb merged 17 commits into
feat/pubsub-ingester-corefrom
feat/pubsub-ingester-config
Oct 23, 2026
Merged

AntonTyb merged 17 commits into
feat/pubsub-ingester-corefrom
feat/pubsub-ingester-config

Conversation

@AntonTyb
@AntonTyb AntonTyb commented Sep 28, 2026 •
Copy link
Copy Markdown
Collaborator

Stack 2/3: core (#1002) ← this PR (config) ← hardening (#1004). The base branch is feat/pubsub-ingester-core, so the diff shows only this layer.

Summary

Makes the metrics ingester deployable. Settings follow flag → env → default precedence and are validated. Startup fails if the subscription is missing, the client has a lease limit, and the service exposes /healthz and /readyz. It also adds a schema drift test and a manual end-to-end test against real ClickHouse.

Motivation & Context

The core PR's entrypoint only works when run by hand. To run under Kubernetes, the ingester needs to:

  • read its settings from the environment
  • fail at startup on a missing or misnamed subscription, rather than receiving nothing without an error
  • expose probes

The drift test protects the key invariant from #1002: the Pub/Sub view has to stay identical to the Kafka view.

Key Changes

  • config.go: RegisterFlags and LoadConfig.

    • Settings are resolved from the flag first, then the environment (PORT, GOOGLE_CLOUD_PROJECT, PUBSUB_SUBSCRIPTION_ID), then defaults.
    • Validation requires the project and subscription, and a positive batch size and interval.
  • subscription.go: OpenSubscription checks Exists, so it needs pubsub.subscriptions.get. It also sets MaxOutstandingMessages = 2 × batch_size.

  • health.go:

    • /healthz reports whether the receive loop is running.
    • /readyz also requires a ClickHouse Ping.
    • Ingester.IsRunning is added to back the probes.
  • main.go is switched to the above and runs the health server.

  • deploy/clickhouse/schema_test.py (py_test): fails if xmc_metrics_pubsub_mv's SELECT (ignoring the source table) or xmc_metrics_pubsub_queue's columns drift from the Kafka equivalents.

  • test/metrics/pubsub_pipeline_test.go: publishes to pstest and runs the real writer against ClickHouse in containers. It checks that:

    • valid rows arrive
    • an empty replica_id defaults to '0'
    • out-of-range timestamps are replaced
    • xid = 0 records are filtered out
    • malformed payloads are acked

    It's added to the existing clickhouse_pipeline_test target (tags: local, manual, requires-network).

Assumptions

  • The startup Exists check is worth the extra pubsub.subscriptions.get permission — it turns a silent misconfiguration into an immediate crash loop, matching the gateway's topic check — if the deployment grants only roles/pubsub.subscriber, startup fails until roles/pubsub.viewer (or a custom role) is added.
  • /readyz gates on ClickHouse, while /healthz doesn't — a ClickHouse outage should take the pod out of rotation without restarting it — the ingester serves no traffic, so readiness only matters for rollouts and dashboards.
  • 2 × batch_size is a reasonable lease limit — it lets one batch fill while the previous one is being written — heavy traffic might need tuning.

Verification & Testing

  • Commands Executed: in the scratch module, go build, go vet, and go test -race -count=30 -cover on the package. python3 deploy/clickhouse/schema_test.py passes, as does ./.githooks/pre-commit.
  • Coverage & Test Status: 100% statement coverage on the package, and all tests pass 30 consecutive race-detector runs. The end-to-end test compiles and vets, and skips under -short. It has not yet been run against real containers because the authoring environment has no Docker. Run it with bazel test //test/metrics:clickhouse_pipeline_test --test_filter=TestPubSubPipelineIngestion.

Related Issues / Bugs

N/A

🤖 Generated with Claude Code

TAG=agy
CONV=c72e85d4-da3a-4185-b361-f01b12d0e3b4

@gemini-code-assist gemini-code-assist Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a health server with liveness and readiness probes for the metrics ingester, refactors configuration loading, and adds consistency tests for the ClickHouse schema alongside Pub/Sub pipeline integration tests. Feedback suggests adding a timeout context to the subscription existence check at startup to prevent blocking indefinitely, and checking for nil messages in the test helper awaitAllAcked to avoid potential nil pointer dereference panics.

Comment thread test/metrics/pubsub_pipeline_test.go Resolved
@AntonTyb
AntonTyb added this pull request to stack #1005 September 28, 2026 13:43
@AntonTyb
AntonTyb marked this pull request as ready for review September 29, 2026 11:33
@github-actions
github-actions Bot requested a review from janzak-dev September 29, 2026 11:33
janzak-dev
janzak-dev previously approved these changes Oct 2, 2026
ClickHouse has no Pub/Sub table engine, so with -message_bus=pubsub the
gateway's published metrics never reached ClickHouse. Add
cmd/metrics_ingester, which pulls from a subscription on the metrics
topic, batches messages and inserts them into a new Null-engine table
xmc_metrics_pubsub_queue. A second materialized view,
xmc_metrics_pubsub_mv, runs the same SELECT as the Kafka view, so both
buses apply the same filtering, defaults and timestamp sanitization.

A batch is flushed when it fills or when the batch interval passes.
Delivery is at least once: a batch is acked only after its insert
succeeds and nacked otherwise. Malformed payloads are acked and dropped,
mirroring kafka_skip_broken_messages. On shutdown, pending messages are
nacked for redelivery.

This is the base of a stack. Config precedence, the startup
subscription check, health probes and the shutdown flush follow in
later PRs.

TAG=agy
CONV=c72e85d4-da3a-4185-b361-f01b12d0e3b4
Run now closes the message channel and waits for the batch loop in a
defer, so the loop is always shut down however Receive exits.

Resetting the batch ticker after a size-triggered flush means a partial
batch waits a full interval from the last insert. Before, a tick
shortly after a full flush could insert a tiny batch, and ClickHouse
handles frequent small inserts poorly.

Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=c72e85d4-da3a-4185-b361-f01b12d0e3b4
…metry

Add bounded exponential backoff (250ms to 30s) in batchLoop after failed
ClickHouse batch writes so an outage does not tight-loop Receive -> Nack
at line rate while remaining interruptible on shutdown cancellation.

Detach regular batch writes from Run's signal context using
context.WithoutCancel bounded by a 10s per-write timeout so SIGTERM does
not abort an in-flight batch.Send() mid-flight, and check ctx.Err()
before flushing when select chooses msgCh or ticker concurrently with
cancellation.

Track malformed payload drops via an atomic counter (DroppedMessages)
logged alongside each warning, clarify the decodeBatch comment vs
kafka_skip_broken_messages, and update -batch_interval help/comments to
reflect target wait time behind slow inserts. Also sync BUILD.bazel and
main.go imports with main's rules_go and internal/logging conventions.

Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Ie330e3d44122d4f733391b19593dd7079f8e4c42
…trics ingester

Make the metrics ingester deployable:

- Settings are resolved from the flag, then the environment variable
  (PORT, GOOGLE_CLOUD_PROJECT, PUBSUB_SUBSCRIPTION_ID), then the
  default, and are validated at startup.
- Startup fails fast if the subscription doesn't exist. The client
  leases at most twice the batch size.
- /healthz reports whether the receive loop is running. /readyz also
  requires ClickHouse to answer a ping.

Add a Python schema test that fails if xmc_metrics_pubsub_mv or its
queue table drift from the Kafka equivalents. Add a manual end-to-end
test that publishes to the Pub/Sub fake and verifies the rows landing
in a real ClickHouse.

TAG=agy
CONV=c72e85d4-da3a-4185-b361-f01b12d0e3b4
Replace the duplicate Flags struct field listing with `type Flags Config`
so raw CLI flag bindings and validated runtime configuration stay
automatically in sync when options are added.

Comment link: #1003 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Ie4091718d863bc3fb615e38fb18c98c4ef80b8f0
…acts

Defer //test/metrics:__pkg__ visibility on metricsingester to #1003 where
pubsub_pipeline_test.go first consumes the package, keeping #1002 free of
forward-looking grants.

Clarify at-least-once ClickHouse batch delivery semantics in writer.go and
ingester.go (post-commit Send network errors and multi-partition toYYYYMM
writes), and document in config.go why metrics_ingester runs as a separate
service rather than inside ingestion_gateway.

Guard gate channel closures with sync.OnceFunc + t.Cleanup so awaitCalls
timeouts fail fast instead of hanging on loopDone, and replace the
bare-struct channel drain test with an end-to-end pstest test asserting
Nack settlement (AckDeadline=0, Acks=0) and redelivery after cancellation.

Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Id2d6c9a451db35e55822ef2311f41673c5a7f3c4
…s visibility

Merge updated feat/pubsub-ingester-core (42d0d4e) and declare
//test/metrics:__pkg__ visibility on //cmd/metrics_ingester/metricsingester
here alongside test/metrics/pubsub_pipeline_test.go.

Comment link: #1002 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Ie2e071863f92376d2372c006c31649fe63dcaed0
janzak-dev
janzak-dev previously approved these changes Oct 23, 2026
…tter

Add cmd/metrics_ingester/README.md and update cmd/ingestion_gateway/README.md
in #1002 so main's gateway documentation and manual setup instructions stay
aligned as soon as the ingester binary lands.

Remove the unused exported DroppedMessages() getter in favor of the
dropped_total log field and direct in-package atomic reads in tests, and
defer closeGate() inside scriptedSource so an awaitCalls t.Fatalf releases
batchLoop before Run's deferred <-loopDone waits.

Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: I5dc801d64761a983a289d97867ecd5a94e4da6d7
…ster-config

Merge updated feat/pubsub-ingester-core (502cd0e) into #1003.

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Ibb309b4323442df6353a57d0dbb0a3097da5b005
…dpoints

Update RegisterFlags docstring in config.go to state that string flags default
to empty so LoadConfig falls back to environment variables when flags are
omitted (matching firstNonEmpty semantics), and extend cmd/metrics_ingester/README.md
for -port, /healthz, /readyz, pubsub.subscriptions.get, and schema_test.

Comment link: #1003 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Ic3c615861d5a7d51ef8bdbc64f8c25876214dd20
@github-actions github-actions Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Incremental PR Approval

PR author is a code owner and changes since last approval (12 lines) are within the 50-line grace delta.

Preserving prior approval by @janzak-dev on commit 6ef9b77.

ClickHouse has no Pub/Sub table engine, so with -message_bus=pubsub the
gateway's published metrics never reached ClickHouse. Add
cmd/metrics_ingester, which pulls from a subscription on the metrics
topic, batches messages and inserts them into a new Null-engine table
xmc_metrics_pubsub_queue. A second materialized view,
xmc_metrics_pubsub_mv, runs the same SELECT as the Kafka view, so both
buses apply the same filtering, defaults and timestamp sanitization.

A batch is flushed when it fills or when the batch interval passes.
Delivery is at least once: a batch is acked only after its insert
succeeds and nacked otherwise. Malformed payloads are acked and dropped,
mirroring kafka_skip_broken_messages. On shutdown, pending messages are
nacked for redelivery.

This is the base of a stack. Config precedence, the startup
subscription check, health probes and the shutdown flush follow in
later PRs.

TAG=agy
CONV=c72e85d4-da3a-4185-b361-f01b12d0e3b4
Run now closes the message channel and waits for the batch loop in a
defer, so the loop is always shut down however Receive exits.

Resetting the batch ticker after a size-triggered flush means a partial
batch waits a full interval from the last insert. Before, a tick
shortly after a full flush could insert a tiny batch, and ClickHouse
handles frequent small inserts poorly.

Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=c72e85d4-da3a-4185-b361-f01b12d0e3b4
…metry

Add bounded exponential backoff (250ms to 30s) in batchLoop after failed
ClickHouse batch writes so an outage does not tight-loop Receive -> Nack
at line rate while remaining interruptible on shutdown cancellation.

Detach regular batch writes from Run's signal context using
context.WithoutCancel bounded by a 10s per-write timeout so SIGTERM does
not abort an in-flight batch.Send() mid-flight, and check ctx.Err()
before flushing when select chooses msgCh or ticker concurrently with
cancellation.

Track malformed payload drops via an atomic counter (DroppedMessages)
logged alongside each warning, clarify the decodeBatch comment vs
kafka_skip_broken_messages, and update -batch_interval help/comments to
reflect target wait time behind slow inserts. Also sync BUILD.bazel and
main.go imports with main's rules_go and internal/logging conventions.

Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Ie330e3d44122d4f733391b19593dd7079f8e4c42
…acts

Defer //test/metrics:__pkg__ visibility on metricsingester to #1003 where
pubsub_pipeline_test.go first consumes the package, keeping #1002 free of
forward-looking grants.

Clarify at-least-once ClickHouse batch delivery semantics in writer.go and
ingester.go (post-commit Send network errors and multi-partition toYYYYMM
writes), and document in config.go why metrics_ingester runs as a separate
service rather than inside ingestion_gateway.

Guard gate channel closures with sync.OnceFunc + t.Cleanup so awaitCalls
timeouts fail fast instead of hanging on loopDone, and replace the
bare-struct channel drain test with an end-to-end pstest test asserting
Nack settlement (AckDeadline=0, Acks=0) and redelivery after cancellation.

Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Id2d6c9a451db35e55822ef2311f41673c5a7f3c4
…tter

Add cmd/metrics_ingester/README.md and update cmd/ingestion_gateway/README.md
in #1002 so main's gateway documentation and manual setup instructions stay
aligned as soon as the ingester binary lands.

Remove the unused exported DroppedMessages() getter in favor of the
dropped_total log field and direct in-package atomic reads in tests, and
defer closeGate() inside scriptedSource so an awaitCalls t.Fatalf releases
batchLoop before Run's deferred <-loopDone waits.

Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: I5dc801d64761a983a289d97867ecd5a94e4da6d7
Resolve merge conflicts with rebased feat/pubsub-ingester-core (a103515)
preserving #1003's configuration, health probes, and ClickHouse schema tests.

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: I66466bc627cf1ce375800c64bb235d6580cb2850
AntonTyb added a commit that referenced this pull request Oct 23, 2026
…acts

Defer //test/metrics:__pkg__ visibility on metricsingester to #1003 where
pubsub_pipeline_test.go first consumes the package, keeping #1002 free of
forward-looking grants.

Clarify at-least-once ClickHouse batch delivery semantics in writer.go and
ingester.go (post-commit Send network errors and multi-partition toYYYYMM
writes), and document in config.go why metrics_ingester runs as a separate
service rather than inside ingestion_gateway.

Guard gate channel closures with sync.OnceFunc + t.Cleanup so awaitCalls
timeouts fail fast instead of hanging on loopDone, and replace the
bare-struct channel drain test with an end-to-end pstest test asserting
Nack settlement (AckDeadline=0, Acks=0) and redelivery after cancellation.

Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)
Comment link: #1002 (comment)

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: Id2d6c9a451db35e55822ef2311f41673c5a7f3c4
…#1003

Resolve merge conflicts with rebased feat/pubsub-ingester-core (0ef3972)
preserving #1003's configuration, health probes, and ClickHouse schema tests.

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: I0f8a7aee55fdb21261f683ccb404d4f7d203bad5
AntonTyb added a commit that referenced this pull request Oct 23, 2026
Merge updated feat/pubsub-ingester-config (38a16cc) into #1004 so the branch
is fully up to date with #1003 and main.

TAG=agy
CONV=0fafd02b-f3b1-4563-a73d-49ec0845afbd

Change-Id: I39d2bb1012f053f2016b201fec7ede27a5b32ac4
@AntonTyb
AntonTyb added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit c2fc5ac Oct 23, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0