fix(p2p/sensor): keep writing when ClickHouse is down at startup - #987
Merged
Conversation
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
marked this pull request as ready for review
August 10, 2026 23:25
praetoriansentry
approved these changes
Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
connectClickHousetreated a failed startup ping as fatal.NewClickHousethen 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.Openperforms no I/O. The pool dials lazily on first use and replaces broken connections.newInsertBatcheralready retries every flushchMaxFlushAttemptstimes 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.connis 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_logshowed 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
startUnavailableWarningcomment, 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🤖 Generated with Claude Code