Skip to content

Commit 71d114d

Browse files
authored
chore(cubestore): exclude stream imports from load-aware placement (cube-js#11159)
1 parent ebad8ed commit 71d114d

1 file changed

Lines changed: 94 additions & 19 deletions

File tree

  • rust/cubestore/cubestore/src/scheduler

rust/cubestore/cubestore/src/scheduler/mod.rs

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,32 @@ impl SchedulerImpl {
12231223
self.cluster.schedule_repartition(p).await
12241224
}
12251225

1226+
// Stateless hash placement of (table_id, location). Empty counts make every worker tie,
1227+
// so pick_import_worker degenerates to the plain hash pick.
1228+
async fn schedule_import_jobs_hashed(
1229+
&self,
1230+
table_id: u64,
1231+
locations: &[&String],
1232+
) -> Result<(), CubeError> {
1233+
let empty = HashMap::new();
1234+
for &l in locations {
1235+
let node = pick_import_worker(self.config.as_ref(), table_id, l, &empty);
1236+
let job = self
1237+
.meta_store
1238+
.add_job(Job::new(
1239+
RowKey::Table(TableId::Tables, table_id),
1240+
JobType::TableImportCSV(l.clone()),
1241+
node.clone(),
1242+
))
1243+
.await?;
1244+
if job.is_some() {
1245+
// TODO queue failover
1246+
self.cluster.notify_job_runner(node).await?;
1247+
}
1248+
}
1249+
Ok(())
1250+
}
1251+
12261252
async fn schedule_table_import(
12271253
&self,
12281254
table_id: u64,
@@ -1233,25 +1259,26 @@ impl SchedulerImpl {
12331259
return Ok(());
12341260
}
12351261

1262+
// Stream imports are long-lived and run on a separate pool; they need stable,
1263+
// deterministic placement, so they always use plain hash placement and are never
1264+
// affected by load-aware placement, which targets bursty CSV file imports only.
1265+
let (csv_locations, stream_locations): (Vec<&String>, Vec<&String>) = locations
1266+
.iter()
1267+
.copied()
1268+
.partition(|&l| !Table::is_stream_location(l));
1269+
1270+
self.schedule_import_jobs_hashed(table_id, &stream_locations)
1271+
.await?;
1272+
1273+
// Stream-only tables (the common reconcile path) have nothing left to place; skip the
1274+
// load-aware lock and the in_flight_import_jobs_by_node scan entirely.
1275+
if csv_locations.is_empty() {
1276+
return Ok(());
1277+
}
1278+
12361279
if !self.config.load_aware_import_placement_enabled() {
1237-
// Default: stateless hash placement of (table_id, location). Empty counts make
1238-
// every worker tie, so pick_import_worker degenerates to the plain hash pick.
1239-
let empty = HashMap::new();
1240-
for &l in locations {
1241-
let node = pick_import_worker(self.config.as_ref(), table_id, l, &empty);
1242-
let job = self
1243-
.meta_store
1244-
.add_job(Job::new(
1245-
RowKey::Table(TableId::Tables, table_id),
1246-
JobType::TableImportCSV(l.clone()),
1247-
node.clone(),
1248-
))
1249-
.await?;
1250-
if job.is_some() {
1251-
// TODO queue failover
1252-
self.cluster.notify_job_runner(node).await?;
1253-
}
1254-
}
1280+
self.schedule_import_jobs_hashed(table_id, &csv_locations)
1281+
.await?;
12551282
return Ok(());
12561283
}
12571284

@@ -1270,7 +1297,7 @@ impl SchedulerImpl {
12701297
);
12711298
}
12721299
let mut counts = self.meta_store.in_flight_import_jobs_by_node().await?;
1273-
for &l in locations {
1300+
for &l in &csv_locations {
12741301
let node = pick_import_worker(self.config.as_ref(), table_id, l, &counts);
12751302
let job = self
12761303
.meta_store
@@ -1687,6 +1714,54 @@ mod tests {
16871714
let _ = fs::remove_dir_all(config.remote_dir());
16881715
}
16891716

1717+
#[tokio::test]
1718+
async fn schedule_table_import_stream_ignores_load_aware() {
1719+
let config =
1720+
Config::test("schedule_table_import_stream_load_aware").update_config(|mut c| {
1721+
c.select_workers = (0..4).map(|i| format!("worker{}", i)).collect();
1722+
c.load_aware_import_placement_enabled = true;
1723+
c
1724+
});
1725+
let (meta_store, scheduler) = test_scheduler(
1726+
"schedule_table_import_stream_load_aware",
1727+
config.config_obj(),
1728+
);
1729+
meta_store
1730+
.create_schema("s".to_string(), false)
1731+
.await
1732+
.unwrap();
1733+
1734+
// Heavily pre-seed worker0 with CSV imports. Stream locations must ignore this load and
1735+
// follow the stateless hash, since load-aware placement targets CSV imports only.
1736+
let seed = create_import_table(&meta_store, "seed").await;
1737+
for i in 0..5 {
1738+
add_import_job(&meta_store, seed, &format!("seed-{}", i), "worker0").await;
1739+
}
1740+
1741+
let table = create_import_table(&meta_store, "bar").await;
1742+
let locs: Vec<String> = (0..6).map(|i| format!("stream:topic-{}", i)).collect();
1743+
let loc_refs: Vec<&String> = locs.iter().collect();
1744+
scheduler
1745+
.schedule_table_import(table, &loc_refs)
1746+
.await
1747+
.unwrap();
1748+
1749+
let placement = import_placement(&meta_store, table).await;
1750+
let empty = HashMap::new();
1751+
for l in &locs {
1752+
let expected = pick_import_worker(config.config_obj().as_ref(), table, l, &empty);
1753+
assert_eq!(
1754+
placement.get(l),
1755+
Some(&expected),
1756+
"stream placement must follow the stateless hash even with load-aware enabled"
1757+
);
1758+
}
1759+
1760+
RocksMetaStore::cleanup_test_metastore("schedule_table_import_stream_load_aware");
1761+
let _ = fs::remove_dir_all(config.local_dir());
1762+
let _ = fs::remove_dir_all(config.remote_dir());
1763+
}
1764+
16901765
#[tokio::test]
16911766
async fn schedule_table_import_hash_placement_by_default() {
16921767
let config = Config::test("schedule_table_import_hash").update_config(|mut c| {

0 commit comments

Comments
 (0)