Skip to content

fix(p2p/sensor): keep writing when ClickHouse is down at startup - #987

Merged
minhd-vu merged 2 commits into
mainfrom
minhd-vu/clickhouse-ping-not-fatal
Aug 11, 2026
Merged

fix(p2p/sensor): keep writing when ClickHouse is down at startup#987
minhd-vu merged 2 commits into
mainfrom
minhd-vu/clickhouse-ping-not-fatal

Conversation

@minhd-vu

Copy link
Copy Markdown
Contributor

Description

connectClickHouse treated a failed startup ping as fatal. NewClickHouse then returned a no-op instance that discarded every row for the lifetime of the process and never reconnected — the sensor peered, tracked the head, and looked entirely healthy while writing nothing.

Nothing about that was necessary. The recovery path already existed:

  • clickhouse.Open performs no I/O. The pool dials lazily on first use and replaces broken connections.
  • newInsertBatcher already retries every flush chMaxFlushAttempts times and logs each failure with its table and row count.

The hard failure was the only thing preventing us from ever reaching that machinery. So the ping becomes diagnostic — logged at warn — and the connection is returned regardless.

c.conn is still assigned exactly once before any goroutine reads it, so this needs no synchronisation and introduces no reconnect goroutine.

What prompted it

A sensor fleet and its ClickHouse VM were replaced in a single Terraform apply. The sensors pinged at 18:35:37; ClickHouse finished binding 9000 at 18:35:49 — twelve seconds later. All seven sensors latched "unavailable" and discarded roughly 2.5M rows over ~30 minutes, until every host was restarted by hand. system.query_log showed zero INSERTs for the whole window.

Restarting the containers fixed it immediately, which is what confirmed the connection logic, not the environment, was the problem.

Behaviour change worth reviewing

A genuinely bad DSN or password no longer fails fast. I don't think that is a regression — the pre-existing comment in this file records an auth failure where "two sensors ran for an hour writing nothing", so the old path degraded to the same silent no-op. It now surfaces as a per-flush warning naming the table and error, which says more than the single startup line that used to scroll past. But it does mean a misconfigured DSN presents as repeated flush warnings rather than one fatal error, and reviewers may prefer a different trade-off.

I also updated the startUnavailableWarning comment, which claimed "a sensor whose database was down at boot needs a restart". That is no longer true — the path now only runs for DSN/config failures.

Jira / Linear Tickets

Testing

  • go build ./p2p/...
  • go vet ./p2p/database/
  • go test ./p2p/database/... — passes
  • Not covered by any test, and the passing suite does not demonstrate the fix. The behaviour that matters is: start a sensor with ClickHouse down, bring ClickHouse up, confirm INSERTs begin without a restart. Worth adding before this merges — happy to write it if you want it in this PR.
  • Verify a deliberately bad password still produces visibly failing flushes rather than silence

🤖 Generated with Claude Code

minhd-vu and others added 2 commits August 10, 2026 15:21
connectClickHouse treated a failed startup ping as fatal, so
NewClickHouse returned a no-op instance that discarded every row for the
lifetime of the process and never reconnected.

Nothing about that was necessary. clickhouse.Open performs no I/O -- the
pool dials lazily and replaces broken connections -- and newInsertBatcher
already retries every flush and logs each failure with its table and row
count. The recovery path existed; the hard failure prevented ever
reaching it.

A sensor fleet started twelve seconds before its ClickHouse finished
binding and lost ~2.5M rows over half an hour, needing a manual restart
on every host, purely because of that ping.

A bad DSN or password no longer fails fast, but it never usefully did --
it degraded to the same silent no-op. It now surfaces as a per-flush
warning naming the table and error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…swer

Making the startup ping non-fatal means c.conn is non-nil even when the
backend is unreachable, so HasBlock no longer takes its `c.conn == nil`
short circuit. It ran a real query against a down backend, got an error,
and returned false for every block -- queuing parent backfill requests
that could never be satisfied, which is exactly what the nil branch
existed to prevent.

Distinguish "not stored" from "could not ask": sql.ErrNoRows still means
false, any other error returns true and suppresses the backfill. Logged
at debug because a real outage is already loud via per-flush errors.

Bound the lookup too. It runs inline in the per-peer message loop on a
context with no deadline of its own, so a dial against an unreachable
backend would stall that peer on every block. Lower the pool's dial
timeout from the driver's 30s default for the same reason -- 30s would
also consume a whole chFlushTimeout window on one dial.

Fix the comments the non-fatal ping made stale: connectClickHouse no
longer verifies connectivity, and only a malformed DSN now reaches the
unavailable branch in NewClickHouse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@minhd-vu
minhd-vu marked this pull request as ready for review August 10, 2026 23:25
@minhd-vu
minhd-vu merged commit d6354fd into main Aug 11, 2026
15 checks passed
@minhd-vu
minhd-vu deleted the minhd-vu/clickhouse-ping-not-fatal branch August 11, 2026 05:40
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