risingwavelabs/risingwave · error · SinkError::Http
Turbopuffer namespace_column cannot be null
Error message
Turbopuffer namespace_column cannot be null
What it means
When the namespace is chosen per row via `namespace_column`, the URL for each write is built from that column's value in `url_for_row`. If the datum at the namespace index is NULL at write time, there is no namespace to route the document to, so an Http error is returned for the chunk.
Solutions
- Make the namespace column NOT NULL in the upstream MV/table (e.g. COALESCE(ns, 'default')).
- Backfill existing NULL rows with a default namespace before enabling the sink.
- Add a WHERE filter upstream to exclude rows with NULL namespace.
- If a single namespace works, switch to the static `namespace` option.
Example fix
// before CREATE MATERIALIZED VIEW mv AS SELECT tenant_id::varchar AS ns, * FROM t; // after CREATE MATERIALIZED VIEW mv AS SELECT COALESCE(tenant_id::varchar, 'default') AS ns, * FROM t;
Defensive patterns
Strategy: validation
Validate before calling
// SQL: detect NULL namespaces before the sink processes them SELECT count(*) FROM mv_for_sink WHERE ns IS NULL; -- should be 0
Type guard
// in a row-processing pipeline, before building the sink payload
function getNamespace(row) {
const ns = row.ns;
if (typeof ns !== 'string') throw new TypeError('namespace_column must be a non-null string');
return ns;
} Try / catch
// route failing rows to a dead-letter path
try {
await sink.write(row);
} catch (e) {
if (String(e).includes('namespace_column cannot be null')) {
deadLetter.push({ row, reason: 'null-namespace' });
} else { throw e; }
} Prevention
- Declare namespace columns NOT NULL with COALESCE defaults
- Filter NULL-namespace rows out of the MV feeding the sink
When it happens
Trigger: Writing a row where the varchar namespace_column is NULL; rows inserted with an unspecified namespace column after the sink was created.
Common situations: Nullable namespace column in the MV feeding the sink; batch backfills that omit the namespace field; LEFT JOIN results producing NULL namespaces for unmatched rows.
Related errors
- Turbopuffer document id column cannot be null
- Turbopuffer string document id exceeds 64 bytes
- Google Pub/Sub sink error: missing value to publish
- Turbopuffer attribute column must not be named id
- Turbopuffer document id column must be an integer or varchar
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/be26efa6cbdd7de9.
Report an issue: GitHub.
Appendix: source
Thrown at src/connector/src/sink/turbopuffer.rs:499
row_encoder,
write_batch_size,
max_linger,
pending_batches: BTreeMap::new(),
})
}
fn url_for_row(&self, row: &impl Row) -> Result<String> {
match &self.namespace {
TurbopufferNamespace::Static(namespace) => Ok(format!(
"{}/v2/namespaces/{}",
self.base_url,
namespace.as_str()
)),
TurbopufferNamespace::Dynamic { index } => {
let namespace = match row.datum_at(*index) {
Some(ScalarRefImpl::Utf8(namespace)) => namespace,
None => {
return Err(SinkError::Http(anyhow!(
"Turbopuffer namespace_column cannot be null"
)));
}
Some(_) => {
return Err(SinkError::Http(anyhow!(
"unexpected namespace_column type, expected varchar"
)));
}
};
validate_namespace(namespace)?;
Ok(format!("{}/v2/namespaces/{}", self.base_url, namespace))
}
}
}
// Turbopuffer document IDs are unsigned 64-bit integers, UUIDs, or strings up to 64 bytes.
// RisingWave UUID IDs can be represented with varchar.
fn id_for_row(&self, row: &impl Row) -> Result<DocumentId> {View on GitHub (pinned to 6469eb736d)