risingwavelabs/risingwave · error
SQL Server min_lsn is NULL
Error message
SQL Server min_lsn is NULL
What it means
Same pattern as the max_lsn check: the min_lsn column, computed as `MIN(sys.fn_cdc_get_min_lsn(capture_instance))` over `cdc.change_tables`, is NULL, so hex conversion is impossible and this error is thrown. NULL here typically means no capture instances exist for the change tables.
Source
Thrown at src/connector/src/source/cdc/enumerator/mod.rs:353
hex_string.push_str(&format!("{:02x}", byte));
}
hex_string.push(':');
for byte in &bytes[8..10] {
hex_string.push_str(&format!("{:02x}", byte));
}
Ok(hex_string)
};
let max_lsn = row
.try_get::<&[u8], usize>(0)?
.map(lsn_bytes_to_hex)
.transpose()?
.ok_or_else(|| anyhow!("SQL Server max_lsn is NULL"))?;
let min_lsn = row
.try_get::<&[u8], usize>(1)?
.map(lsn_bytes_to_hex)
.transpose()?
.ok_or_else(|| anyhow!("SQL Server min_lsn is NULL"))?;
Ok(Some((min_lsn, max_lsn)))
}
async fn monitor_sql_server_lsns(&mut self) -> ConnectorResult<()> {
let lsns = self.query_sql_server_lsns().await.with_context(|| {
format!(
"failed to query SQL Server LSNs for source {}",
self.source_id
)
})?;
if let Some((min_lsn, max_lsn)) = lsns {
let labels = vec![self.source_id.to_string()];
if let Some(value) = Self::sql_server_lsn_to_i64(&min_lsn) {
get_or_create_guarded_int_gauge(
&mut self.sqlserver_cdc_upstream_min_lsn,
&self.metrics.sqlserver_cdc_upstream_min_lsn,View on GitHub (pinned to 6469eb736d)
Solutions
- Re-enable CDC capture instances on the source table (`sys.sp_cdc_enable_table`)
- Verify `SELECT capture_instance FROM cdc.change_tables` returns the expected instances
- Restart the CDC capture job so the low water mark is established
- If the table was dropped/altered, recreate the RW CDC source against a valid capture instance
Example fix
// re-create capture instance EXEC sys.sp_cdc_enable_table @source_schema = N'dbo', @source_name = N'my_table', @role_name = NULL;
Defensive patterns
Strategy: validation
Validate before calling
// Precheck on SQL Server: // SELECT COUNT(*) FROM cdc.change_tables; // SELECT MIN(sys.fn_cdc_get_min_lsn(capture_instance)) FROM cdc.change_tables;
Try / catch
match res {
Err(e) if e.to_string().contains("min_lsn is NULL") => {
warn!("CDC min LSN unavailable; check capture instances on cdc.change_tables");
}
other => other?,
} Prevention
- Keep capture instances intact; do not drop tables under an active CDC source
- Recreate capture instances after database restores
- Monitor cdc.change_tables for cleanup-job side effects
When it happens
Trigger: The LSN query returns a row whose second column (min_lsn) is NULL — `cdc.change_tables` has rows but `fn_cdc_get_min_lsn` yields NULL, or aggregates to NULL over an empty set.
Common situations: CDC table cleanup jobs removed capture instances; capture instances were dropped while the RW source still monitors them; database restored without restoring CDC metadata; low water mark never initialized.
Related errors
- SQL Server max_lsn is NULL
- SQL Server LSN should be 10 bytes, got {} bytes
- No result returned by `SELECT sys.fn_cdc_get_max_lsn()`
- None is returned by `SELECT sys.fn_cdc_get_max_lsn()`, pleas
- conversion of SQL Server money to {data_type} is not support
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/eea16422abfe55f3.
Report an issue: GitHub.