{"record":{"id":"564d02792ed4dcaa","repo":"quickwit-oss/quickwit","slug":"attempted-to-merge-splits-with-different-doc-mappi","errorCode":null,"errorMessage":"attempted to merge splits with different doc mapping uid","messagePattern":"attempted to merge splits with different doc mapping uid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"quickwit/quickwit-indexing/src/actors/merge_executor.rs","lineNumber":299,"sourceCode":"    let num_docs = sum_num_docs(splits);\n    let replaced_split_ids: Vec<SplitId> = splits\n        .iter()\n        .map(|split| split.split_id().clone())\n        .collect();\n    let delete_opstamp = splits\n        .iter()\n        .map(|split| split.delete_opstamp)\n        .min()\n        .unwrap_or(0);\n    let doc_mapping_uid = splits\n        .first()\n        .ok_or_else(|| anyhow::anyhow!(\"attempted to merge zero splits\"))?\n        .doc_mapping_uid;\n    if splits\n        .iter()\n        .any(|split| split.doc_mapping_uid != doc_mapping_uid)\n    {\n        anyhow::bail!(\"attempted to merge splits with different doc mapping uid\");\n    }\n    Ok(SplitAttrs {\n        node_id: pipeline_id.node_id.clone(),\n        index_uid: pipeline_id.index_uid.clone(),\n        source_id: pipeline_id.source_id.clone(),\n        doc_mapping_uid,\n        split_id: merge_split_id,\n        partition_id,\n        replaced_split_ids,\n        time_range,\n        num_docs,\n        uncompressed_docs_size_in_bytes,\n        delete_opstamp,\n        num_merge_ops: max_merge_ops(splits) + 1,\n    })\n}\n\nfn max_merge_ops(splits: &[SplitMetadata]) -> usize {","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/quickwit-oss/quickwit/blob/a39730c5cdcd1a4fe798403737ae293999ea21f8/quickwit/quickwit-indexing/src/actors/merge_executor.rs#L281-L317","documentation":"`merge_split_attrs` verifies that all splits scheduled for a merge share the same `doc_mapping_uid`, which identifies the doc mapper version that produced them. Merging splits from different mapping versions would produce an inconsistent merged split, so it is refused.","triggerScenarios":"A merge task (via `process_merge` or `fake_merge`) receives a list of splits whose `doc_mapping_uid` values differ — typically stale splits left over after the index config's schema was changed and reloaded.","commonSituations":"Index config updated (new doc mapping uid) while older splits from the previous mapping still exist; control plane scheduling a merge across mapping-version boundaries; replayed or copied splits from an older index generation.","solutions":["Wait for the publisher/merge pipeline to drain, or exclude splits with an older `doc_mapping_uid` from the merge batch.","Re-trigger merge scheduling so it groups splits by doc mapping version.","If old-generation splits are obsolete, let retention/delete tasks remove them instead of merging."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"fn merges_are_compatible(splits: &[Split]) -> bool {\n    let uid = splits[0].doc_mapping_uid;\n    splits.iter().all(|s| s.doc_mapping_uid == uid)\n}","typeGuard":null,"tryCatchPattern":"match merge_split_attrs(&splits, &pipeline_id) {\n    Ok(attrs) => { /* proceed with merge */ }\n    Err(e) if e.to_string().contains(\"different doc mapping uid\") => {\n        // skip/regroup this merge batch by doc_mapping_uid\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Group merge tasks by doc_mapping_uid before scheduling.","After changing an index config/schema, expect a transition period with mixed generations.","Monitor for stale splits and let retention policies clean old-generation splits."],"tags":["merge","splits","invariant"],"backgroundTag":"internal-invariant-violation","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"}