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

  1. Test the exact URL: psql "$BUZZ_READ_DATABASE_URL" -c 'select 1' (or DATABASE_URL if unset)
  2. Fix or update the replica URL / credentials / network rules
  3. If no healthy replica exists, unset BUZZ_READ_DATABASE_URL so search uses the writer
  4. 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

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


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)