influxdata/influxdb · error · Error

row range .. is out of bounds for a chunk of rows

Error message

row range {start}..{end} is out of bounds for a chunk of {row_count} rows

What it means

Thrown when a caller slices a buffered chunk with a row range (start..end) that exceeds the chunk's actual row_count. The buffer validates the requested bounds before reading rows and reports the offending start, end, and the chunk's size. It protects against out-of-bounds reads on Arrow record data.

Solutions

  1. Clamp the requested range to the chunk's row_count before reading: end = end.min(row_count), and skip the read when start >= end.
  2. Fetch row_count from the live chunk/chunk summary rather than a cached or assumed value.
  3. Fix pagination loops to use min(start + page_size, row_count) for the last page.
  4. If ranges come from query planning, recompute bounds against the current buffer state under the same lock/epoch that serves the read.

Example fix

// before
let rows = chunk.read(start..start + PAGE_SIZE)?;

// after
let end = (start + PAGE_SIZE).min(chunk.row_count());
let rows = if start < end { chunk.read(start..end)? } else { &[] };
Defensive patterns

Strategy: validation

Validate before calling

let row_count = chunk.row_count();
let start = start.min(row_count);
let end = end.min(row_count);
if start >= end { return Ok(Vec::new()); } // empty range is valid, skip the read
chunk.read(start..end)

Type guard

fn is_valid_range(range: std::ops::Range<usize>, row_count: usize) -> bool {
    range.start < range.end && range.end <= row_count
}

Try / catch

match chunk.read(range.clone()) {
    Ok(rows) => process(rows),
    Err(Error::RowRangeOutOfBounds { start, end, row_count }) => {
        log::warn("range {start}..{end} exceeds {row_count} rows; clamping");
        process(chunk.read(start.min(row_count)..end.min(row_count))?)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling a chunk-read/range API on TableBuffer (e.g. reading rows start..end from a Chunk) where end > row_count or start > end / start > row_count — typically from computed pagination offsets, a stale cached row count, or a caller assuming a different chunk size than the buffer holds.

Common situations: Paginating query results with a fixed page size larger than the final chunk; cached chunk metadata (row_count) going stale after a compaction/deduplication pass; off-by-one in end = start + page_size without clamping to row_count; concurrent truncation of the buffer while a read is computed.

Related errors


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

Appendix: source

Thrown at influxdb3_write/src/write_buffer/table_buffer.rs:35

use schema::{InfluxColumnType, InfluxFieldType, Schema, SchemaBuilder};
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::mem::size_of;
use std::ops::Range;
use std::sync::Arc;
use thiserror::Error;

use crate::ChunkFilter;

#[derive(Debug, Error)]
pub enum Error {
    #[error("Field not found in table buffer: {0}")]
    FieldNotFound(String),

    #[error("Error creating record batch: {0}")]
    RecordBatchError(#[from] arrow::error::ArrowError),

    #[error("row range {start}..{end} is out of bounds for a chunk of {row_count} rows")]
    RowRangeOutOfBounds {
        start: usize,
        end: usize,
        row_count: usize,
    },
}

pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Default)]
pub struct TableBuffer {
    chunk_time_to_chunks: BTreeMap<i64, ChunkTimeBuffer>,
    snapshotting_chunks: Vec<SnapshotChunk>,
}

/// All buffered chunks for one chunk_time (gen1 window) of a table, plus the
/// overwrite index spanning them. A chunk_time usually holds one chunk; a
/// var-column overflow splits it into several, and hoisting the index here

View on GitHub (pinned to 06200ef96b)