Skip to content

Commit 2ffdf2e

Browse files
committed
perf(query): adaptively distribute lazy row fetch
1 parent 0cfc0f5 commit 2ffdf2e

29 files changed

Lines changed: 1297 additions & 35 deletions

File tree

src/common/base/src/runtime/profile/profiles.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ pub enum ProfileStatisticsName {
4141
ScanBytesFromDataCache,
4242
ScanBytesFromMemory,
4343

44+
RowFetchRows,
45+
RowFetchBlocks,
46+
RowFetchEstimatedBytes,
47+
RowFetchBatches,
48+
RowFetchLocalBatches,
49+
RowFetchDistributedBatches,
50+
4451
RemoteSpillWriteCount,
4552
RemoteSpillWriteBytes,
4653
RemoteSpillWriteTime,
@@ -65,6 +72,9 @@ pub enum ProfileStatisticsName {
6572
MemoryUsage,
6673
ExternalServerRetryCount,
6774
ExternalServerRequestCount,
75+
76+
RowFetchInputBatches,
77+
RowFetchAffinityReassignedBlocks,
6878
}
6979

7080
#[derive(Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize, Debug)]
@@ -228,6 +238,48 @@ pub fn get_statistics_desc() -> Arc<BTreeMap<ProfileStatisticsName, ProfileDesc>
228238
unit: StatisticsUnit::Bytes,
229239
plain_statistics: true,
230240
}),
241+
(ProfileStatisticsName::RowFetchRows, ProfileDesc {
242+
display_name: "row fetch rows",
243+
desc: "The number of rows requested by RowFetch",
244+
index: ProfileStatisticsName::RowFetchRows as usize,
245+
unit: StatisticsUnit::Rows,
246+
plain_statistics: true,
247+
}),
248+
(ProfileStatisticsName::RowFetchBlocks, ProfileDesc {
249+
display_name: "row fetch blocks",
250+
desc: "The number of distinct block fetches requested by RowFetch batches",
251+
index: ProfileStatisticsName::RowFetchBlocks as usize,
252+
unit: StatisticsUnit::Count,
253+
plain_statistics: true,
254+
}),
255+
(ProfileStatisticsName::RowFetchEstimatedBytes, ProfileDesc {
256+
display_name: "row fetch estimated bytes",
257+
desc: "The estimated uncompressed bytes of columns requested by RowFetch",
258+
index: ProfileStatisticsName::RowFetchEstimatedBytes as usize,
259+
unit: StatisticsUnit::Bytes,
260+
plain_statistics: true,
261+
}),
262+
(ProfileStatisticsName::RowFetchBatches, ProfileDesc {
263+
display_name: "row fetch batches",
264+
desc: "The number of RowFetch batches",
265+
index: ProfileStatisticsName::RowFetchBatches as usize,
266+
unit: StatisticsUnit::Count,
267+
plain_statistics: true,
268+
}),
269+
(ProfileStatisticsName::RowFetchLocalBatches, ProfileDesc {
270+
display_name: "row fetch local batches",
271+
desc: "The number of RowFetch batches kept on the coordinator by adaptive routing",
272+
index: ProfileStatisticsName::RowFetchLocalBatches as usize,
273+
unit: StatisticsUnit::Count,
274+
plain_statistics: true,
275+
}),
276+
(ProfileStatisticsName::RowFetchDistributedBatches, ProfileDesc {
277+
display_name: "row fetch distributed batches",
278+
desc: "The number of RowFetch batches hash-distributed by adaptive routing",
279+
index: ProfileStatisticsName::RowFetchDistributedBatches as usize,
280+
unit: StatisticsUnit::Count,
281+
plain_statistics: true,
282+
}),
231283
(ProfileStatisticsName::RemoteSpillWriteCount, ProfileDesc {
232284
display_name: "numbers remote spilled by write",
233285
desc: "The number of remote spilled by write",
@@ -368,6 +420,20 @@ pub fn get_statistics_desc() -> Arc<BTreeMap<ProfileStatisticsName, ProfileDesc>
368420
unit: StatisticsUnit::Count,
369421
plain_statistics: true,
370422
}),
423+
(ProfileStatisticsName::RowFetchInputBatches, ProfileDesc {
424+
display_name: "row fetch input batches",
425+
desc: "The number of input batches coalesced before adaptive RowFetch routing",
426+
index: ProfileStatisticsName::RowFetchInputBatches as usize,
427+
unit: StatisticsUnit::Count,
428+
plain_statistics: true,
429+
}),
430+
(ProfileStatisticsName::RowFetchAffinityReassignedBlocks, ProfileDesc {
431+
display_name: "row fetch affinity reassigned blocks",
432+
desc: "The number of RowFetch blocks moved from their primary hash destination to bound load skew",
433+
index: ProfileStatisticsName::RowFetchAffinityReassignedBlocks as usize,
434+
unit: StatisticsUnit::Count,
435+
plain_statistics: true,
436+
}),
371437
]))
372438
}).clone()
373439
}

src/common/metrics/src/metrics/storage.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,22 @@ static REMOTE_IO_READ_MILLISECONDS: LazyLock<Histogram> =
147147
LazyLock::new(|| register_histogram_in_milliseconds("fuse_remote_io_read_milliseconds"));
148148
static REMOTE_IO_DESERIALIZE_MILLISECONDS: LazyLock<Histogram> =
149149
LazyLock::new(|| register_histogram_in_milliseconds("fuse_remote_io_deserialize_milliseconds"));
150+
static ROW_FETCH_ROWS: LazyLock<Counter> =
151+
LazyLock::new(|| register_counter("fuse_row_fetch_rows"));
152+
static ROW_FETCH_BLOCKS: LazyLock<Counter> =
153+
LazyLock::new(|| register_counter("fuse_row_fetch_blocks"));
154+
static ROW_FETCH_ESTIMATED_BYTES: LazyLock<Counter> =
155+
LazyLock::new(|| register_counter("fuse_row_fetch_estimated_bytes"));
156+
static ROW_FETCH_BATCHES: LazyLock<Counter> =
157+
LazyLock::new(|| register_counter("fuse_row_fetch_batches"));
158+
static ROW_FETCH_LOCAL_BATCHES: LazyLock<Counter> =
159+
LazyLock::new(|| register_counter("fuse_row_fetch_local_batches"));
160+
static ROW_FETCH_DISTRIBUTED_BATCHES: LazyLock<Counter> =
161+
LazyLock::new(|| register_counter("fuse_row_fetch_distributed_batches"));
162+
static ROW_FETCH_INPUT_BATCHES: LazyLock<Counter> =
163+
LazyLock::new(|| register_counter("fuse_row_fetch_input_batches"));
164+
static ROW_FETCH_AFFINITY_REASSIGNED_BLOCKS: LazyLock<Counter> =
165+
LazyLock::new(|| register_counter("fuse_row_fetch_affinity_reassigned_blocks"));
150166
static BLOCK_WRITE_NUMS: LazyLock<Counter> =
151167
LazyLock::new(|| register_counter("fuse_block_write_nums"));
152168
static BLOCK_WRITE_BYTES: LazyLock<Counter> =
@@ -589,6 +605,38 @@ pub fn metrics_inc_remote_io_deserialize_milliseconds(c: u64) {
589605
REMOTE_IO_DESERIALIZE_MILLISECONDS.observe(c as f64);
590606
}
591607

608+
pub fn metrics_inc_row_fetch_rows(c: u64) {
609+
ROW_FETCH_ROWS.inc_by(c);
610+
}
611+
612+
pub fn metrics_inc_row_fetch_blocks(c: u64) {
613+
ROW_FETCH_BLOCKS.inc_by(c);
614+
}
615+
616+
pub fn metrics_inc_row_fetch_estimated_bytes(c: u64) {
617+
ROW_FETCH_ESTIMATED_BYTES.inc_by(c);
618+
}
619+
620+
pub fn metrics_inc_row_fetch_batches(c: u64) {
621+
ROW_FETCH_BATCHES.inc_by(c);
622+
}
623+
624+
pub fn metrics_inc_row_fetch_local_batches(c: u64) {
625+
ROW_FETCH_LOCAL_BATCHES.inc_by(c);
626+
}
627+
628+
pub fn metrics_inc_row_fetch_distributed_batches(c: u64) {
629+
ROW_FETCH_DISTRIBUTED_BATCHES.inc_by(c);
630+
}
631+
632+
pub fn metrics_inc_row_fetch_input_batches(c: u64) {
633+
ROW_FETCH_INPUT_BATCHES.inc_by(c);
634+
}
635+
636+
pub fn metrics_inc_row_fetch_affinity_reassigned_blocks(c: u64) {
637+
ROW_FETCH_AFFINITY_REASSIGNED_BLOCKS.inc_by(c);
638+
}
639+
592640
/// Block metrics.
593641
pub fn metrics_inc_block_write_nums(c: u64) {
594642
BLOCK_WRITE_NUMS.inc_by(c);

src/query/service/src/physical_plans/format/format_exchange.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ impl<'a> PhysicalFormat for ExchangeFormatter<'a> {
5757
.collect::<Vec<_>>()
5858
.join(", ")
5959
),
60+
FragmentKind::RowFetch {
61+
local_block_threshold,
62+
..
63+
} => format!(
64+
"Adaptive RowFetch(Hash({}), local blocks <= {})",
65+
self.inner
66+
.keys
67+
.iter()
68+
.map(|key| { key.as_expr(&BUILTIN_FUNCTIONS).sql_display() })
69+
.collect::<Vec<_>>()
70+
.join(", "),
71+
local_block_threshold
72+
),
6073
FragmentKind::Expansive => "Broadcast".to_string(),
6174
FragmentKind::Merge => "Merge".to_string(),
6275
FragmentKind::GlobalShuffle => format!(

src/query/service/src/physical_plans/physical_limit.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,31 @@ use std::any::Any;
1616
use std::collections::HashMap;
1717

1818
use databend_common_catalog::plan::DataSourcePlan;
19+
use databend_common_catalog::plan::NUM_ROW_ID_PREFIX_BITS;
1920
use databend_common_exception::ErrorCode;
2021
use databend_common_exception::Result;
22+
use databend_common_expression::ColumnRef;
23+
use databend_common_expression::Constant;
2124
use databend_common_expression::DataField;
25+
use databend_common_expression::Expr;
2226
use databend_common_expression::ROW_ID_COL_NAME;
27+
use databend_common_expression::Scalar;
28+
use databend_common_expression::type_check::check_function;
29+
use databend_common_expression::types::DataType;
30+
use databend_common_expression::types::NumberDataType;
31+
use databend_common_functions::BUILTIN_FUNCTIONS;
2332
use databend_common_pipeline::core::ProcessorPtr;
2433
use databend_common_pipeline_transforms::filters::TransformLimit;
2534
use databend_common_sql::ColumnEntry;
2635
use databend_common_sql::ColumnSet;
2736
use databend_common_sql::IndexType;
2837
use databend_common_sql::Symbol;
38+
use databend_common_sql::executor::physical_plans::FragmentKind;
2939
use databend_common_sql::optimizer::ir::SExpr;
3040

41+
use crate::physical_plans::Exchange;
3142
use crate::physical_plans::PhysicalPlanBuilder;
43+
use crate::physical_plans::Sort;
3244
use crate::physical_plans::explain::PlanStatsInfo;
3345
use crate::physical_plans::format::LimitFormatter;
3446
use crate::physical_plans::format::PhysicalFormat;
@@ -37,6 +49,7 @@ use crate::physical_plans::physical_plan::PhysicalPlan;
3749
use crate::physical_plans::physical_plan::PhysicalPlanCast;
3850
use crate::physical_plans::physical_plan::PhysicalPlanMeta;
3951
use crate::physical_plans::physical_row_fetch::RowFetch;
52+
use crate::physical_plans::physical_sort::SortStep;
4053
use crate::pipelines::PipelineBuilder;
4154

4255
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -130,6 +143,44 @@ impl IPhysicalPlan for Limit {
130143
}
131144

132145
impl PhysicalPlanBuilder {
146+
fn contains_merge_exchange(plan: &PhysicalPlan) -> bool {
147+
if let Some(exchange) = Exchange::from_physical_plan(plan)
148+
&& exchange.kind == FragmentKind::Merge
149+
{
150+
return true;
151+
}
152+
153+
plan.children().any(Self::contains_merge_exchange)
154+
}
155+
156+
fn build_row_fetch_shuffle_key(
157+
input_schema: &databend_common_expression::DataSchema,
158+
row_id_col_offset: usize,
159+
) -> Result<databend_common_expression::RemoteExpr> {
160+
let row_id_field = input_schema.field(row_id_col_offset);
161+
let row_id_expr = Expr::ColumnRef(ColumnRef {
162+
span: None,
163+
id: row_id_col_offset,
164+
data_type: row_id_field.data_type().clone(),
165+
display_name: row_id_field.name().to_string(),
166+
});
167+
let block_prefix = check_function(
168+
None,
169+
"bit_shift_right",
170+
&[],
171+
&[
172+
row_id_expr,
173+
Expr::Constant(Constant {
174+
span: None,
175+
scalar: Scalar::Number(((64 - NUM_ROW_ID_PREFIX_BITS) as u64).into()),
176+
data_type: DataType::Number(NumberDataType::UInt64),
177+
}),
178+
],
179+
&BUILTIN_FUNCTIONS,
180+
)?;
181+
Ok(block_prefix.as_remote_expr())
182+
}
183+
133184
pub async fn build_limit(
134185
&mut self,
135186
s_expr: &SExpr,
@@ -164,6 +215,7 @@ impl PhysicalPlanBuilder {
164215
}
165216

166217
// If `lazy_columns` is not empty, build a `RowFetch` plan on top of the `Limit` plan.
218+
let order_by = Sort::from_physical_plan(&input_plan).map(|sort| sort.order_by.clone());
167219
let mut plan = PhysicalPlan::new(Limit {
168220
meta: PhysicalPlanMeta::new("Limit"),
169221
input: input_plan,
@@ -220,6 +272,16 @@ impl PhysicalPlanBuilder {
220272

221273
let mut table_indexes: Vec<IndexType> = lazy_columns_by_table.keys().copied().collect();
222274
table_indexes.sort_unstable();
275+
let max_threads = self.ctx.get_settings().get_max_threads()? as usize;
276+
// A local RowFetch already reads up to `max_threads` blocks concurrently. Avoid a
277+
// cluster round trip for small limits, where all possible blocks fit in only a few
278+
// local I/O waves. The runtime metrics below let us tune this conservative guard.
279+
let distribute_row_fetch = table_indexes.len() == 1
280+
&& limit
281+
.limit
282+
.is_some_and(|rows| rows > max_threads.saturating_mul(4))
283+
&& !self.ctx.get_cluster().is_empty()
284+
&& Self::contains_merge_exchange(&plan);
223285

224286
let mut row_id_col_offsets = HashMap::new();
225287
for table_index in table_indexes.iter() {
@@ -288,6 +350,22 @@ impl PhysicalPlanBuilder {
288350
))
289351
})?;
290352

353+
if distribute_row_fetch {
354+
let shuffle_key =
355+
Self::build_row_fetch_shuffle_key(input_schema.as_ref(), row_id_col_offset)?;
356+
plan = PhysicalPlan::new(Exchange {
357+
input: plan,
358+
kind: FragmentKind::RowFetch {
359+
row_id_col_offset,
360+
local_block_threshold: max_threads.saturating_mul(8).max(128),
361+
},
362+
keys: vec![shuffle_key],
363+
ignore_exchange: false,
364+
allow_adjust_parallelism: true,
365+
meta: PhysicalPlanMeta::new("Exchange"),
366+
});
367+
}
368+
291369
plan = PhysicalPlan::new(RowFetch {
292370
meta: PhysicalPlanMeta::new("RowFetch"),
293371
input: plan,
@@ -299,6 +377,33 @@ impl PhysicalPlanBuilder {
299377
enable_block_id_repartition: false,
300378
stat_info: Some(stat_info.clone()),
301379
});
380+
381+
if distribute_row_fetch {
382+
plan = PhysicalPlan::new(Exchange {
383+
input: plan,
384+
kind: FragmentKind::Merge,
385+
keys: vec![],
386+
ignore_exchange: false,
387+
allow_adjust_parallelism: true,
388+
meta: PhysicalPlanMeta::new("Exchange"),
389+
});
390+
391+
if let Some(order_by) = order_by.clone()
392+
&& !order_by.is_empty()
393+
{
394+
plan = PhysicalPlan::new(Sort {
395+
input: plan,
396+
order_by,
397+
limit: None,
398+
step: SortStep::Single,
399+
pre_projection: None,
400+
broadcast_id: None,
401+
enable_fixed_rows: false,
402+
stat_info: Some(stat_info.clone()),
403+
meta: PhysicalPlanMeta::new("Sort"),
404+
});
405+
}
406+
}
302407
}
303408

304409
Ok(plan)

0 commit comments

Comments
 (0)