| 3571 | } |
| 3572 | |
| 3573 | void ReadFromMergeTree::initializePipeline(QueryPipelineBuilder & pipeline, [[maybe_unused]] const BuildQueryPipelineSettings & settings) |
| 3574 | { |
| 3575 | auto & result = getAnalysisResult(); |
| 3576 | |
| 3577 | logPredicateStatistics(result); |
| 3578 | |
| 3579 | /// Filter ranges by 'bucket_id' parameter so that each distributed worker reads only its slice of the parts. |
| 3580 | if (distributed_read_bucket_count > 0 && settings.parameter_lookup) |
| 3581 | { |
| 3582 | /// Bucket over the coordinator-selected parts in a fixed order, so every worker partitions the |
| 3583 | /// same ordered list (replicas can have different local part layouts). A missing part is a |
| 3584 | /// retryable error rather than a silently divergent read. |
| 3585 | if (!distributed_read_part_names.empty()) |
| 3586 | { |
| 3587 | std::unordered_map<String, RangesInDataPart> parts_by_name; |
| 3588 | for (auto & part : result.parts_with_ranges) |
| 3589 | parts_by_name.emplace(part.data_part->name, std::move(part)); |
| 3590 | |
| 3591 | RangesInDataParts coordinator_parts; |
| 3592 | coordinator_parts.reserve(distributed_read_part_names.size()); |
| 3593 | for (const auto & part_name : distributed_read_part_names) |
| 3594 | { |
| 3595 | auto it = parts_by_name.find(part_name); |
| 3596 | if (it == parts_by_name.end()) |
| 3597 | throw Exception(ErrorCodes::NO_SUCH_DATA_PART, |
| 3598 | "Distributed read: part {} selected by the coordinator is not available on this replica " |
| 3599 | "(diverged by merge or replication lag); retry the query", part_name); |
| 3600 | coordinator_parts.push_back(std::move(it->second)); |
| 3601 | } |
| 3602 | result.parts_with_ranges = std::move(coordinator_parts); |
| 3603 | } |
| 3604 | |
| 3605 | const size_t bucket_id = parse<UInt64>(settings.parameter_lookup->getParameter("bucket_id").safeGet<String>()); |
| 3606 | const size_t total_buckets = settings.parameter_lookup->getParameter("total_buckets").safeGet<UInt64>(); |
| 3607 | |
| 3608 | size_t effective_bucket_index = bucket_id; |
| 3609 | RangesInDataParts filtered_parts; |
| 3610 | for (const auto & part : result.parts_with_ranges) |
| 3611 | { |
| 3612 | auto filtered_part = part; |
| 3613 | filtered_part.ranges = filterMarkRangesForBucket(part.ranges, effective_bucket_index, total_buckets); |
| 3614 | if (!filtered_part.ranges.empty()) |
| 3615 | filtered_parts.push_back(std::move(filtered_part)); |
| 3616 | } |
| 3617 | result.parts_with_ranges = std::move(filtered_parts); |
| 3618 | |
| 3619 | /// Cannot cache PREWHERE results when ranges are filtered by bucket_id. |
| 3620 | reader_settings.use_query_condition_cache = false; |
| 3621 | } |
| 3622 | |
| 3623 | if (enable_remove_parts_from_snapshot_optimization || query_info.isStream()) |
| 3624 | { |
| 3625 | /// Do not keep data parts in snapshot. |
| 3626 | /// They are stored separately, and some could be released after PK analysis. |
| 3627 | /// Keep the underlying storage alive because part teardown still reaches |
| 3628 | /// `data_part->storage.getContext()`. |
| 3629 | auto stripped_snapshot_data = std::make_unique<MergeTreeData::SnapshotData>(); |
| 3630 | if (const auto * snapshot_data = dynamic_cast<const MergeTreeData::SnapshotData *>(storage_snapshot->data.get())) |
nothing calls this directly
no test coverage detected