influxdata/influxdb · error · Error::NullTime

time column must not contain nulls

Error message

time column must not contain nulls

What it means

NullTimeSnafu is raised by write_table_batch in mutable_batch_pb when the batch's `time` column carries a non-null validity mask — i.e. some rows have null timestamps. IOx requires every row to have a concrete timestamp, so the decoder ensures valid_mask.is_none() for the Timestamp column before writing values via write_time_from_slice.

Solutions

  1. Make the producer populate a timestamp for every row so the time column has no null mask (or an all-valid mask)
  2. Drop rows with null timestamps before sending the batch
  3. If the null mask is spurious (all values valid), fix the serializer to emit an empty/all-true mask

Example fix

// before
// time column null_mask with a false bit for row 2
// after
// either fill the timestamp or filter the row
let rows: Vec<_> = rows.into_iter().filter(|r| r.time.is_some()).collect();
Defensive patterns

Strategy: validation

Validate before calling

fn time_column_has_nulls(null_mask: &[bool]) -> bool {
    null_mask.iter().any(|v| !v)
}
if time_column_has_nulls(&time_null_mask) { /* drop offending rows before sending */ }

Try / catch

match write_table_batch(batch, table_batch) {
    Err(e) if e.to_string() == "time column must not contain nulls" => /* filter null-timestamp rows and retry */,
    other => other?,
}

Prevention

When it happens

Trigger: Calling write_table_batch / decode_database_batch where the time column's null_mask has any false bits (null timestamps), typically because a row's timestamp field was missing or set to null in the protobuf.

Common situations: Producers sending rows without timestamps relying on server defaults; upstream ETL that writes optional timestamps as nulls; a serializer bug emitting a null mask for the time column even when all values exist.

Related errors


AI-assisted analysis of influxdata/influxdb@06200ef96b (2026-09-19). Data as JSON: /api/errors/923e20a97763ea30. Report an issue: GitHub.

Appendix: source

Thrown at core/mutable_batch_pb/src/decode.rs:217

                        &column.column_name,
                        valid_mask,
                        RepeatLastElement::new(packed_strings_iter(packed)),
                    )
                } else {
                    writer.write_string(
                        &column.column_name,
                        valid_mask,
                        RepeatLastElement::new(values.string_values.iter().map(|x| x.as_str())),
                    )
                }
            }
            InfluxColumnType::Field(InfluxFieldType::Boolean) => writer.write_bool(
                &column.column_name,
                valid_mask,
                RepeatLastElement::new(values.bool_values.iter().cloned()),
            ),
            InfluxColumnType::Timestamp => {
                ensure!(valid_mask.is_none(), NullTimeSnafu);
                writer.write_time_from_slice(&column.column_name, &values.i64_values)
            }
        }
        .context(WriteSnafu {
            column: &column.column_name,
        })?;
    }

    writer.commit();
    Ok(())
}

/// Iterator wrapper that repeats the last element forever.
///
/// This will just yield `None` if the wrapped iterator was empty.
struct RepeatLastElement<I>
where
    I: Iterator,

View on GitHub (pinned to 06200ef96b)