{"record":{"id":"c83254ea54a512bb","repo":"quickwit-oss/quickwit","slug":"num-outputs-must-be-at-least-1","errorCode":null,"errorMessage":"num_outputs must be at least 1","messagePattern":"num_outputs must be at least 1","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"quickwit/quickwit-parquet-engine/src/merge/mod.rs","lineNumber":181,"sourceCode":"    input_paths: &[PathBuf],\n    output_dir: &Path,\n    config: &MergeConfig,\n    read_batch_size: usize,\n) -> Result<Vec<MergeOutputFile>> {\n    merge_sorted_parquet_files_impl(input_paths, output_dir, config, Some(read_batch_size))\n}\n\nfn merge_sorted_parquet_files_impl(\n    input_paths: &[PathBuf],\n    output_dir: &Path,\n    config: &MergeConfig,\n    read_batch_size: Option<usize>,\n) -> Result<Vec<MergeOutputFile>> {\n    if input_paths.is_empty() {\n        bail!(\"merge requires at least one input file\");\n    }\n    if config.num_outputs == 0 {\n        bail!(\"num_outputs must be at least 1\");\n    }\n\n    // Step 0: Read and validate metadata from all input files.\n    // Sort schema, window, and merge ops are derived from the files themselves.\n    let input_meta = extract_and_validate_input_metadata(input_paths)?;\n\n    info!(\n        num_inputs = input_paths.len(),\n        num_outputs = config.num_outputs,\n        sort_fields = %input_meta.sort_fields,\n        \"starting sorted parquet merge\"\n    );\n\n    // Step 1: Read all input files into RecordBatches.\n    let inputs = read_inputs(input_paths, read_batch_size)?;\n    let total_rows: usize = inputs.iter().map(|b| b.num_rows()).sum();\n\n    if total_rows == 0 {","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/quickwit-oss/quickwit/blob/a39730c5cdcd1a4fe798403737ae293999ea21f8/quickwit/quickwit-parquet-engine/src/merge/mod.rs#L163-L199","documentation":"merge_sorted_parquet_files_impl validates that MergeConfig.num_outputs is at least 1. num_outputs controls how many output files the merge produces by splitting at sorted_series transitions; a value of 0 would make boundary computation and the writer produce no output for non-empty input, so it is rejected up front as an invalid configuration.","triggerScenarios":"Calling merge_sorted_parquet_files with a MergeConfig where num_outputs == 0 — e.g. num_outputs computed as `total_bytes / target_bytes_per_output` with integer division rounding to 0 for very small inputs, or a misconfigured/zero-valued merge config field.","commonSituations":"Integer-division rounding when deriving num_outputs from input size vs. a large target file size; config file with num_outputs = 0; tests constructing MergeConfig::default() variants and forgetting to set the field.","solutions":["Clamp the computed value before calling: `config.num_outputs = config.num_outputs.max(1)` (compute as `.max(1)` after division).","Fix the derivation formula to use saturating/ceiling logic so tiny inputs still yield 1 output.","Validate MergeConfig at construction/deserialization time (reject num_outputs = 0 there with a clearer message).","Check the config source for a literal 0 or an unset field defaulting to 0."],"exampleFix":"// before\nlet num_outputs = total_input_bytes / target_bytes_per_output;\nlet config = MergeConfig { num_outputs, ..base };\n// after\nlet num_outputs = (total_input_bytes / target_bytes_per_output).max(1);\nlet config = MergeConfig { num_outputs, ..base };","handlingStrategy":"validation","validationCode":"if config.num_outputs == 0 {\n    return Err(anyhow::anyhow!(\"num_outputs must be >= 1; got {}\", config.num_outputs));\n}","typeGuard":"fn valid_merge_config(config: &MergeConfig) -> bool {\n    config.num_outputs >= 1\n}","tryCatchPattern":"match merge_sorted_parquet_files(&paths, out_dir, &config) {\n    Err(e) if e.to_string().contains(\"num_outputs must be at least 1\") => {\n        warn!(\"num_outputs=0, retrying with 1\");\n        merge_sorted_parquet_files(&paths, out_dir, &MergeConfig { num_outputs: 1, ..config })\n    }\n    other => other,\n}","preventionTips":["Compute num_outputs with `.max(1)` after integer division.","Validate MergeConfig at construction/deserialization (reject 0 early).","Add a unit test asserting MergeConfig::default() has num_outputs >= 1."],"tags":["rust","merge","config","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"a39730c5cdcd1a4fe798403737ae293999ea21f8","analyzedAt":"2026-09-08T13:19:37.784Z","contentChangedAt":"2026-09-08T13:19:37.784Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}