{"record":{"id":"39a52ac8c3f3c0ab","repo":"block/buzz","slug":"search-db-connection-failed-e","errorCode":null,"errorMessage":"Search DB connection failed: {e}","messagePattern":"Search DB connection failed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/buzz-relay/src/main.rs","lineNumber":415,"sourceCode":"    // sockets (via the consumer loop below), enforcing live disconnect fan-out.\n    let pubsub_for_conn_ctrl = Arc::clone(&pubsub);\n    tokio::spawn(async move { pubsub_for_conn_ctrl.run_conn_control_subscriber().await });\n\n    let auth = AuthService::new(config.auth.clone());\n\n    // Postgres FTS: the searchable row IS the persisted event row (its\n    // `tsvector` column is populated by the `insert_event` write), so there is\n    // no external collection to provision — the search service just queries the\n    // same Postgres over its own pool. Search is lag-tolerant, so it prefers\n    // the read replica when one is configured.\n    let search_db_url = config\n        .read_database_url\n        .as_deref()\n        .unwrap_or(&config.database_url);\n    let search_pool = sqlx::postgres::PgPoolOptions::new()\n        .connect(search_db_url)\n        .await\n        .map_err(|e| anyhow::anyhow!(\"Search DB connection failed: {e}\"))?;\n    let search = SearchService::new(search_pool);\n    info!(\n        replica = config.read_database_url.is_some(),\n        \"Search service ready (Postgres FTS)\"\n    );\n\n    let workflow_config = buzz_workflow::WorkflowConfig::default();\n    let workflow_engine = Arc::new(WorkflowEngine::new(db.clone(), workflow_config));\n\n    let relay_keypair = if let Some(hex) = &config.relay_private_key {\n        nostr::Keys::parse(hex)\n            .map_err(|e| anyhow::anyhow!(\"invalid BUZZ_RELAY_PRIVATE_KEY: {e}\"))?\n    } else if !config.require_auth_token {\n        // Dev mode: use a deterministic keypair so addressable events (kind:39000/39001/39002)\n        // replace correctly across restarts. Without this, each restart generates a new pubkey\n        // and replace_addressable_event inserts duplicates instead of replacing.\n        const DEV_RELAY_PRIVKEY: &str =\n            \"0000000000000000000000000000000000000000000000000000000000000001\";","sourceCodeStart":397,"sourceCodeEnd":433,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/main.rs#L397-L433","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before\nREAD_DATABASE_URL=postgres://reader.internal:5432/buzz   # replica gone\n\n# after\nREAD_DATABASE_URL=   # unset: search falls back to the writer","handlingStrategy":"retry","validationCode":"# If a reader is configured, it must be connectable — else unset it.\nif [ -n \"${READ_DATABASE_URL:-}\" ]; then\n  psql \"$READ_DATABASE_URL\" -c 'select 1' || { echo 'READ_DATABASE_URL unreachable'; exit 1; }\nfi","typeGuard":null,"tryCatchPattern":"relay:\n  restart: on-failure:5","preventionTips":["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"],"tags":["postgres","search","replica","connection","startup","env-vars"],"backgroundTag":"postgres-connection-failed","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}