block/buzz · critical · anyhow::Error
Search DB connection failed: {e}
Error message
Search DB connection failed: {e} What it means
The search service (Postgres FTS) opens its own pool, preferring READ_DATABASE_URL when set and falling back to DATABASE_URL. If that URL cannot be connected at boot, startup fails — even though search queries the same Postgres rows, an unreachable reader endpoint is treated as fatal rather than degrading search.
Source
Thrown at crates/buzz-relay/src/main.rs:415
// sockets (via the consumer loop below), enforcing live disconnect fan-out.
let pubsub_for_conn_ctrl = Arc::clone(&pubsub);
tokio::spawn(async move { pubsub_for_conn_ctrl.run_conn_control_subscriber().await });
let auth = AuthService::new(config.auth.clone());
// Postgres FTS: the searchable row IS the persisted event row (its
// `tsvector` column is populated by the `insert_event` write), so there is
// no external collection to provision — the search service just queries the
// same Postgres over its own pool. Search is lag-tolerant, so it prefers
// the read replica when one is configured.
let search_db_url = config
.read_database_url
.as_deref()
.unwrap_or(&config.database_url);
let search_pool = sqlx::postgres::PgPoolOptions::new()
.connect(search_db_url)
.await
.map_err(|e| anyhow::anyhow!("Search DB connection failed: {e}"))?;
let search = SearchService::new(search_pool);
info!(
replica = config.read_database_url.is_some(),
"Search service ready (Postgres FTS)"
);
let workflow_config = buzz_workflow::WorkflowConfig::default();
let workflow_engine = Arc::new(WorkflowEngine::new(db.clone(), workflow_config));
let relay_keypair = if let Some(hex) = &config.relay_private_key {
nostr::Keys::parse(hex)
.map_err(|e| anyhow::anyhow!("invalid BUZZ_RELAY_PRIVATE_KEY: {e}"))?
} else if !config.require_auth_token {
// Dev mode: use a deterministic keypair so addressable events (kind:39000/39001/39002)
// replace correctly across restarts. Without this, each restart generates a new pubkey
// and replace_addressable_event inserts duplicates instead of replacing.
const DEV_RELAY_PRIVKEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";View on GitHub (pinned to f956e6fe06)
Solutions
- Test the reader endpoint: `psql "$READ_DATABASE_URL" -c 'select 1'`
- Fix or remove READ_DATABASE_URL — unsetting it falls back to the writer
- Verify DNS and security groups for the replica
Example fix
# before READ_DATABASE_URL=postgres://reader.internal:5432/buzz # replica gone # after READ_DATABASE_URL= # unset: search falls back to the writer
Defensive patterns
Strategy: retry
Validate before calling
# If a reader is configured, it must be connectable — else unset it.
if [ -n "${READ_DATABASE_URL:-}" ]; then
psql "$READ_DATABASE_URL" -c 'select 1' || { echo 'READ_DATABASE_URL unreachable'; exit 1; }
fi Try / catch
relay: restart: on-failure:5
Prevention
- Remove READ_DATABASE_URL when the replica is decommissioned — search falls back to the writer
- Include the reader endpoint in connectivity probes
- Keep replica credentials in sync with rotation of the writer's
When it happens
Trigger: READ_DATABASE_URL points to a replica that is unreachable, firewalled, has invalid credentials, or was decommissioned; or DATABASE_URL itself is bad (same causes as the main pool failure).
Common situations: Replica removed or renamed but the env not updated; cross-VPC reader with network policy changes; staging copying prod env with a stale reader endpoint.
Related errors
- Audit DB connection failed: {e}
- DB connection failed: {e}
- Configuration error: {e}
- Database migration failed: {e}
- Community deletion serving fence is unsafe: {e}
AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16).
Data as JSON: /api/errors/39a52ac8c3f3c0ab.
Report an issue: GitHub.