`query_id` must be the node-independent distributed query id: it keys the in-memory and streaming exchanges, so producers and consumers on different nodes (and the cleanup paths) must agree on it. It must not embed any node-local object-storage subpath, which would differ between nodes.
| 520 | /// exchanges, so producers and consumers on different nodes (and the cleanup paths) must agree on it. |
| 521 | /// It must not embed any node-local object-storage subpath, which would differ between nodes. |
| 522 | ExchangeLookupPtr createExchangeLookup( |
| 523 | const String & query_id, |
| 524 | const ExchangeDescriptions & exchanges_, |
| 525 | const ExchangeStreamSources & exchange_stream_sources, |
| 526 | TemporaryFileLookupPtr temporary_files_, |
| 527 | ContextPtr context) |
| 528 | { |
| 529 | bool run_locally = context->getSettingsRef()[Setting::distributed_plan_execute_locally]; |
| 530 | if (run_locally) |
| 531 | { |
| 532 | LOG_DEBUG(getLogger("createExchangeLookup"), "`distributed_plan_execute_locally` setting is enabled, using in-memory queues for all exchanges"); |
| 533 | return std::make_shared<ExchangeViaChunks>(query_id); |
| 534 | } |
| 535 | |
| 536 | auto persisted_exchanges = std::make_shared<ExchangeViaTemporaryFiles>(temporary_files_); |
| 537 | |
| 538 | bool has_streaming_exchange = false; |
| 539 | for (const auto & [exchange_id, exchange] : exchanges_) |
| 540 | if (exchange.kind == ExchangeDescription::Kind::Streaming) |
| 541 | { |
| 542 | has_streaming_exchange = true; |
| 543 | break; |
| 544 | } |
| 545 | |
| 546 | /// Persisted exchanges only need the temporary-file lookup, so a plan where every exchange |
| 547 | /// is Persisted runs without a streaming transport (and on any platform). The streaming |
| 548 | /// port and lookup are required only when the plan actually contains a Streaming exchange. |
| 549 | if (!has_streaming_exchange) |
| 550 | { |
| 551 | UNUSED(exchange_stream_sources); |
| 552 | return std::make_shared<AllKindsExchangeLookup>(exchanges_, persisted_exchanges, /*streaming_exchange_lookup=*/nullptr); |
| 553 | } |
| 554 | |
| 555 | #ifdef OS_LINUX |
| 556 | auto streaming_exchange_port = context->getConfigRef().getUInt("distributed_query.streaming_exchange_port", 0); |
| 557 | if (streaming_exchange_port == 0) |
| 558 | throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, |
| 559 | "Streaming exchange requires `distributed_query.streaming_exchange_port` to be configured; " |
| 560 | "set the port, force `distributed_plan_force_exchange_kind = 'Persisted'`, or enable " |
| 561 | "`distributed_plan_execute_locally` for in-process testing"); |
| 562 | if (streaming_exchange_port > 65535) |
| 563 | throw Exception(ErrorCodes::INVALID_CONFIG_PARAMETER, |
| 564 | "`distributed_query.streaming_exchange_port` must be in range 1..65535, got {}", streaming_exchange_port); |
| 565 | |
| 566 | /// The listener starts only when a listen host is also configured, so streaming peers are |
| 567 | /// unreachable without one. Reject here instead of connecting to a listener that never started. |
| 568 | if (getMultipleValuesFromConfig(context->getConfigRef(), "distributed_query", "streaming_exchange_listen_host").empty()) |
| 569 | throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, |
| 570 | "Streaming exchange requires `distributed_query.streaming_exchange_listen_host` to be configured; " |
| 571 | "set it, force `distributed_plan_force_exchange_kind = 'Persisted'`, or enable " |
| 572 | "`distributed_plan_execute_locally` for in-process testing"); |
| 573 | |
| 574 | /// A task from an older initiator (version 1) ships no per-stream ports; fall back to this |
| 575 | /// node's configured exchange port to preserve the previous single-port behavior. |
| 576 | ExchangeStreamSources sources_with_ports = exchange_stream_sources; |
| 577 | for (auto & [stream, address] : sources_with_ports.stream_hosts) |
| 578 | if (address.port == 0) |
| 579 | address.port = static_cast<UInt16>(streaming_exchange_port); |
no test coverage detected