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 onlyView on GitHub (pinned to e474e8803c)
Solutions
- Reduce the size of individual rows: drop or truncate oversized columns before returning results.
- Split large payloads into multiple rows instead of one giant row.
- Store large blobs in the Windmill S3 object store and keep only references/URLs in the row.
- 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
- Never embed large blobs (base64, files) in individual result rows
- Truncate or summarize oversized text/JSON columns in SQL before returning
- Store large payloads in the S3 object store and reference them by key
- Split wide denormalized rows into normalized child rows
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
- Could not generate OpenAPI document in JSON format: {}
- invalid json-float
- Completed jobs file must contain an array of jobs
- Failed to push completed jobs: ${e}
- Queued jobs file must contain an array of jobs
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/fb2ffaad8883a0e5.
Report an issue: GitHub.