Listing all parts in log have no sense. 
Should be either silenced, either combined into a single line.
pool.scheduleOrThrowOnError([&, part_index, thread_group = CurrentThread::getGroup()] {
                    SCOPE_EXIT(
                        if (thread_group)
                            CurrentThread::detachQueryIfNotDetached();
                    );
                    if (thread_group)
                        CurrentThread::attachTo(thread_group);
                    process_part(part_index);
                });
            pool.wait();
        }
        
        size_t next_part = 0;
        for (size_t part_index = 0; part_index < parts.size(); ++part_index)
        {
            auto & part = parts_with_ranges[part_index];
            if (!part.data_part)
                continue;
            sum_ranges += part.ranges.size();
            sum_marks += part.getMarksCount();
            if (next_part != part_index)
                std::swap(parts_with_ranges[next_part], part);
            ++next_part;
        }
        parts_with_ranges.resize(next_part);
    }
    LOG_DEBUG(log, "Selected {} parts by partition key, {} parts by primary key, {} marks by primary key, {} marks to read from {} ranges", parts.size(), parts_with_ranges.size(), sum_marks_pk.load(std::memory_order_relaxed), sum_marks, sum_ranges);
    if (parts_with_ranges.empty())
        return std::make_unique<QueryPlan>();
    const auto data_settings = data.getSettings();