Skip to content

Commit 0c8cd24

Browse files
authored
feat(cubestore): top-k merge strategies via CUBESTORE_TOPK_STRATEGY (cube-js#11152)
1 parent 9da8e07 commit 0c8cd24

7 files changed

Lines changed: 1034 additions & 41 deletions

File tree

rust/cubestore/cubestore/src/config/mod.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,9 @@ pub trait ConfigObj: DIService {
600600
/// partition coalesce (the hash aggregate ignores input order, so the per-row merge is wasted).
601601
fn coalesce_under_hash_aggregate(&self) -> bool;
602602

603+
/// Router-side merge strategy for distributed value-ordered top-k.
604+
fn topk_aggregate_strategy(&self) -> TopKAggregateStrategy;
605+
603606
fn allow_decimal128(&self) -> bool;
604607

605608
fn enable_remove_orphaned_remote_files(&self) -> bool;
@@ -762,6 +765,7 @@ pub struct ConfigObjImpl {
762765
pub group_by_limit_factor: usize,
763766
pub group_by_limit_per_partition: bool,
764767
pub coalesce_under_hash_aggregate: bool,
768+
pub topk_aggregate_strategy: TopKAggregateStrategy,
765769
pub allow_decimal128: bool,
766770
pub enable_remove_orphaned_remote_files: bool,
767771
pub enable_startup_warmup: bool,
@@ -1116,6 +1120,10 @@ impl ConfigObj for ConfigObjImpl {
11161120
self.coalesce_under_hash_aggregate
11171121
}
11181122

1123+
fn topk_aggregate_strategy(&self) -> TopKAggregateStrategy {
1124+
self.topk_aggregate_strategy
1125+
}
1126+
11191127
fn allow_decimal128(&self) -> bool {
11201128
self.allow_decimal128
11211129
}
@@ -1291,6 +1299,43 @@ pub async fn init_test_logger() {
12911299
}
12921300
}
12931301

1302+
/// Router-side merge strategy for distributed value-ordered top-k (`SELECT ... GROUP BY x ORDER BY
1303+
/// agg(...) LIMIT k`). Selected by `CUBESTORE_TOPK_STRATEGY`.
1304+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1305+
pub enum TopKAggregateStrategy {
1306+
/// Original streaming NRA merge with per-row state (default).
1307+
Streaming,
1308+
/// Same streaming NRA merge (keeps early termination, bounded router memory), but vectorized.
1309+
VectorizedStreaming,
1310+
/// ClickHouse-style full re-aggregation on the router + fetch-limited sort. Drops early
1311+
/// termination, so the router materializes every distinct group.
1312+
FullMerge,
1313+
}
1314+
1315+
/// Parses [`TopKAggregateStrategy`] from an env var. Lenient: an unset or unrecognized value falls
1316+
/// back to the default (`Streaming`) with a warning rather than panicking -- a malformed perf toggle
1317+
/// must never take a node down.
1318+
fn env_topk_strategy(name: &str) -> TopKAggregateStrategy {
1319+
match env::var(name) {
1320+
Err(_) => TopKAggregateStrategy::Streaming,
1321+
Ok(v) => match v.trim().to_ascii_lowercase().as_str() {
1322+
"" | "streaming" | "default" | "v1" => TopKAggregateStrategy::Streaming,
1323+
"vectorized" | "vectorized_streaming" | "v2" => {
1324+
TopKAggregateStrategy::VectorizedStreaming
1325+
}
1326+
"full_merge" | "full-merge" | "fullmerge" => TopKAggregateStrategy::FullMerge,
1327+
other => {
1328+
log::warn!(
1329+
"unknown {} value '{}', using default (streaming)",
1330+
name,
1331+
other
1332+
);
1333+
TopKAggregateStrategy::Streaming
1334+
}
1335+
},
1336+
}
1337+
}
1338+
12941339
fn env_bool(name: &str, default: bool) -> bool {
12951340
env::var(name)
12961341
.ok()
@@ -1846,6 +1891,7 @@ impl Config {
18461891
group_by_limit_factor: env_parse_lenient("CUBESTORE_GROUP_BY_LIMIT_FACTOR", 0),
18471892
group_by_limit_per_partition: env_flag("CUBESTORE_GROUP_BY_LIMIT_PER_PARTITION"),
18481893
coalesce_under_hash_aggregate: env_flag("CUBESTORE_COALESCE_UNDER_HASH_AGGREGATE"),
1894+
topk_aggregate_strategy: env_topk_strategy("CUBESTORE_TOPK_STRATEGY"),
18491895
allow_decimal128: env_bool("CUBESTORE_ALLOW_DECIMAL128", false),
18501896
enable_remove_orphaned_remote_files: env_bool(
18511897
"CUBESTORE_ENABLE_REMOVE_ORPHANED_REMOTE_FILES",
@@ -2105,6 +2151,7 @@ impl Config {
21052151
group_by_limit_factor: 2,
21062152
group_by_limit_per_partition: false,
21072153
coalesce_under_hash_aggregate: false,
2154+
topk_aggregate_strategy: TopKAggregateStrategy::Streaming,
21082155
allow_decimal128: false,
21092156
enable_remove_orphaned_remote_files: false,
21102157
enable_startup_warmup: true,

rust/cubestore/cubestore/src/queryplanner/optimizations/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod trace_data_loaded;
88

99
use super::serialized_plan::PreSerializedPlan;
1010
use crate::cluster::{Cluster, WorkerPlanningParams};
11+
use crate::config::TopKAggregateStrategy;
1112
use crate::queryplanner::optimizations::distributed_partial_aggregate::{
1213
add_limit_to_workers, drop_sort_merge_under_global_aggregate, ensure_partition_merge,
1314
push_aggregate_to_workers, push_sorted_partial_aggregate_below_merge,
@@ -45,6 +46,7 @@ pub struct CubeQueryPlanner {
4546
data_loaded_size: Option<Arc<DataLoadedSize>>,
4647
group_by_limit_factor: usize,
4748
group_by_limit_per_partition: bool,
49+
topk_strategy: TopKAggregateStrategy,
4850
}
4951

5052
impl CubeQueryPlanner {
@@ -54,6 +56,7 @@ impl CubeQueryPlanner {
5456
memory_handler: Arc<dyn MemoryHandler>,
5557
group_by_limit_factor: usize,
5658
group_by_limit_per_partition: bool,
59+
topk_strategy: TopKAggregateStrategy,
5760
) -> CubeQueryPlanner {
5861
CubeQueryPlanner {
5962
cluster: Some(cluster),
@@ -63,6 +66,7 @@ impl CubeQueryPlanner {
6366
data_loaded_size: None,
6467
group_by_limit_factor,
6568
group_by_limit_per_partition,
69+
topk_strategy,
6670
}
6771
}
6872

@@ -73,6 +77,7 @@ impl CubeQueryPlanner {
7377
data_loaded_size: Option<Arc<DataLoadedSize>>,
7478
group_by_limit_factor: usize,
7579
group_by_limit_per_partition: bool,
80+
topk_strategy: TopKAggregateStrategy,
7681
) -> CubeQueryPlanner {
7782
CubeQueryPlanner {
7883
serialized_plan,
@@ -82,6 +87,7 @@ impl CubeQueryPlanner {
8287
data_loaded_size,
8388
group_by_limit_factor,
8489
group_by_limit_per_partition,
90+
topk_strategy,
8591
}
8692
}
8793
}
@@ -104,6 +110,7 @@ impl QueryPlanner for CubeQueryPlanner {
104110
cluster: self.cluster.clone(),
105111
worker_planning_params: self.worker_partition_count,
106112
serialized_plan: self.serialized_plan.clone(),
113+
topk_strategy: self.topk_strategy,
107114
}),
108115
Arc::new(RollingWindowPlanner {}),
109116
])

rust/cubestore/cubestore/src/queryplanner/planning.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use std::any::Any;
3333
use std::fmt::Formatter;
3434

3535
use crate::cluster::{Cluster, WorkerPlanningParams};
36+
use crate::config::TopKAggregateStrategy;
3637
use crate::metastore::multi_index::MultiPartition;
3738
use crate::metastore::table::{Table, TablePath};
3839
use crate::metastore::{
@@ -1970,6 +1971,7 @@ pub struct CubeExtensionPlanner {
19701971
// Set on the workers.
19711972
pub worker_planning_params: Option<WorkerPlanningParams>,
19721973
pub serialized_plan: Arc<PreSerializedPlan>,
1974+
pub topk_strategy: TopKAggregateStrategy,
19731975
}
19741976

19751977
#[async_trait]

rust/cubestore/cubestore/src/queryplanner/query_executor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ impl QueryExecutorImpl {
569569
self.memory_handler.clone(),
570570
self.config.group_by_limit_factor(),
571571
self.config.group_by_limit_per_partition(),
572+
self.config.topk_aggregate_strategy(),
572573
))
573574
}
574575

@@ -586,6 +587,7 @@ impl QueryExecutorImpl {
586587
data_loaded_size.clone(),
587588
self.config.group_by_limit_factor(),
588589
self.config.group_by_limit_per_partition(),
590+
self.config.topk_aggregate_strategy(),
589591
))
590592
}
591593

0 commit comments

Comments
 (0)