block/buzz · critical · anyhow::Error
Search DB connection failed
Error message
Search DB connection failed: {e} What it means
Fatal startup error: the search service's dedicated Postgres pool (buzz-search, Postgres FTS) failed its eager connect. The search pool prefers BUZZ_READ_DATABASE_URL when set and falls back to DATABASE_URL — so this can fire even though the main DB pool connected, because the main pool's read side is lazy (min_connections=0) while this connect is immediate.
Solutions
- Test the exact URL: psql "$BUZZ_READ_DATABASE_URL" -c 'select 1' (or DATABASE_URL if unset)
- Fix or update the replica URL / credentials / network rules
- If no healthy replica exists, unset BUZZ_READ_DATABASE_URL so search uses the writer
- Restart the relay once the target accepts connections
Example fix
# before export BUZZ_READ_DATABASE_URL=postgres://relay@replica.internal:5432/buzz # stale # after export BUZZ_READ_DATABASE_URL=postgres://relay@replica-2.internal:5432/buzz # or, to run search off the writer: # unset BUZZ_READ_DATABASE_URL
Defensive patterns
Strategy: validation
Validate before calling
# Only configure the replica when it is actually reachable: if [ -n "$BUZZ_READ_DATABASE_URL" ]; then psql "$BUZZ_READ_DATABASE_URL" -c 'select 1' || unset BUZZ_READ_DATABASE_URL fi
Try / catch
// On boot failure matching "Search DB connection failed", retry only after // the replica accepts connections; otherwise drop BUZZ_READ_DATABASE_URL and // redeploy so search uses the writer.
Prevention
- Healthcheck the read replica URL in CI before configuring it
- Remember the search pool connects eagerly even though the main read pool is lazy — an unreachable replica still kills boot
When it happens
Trigger: BUZZ_READ_DATABASE_URL points at a read replica that is unreachable, firewalled, has wrong credentials, or does not exist; or the fallback DATABASE_URL target stopped accepting connections between the main pool connect and here.
Common situations: Stale replica URL after infrastructure changes; replica credentials rotated separately from the writer; security groups allowing writer but not replica traffic; pointing BUZZ_READ_DATABASE_URL at a not-yet-provisioned replica.
Related errors
- Audit DB connection failed
- DB connection failed
- Community deletion serving fence is unsafe
- Database migration failed
- Failed to backfill pubkey_allowlist (required when…
AI-assisted analysis of block/buzz@ef2aa1ae38 (2026-09-20).
Data as JSON: /api/errors/39a52ac8c3f3c0ab.
Report an issue: GitHub.
Appendix: source
Thrown at crates/buzz-relay/src/main.rs:498
// 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_metrics_pool = search_pool.clone();
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));
config
.media
.validate()
.map_err(|e| anyhow::anyhow!("invalid media config: {e}"))?;
let media_storage = buzz_media::MediaStorage::new(&config.media)
.map_err(|e| anyhow::anyhow!("failed to initialize media storage: {e}"))?;
info!("Media storage connected");
View on GitHub (pinned to ef2aa1ae38)