windmill-labs/windmill · error · std::io::Error (WriteZero)

row over budget

Error message

row over budget

What it means

The DuckDB FFI writer serializes each JSON row into a bounded in-memory buffer. The custom `Write::write_all` checks the incoming bytes against the remaining budget (`self.left`) and raises WriteZero 'row over budget' when a single write would exceed the per-row size limit. This prevents unbounded memory use from oversized rows during JSON-to-DuckDB ingestion.

Source

Thrown at backend/windmill-duckdb-ffi-internal/src/lib.rs:926

/// `None` is reported to the caller as "too large", which is only honest because
/// the callers pass a `serde_json::Map` of `Value`s: serializing one cannot fail
/// for any reason except the budget. A caller passing a type with a fallible
/// `Serialize` would have its error silently retold as a size limit.
fn to_raw_value_within<T: serde::Serialize>(value: &T, budget: usize) -> Option<Box<RawValue>> {
    struct Budgeted {
        buf: Vec<u8>,
        left: usize,
    }
    impl std::io::Write for Budgeted {
        fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
            self.write_all(bytes)?;
            Ok(bytes.len())
        }
        // `Vec<u8>` overrides this too: the default implementation loops over
        // `write`, and serde_json emits a great many small pieces per row.
        fn write_all(&mut self, bytes: &[u8]) -> std::io::Result<()> {
            if bytes.len() > self.left {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::WriteZero,
                    "row over budget",
                ));
            }
            self.left -= bytes.len();
            self.buf.extend_from_slice(bytes);
            Ok(())
        }
        fn flush(&mut self) -> std::io::Result<()> {
            Ok(())
        }
    }

    let mut writer = Budgeted { buf: Vec::new(), left: budget };
    serde_json::to_writer(&mut writer, value).ok()?;
    let json = String::from_utf8(writer.buf).ok()?;
    // SAFETY: `to_writer` returned `Ok`, so `json` holds one complete, well-formed
    // JSON value with no surrounding whitespace. Running out of budget is the only

View on GitHub (pinned to e474e8803c)

Solutions

  1. Reduce the size of individual rows: drop or truncate oversized columns before returning results.
  2. Split large payloads into multiple rows instead of one giant row.
  3. Store large blobs in the Windmill S3 object store and keep only references/URLs in the row.
  4. If this is a legitimate workload, increase the row budget in the duckdb FFI writer configuration.

Example fix

// before
SELECT payload FROM events; // payload is a 20MB JSON blob per row
// after
SELECT id, json_extract_string(payload, '$.summary') AS summary FROM events; // or store payload in S3 and reference it
Defensive patterns

Strategy: validation

Validate before calling

// check serialized row size against budget before ingestion
let serialized = serde_json::to_vec(&row)?;
const ROW_BUDGET: usize = 4 * 1024 * 1024;
if serialized.len() > ROW_BUDGET {
    return Err(format!("row {} is {} bytes, exceeds {} budget; truncate or move payload to S3", id, serialized.len(), ROW_BUDGET));
}

Try / catch

match write_all_result {
    Err(e) if e.kind() == std::io::ErrorKind::WriteZero && e.to_string().contains("row over budget") => {
        eprintln!("row too large: drop/truncate large columns or store payload in S3");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Streaming a JSON result row via serde_json whose serialized size exceeds the row budget in write_all — i.e. one result row (wide columns, large strings, big nested JSON blobs) is too large for the configured row buffer.

Common situations: Scripts returning huge JSON objects per row (embedded base64 payloads, large arrays); selecting entire large JSON/text columns; flow steps aggregating many fields into a single row.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/fb2ffaad8883a0e5. Report an issue: GitHub.