Skip to content

Commit c912960

Browse files
committed
Give indexing plan a time-based ID; use it to tiebreak publish tokens
1 parent 74b39ac commit c912960

11 files changed

Lines changed: 264 additions & 33 deletions

File tree

quickwit/quickwit-control-plane/src/indexing_scheduler/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use quickwit_proto::types::NodeId;
3535
use scheduling::{SourceToSchedule, SourceToScheduleType};
3636
use serde::Serialize;
3737
use tracing::{debug, info, warn};
38+
use ulid::Ulid;
3839

3940
use crate::indexing_plan::PhysicalIndexingPlan;
4041
use crate::indexing_scheduler::change_tracker::{NotifyChangeOnDrop, RebuildNotifier};
@@ -414,11 +415,16 @@ impl IndexingScheduler {
414415
) {
415416
debug!(new_physical_plan=?new_physical_plan, "apply physical indexing plan");
416417
APPLY_PLAN_TOTAL.inc();
418+
// ULIDs are time-ordered, so a later application always yields a greater id. Indexers use
419+
// it as the publish token for the shards they acquire. This guarantees a stale plan
420+
// can never steal a shard from a more recent one.
421+
let indexing_plan_id = Ulid::new().to_string();
417422
for (node_id, indexing_tasks) in new_physical_plan.indexing_tasks_per_indexer() {
418423
// We don't want to block on a slow indexer so we apply this change asynchronously
419424
// TODO not blocking is cool, but we need to make sure there is not accumulation
420425
// possible here.
421426
let notify_on_drop = notify_on_drop.clone();
427+
let indexing_plan_id = indexing_plan_id.clone();
422428
tokio::spawn({
423429
let indexer = indexers
424430
.iter()
@@ -430,7 +436,10 @@ impl IndexingScheduler {
430436
if let Err(error) = indexer
431437
.client
432438
.clone()
433-
.apply_indexing_plan(ApplyIndexingPlanRequest { indexing_tasks })
439+
.apply_indexing_plan(ApplyIndexingPlanRequest {
440+
indexing_tasks,
441+
indexing_plan_id,
442+
})
434443
.await
435444
{
436445
warn!(

quickwit/quickwit-indexing/src/actors/indexing_pipeline.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ pub struct IndexingPipeline {
8989
// requiring a respawn of the pipeline.
9090
// We keep the list of shards here however, to reassign them after a respawn.
9191
shard_ids: BTreeSet<ShardId>,
92+
// Id of the last indexing plan assigned to this pipeline. Kept here, like `shard_ids`, so it
93+
// can be re-sent to the source on respawn; the source adopts it as its publish token.
94+
indexing_plan_id: String,
9295
_indexing_pipelines_gauge_guard: GaugeGuard,
9396
}
9497

@@ -137,6 +140,7 @@ impl IndexingPipeline {
137140
..Default::default()
138141
},
139142
shard_ids: Default::default(),
143+
indexing_plan_id: String::new(),
140144
_indexing_pipelines_gauge_guard: indexing_pipelines_gauge_guard,
141145
}
142146
}
@@ -402,6 +406,7 @@ impl IndexingPipeline {
402406
.spawn(actor_source);
403407
let assign_shards_message = AssignShards(Assignment {
404408
shard_ids: self.shard_ids.clone(),
409+
indexing_plan_id: self.indexing_plan_id.clone(),
405410
});
406411
source_mailbox.send_message(assign_shards_message).await?;
407412

@@ -496,6 +501,8 @@ impl Handler<AssignShards> for IndexingPipeline {
496501
) -> Result<(), ActorExitStatus> {
497502
self.shard_ids
498503
.clone_from(&assign_shards_message.0.shard_ids);
504+
self.indexing_plan_id
505+
.clone_from(&assign_shards_message.0.indexing_plan_id);
499506
// If the pipeline is running, we forward the message to its source.
500507
// If it is not, it will be respawned soon, and the shards will be assigned afterward.
501508
if let Some(handles) = &self.handles_opt {

quickwit/quickwit-indexing/src/actors/indexing_service.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -760,8 +760,8 @@ impl IndexingService {
760760
/// or not.
761761
///
762762
/// If a pipeline actor has failed, this function just logs an error.
763-
async fn assign_shards_to_pipelines(&mut self, tasks: &[IndexingTask]) {
764-
for task in tasks {
763+
async fn assign_shards_to_pipelines(&mut self, plan_request: &ApplyIndexingPlanRequest) {
764+
for task in &plan_request.indexing_tasks {
765765
if task.shard_ids.is_empty() {
766766
continue;
767767
}
@@ -771,6 +771,7 @@ impl IndexingService {
771771
};
772772
let assignment = Assignment {
773773
shard_ids: task.shard_ids.iter().cloned().collect(),
774+
indexing_plan_id: plan_request.indexing_plan_id.clone(),
774775
};
775776
let message = AssignShards(assignment);
776777

@@ -785,9 +786,10 @@ impl IndexingService {
785786
/// - Starting the pipelines that are not running.
786787
async fn apply_indexing_plan(
787788
&mut self,
788-
tasks: &[IndexingTask],
789+
plan_request: ApplyIndexingPlanRequest,
789790
ctx: &ActorContext<Self>,
790791
) -> Result<(), IndexingError> {
792+
let tasks = &plan_request.indexing_tasks;
791793
let pipeline_diff = self.compute_pipeline_diff(tasks);
792794

793795
if !pipeline_diff.pipelines_to_shutdown.is_empty() {
@@ -801,7 +803,7 @@ impl IndexingService {
801803
.spawn_pipelines(&pipeline_diff.pipelines_to_spawn, ctx)
802804
.await?;
803805
}
804-
self.assign_shards_to_pipelines(tasks).await;
806+
self.assign_shards_to_pipelines(&plan_request).await;
805807
self.update_chitchat_running_plan().await;
806808

807809
if !spawn_pipeline_failures.is_empty() {
@@ -1135,7 +1137,7 @@ impl Handler<ApplyIndexingPlanRequest> for IndexingService {
11351137
ctx: &ActorContext<Self>,
11361138
) -> Result<Self::Reply, ActorExitStatus> {
11371139
Ok(self
1138-
.apply_indexing_plan(&plan_request.indexing_tasks, ctx)
1140+
.apply_indexing_plan(plan_request, ctx)
11391141
.await
11401142
.map(|_| ApplyIndexingPlanResponse {}))
11411143
}
@@ -1465,7 +1467,10 @@ mod tests {
14651467
},
14661468
];
14671469
indexing_service
1468-
.ask_for_res(ApplyIndexingPlanRequest { indexing_tasks })
1470+
.ask_for_res(ApplyIndexingPlanRequest {
1471+
indexing_tasks,
1472+
indexing_plan_id: "01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
1473+
})
14691474
.await
14701475
.unwrap();
14711476
assert_eq!(
@@ -1531,6 +1536,7 @@ mod tests {
15311536
indexing_service
15321537
.ask_for_res(ApplyIndexingPlanRequest {
15331538
indexing_tasks: indexing_tasks.clone(),
1539+
indexing_plan_id: "01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
15341540
})
15351541
.await
15361542
.unwrap();
@@ -1587,6 +1593,7 @@ mod tests {
15871593
indexing_service
15881594
.ask_for_res(ApplyIndexingPlanRequest {
15891595
indexing_tasks: indexing_tasks.clone(),
1596+
indexing_plan_id: "01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
15901597
})
15911598
.await
15921599
.unwrap();
@@ -1646,6 +1653,7 @@ mod tests {
16461653
indexing_service
16471654
.ask_for_res(ApplyIndexingPlanRequest {
16481655
indexing_tasks: indexing_tasks.clone(),
1656+
indexing_plan_id: "01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
16491657
})
16501658
.await
16511659
.unwrap();
@@ -1665,6 +1673,7 @@ mod tests {
16651673
indexing_service
16661674
.ask_for_res(ApplyIndexingPlanRequest {
16671675
indexing_tasks: Vec::new(),
1676+
indexing_plan_id: "01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
16681677
})
16691678
.await
16701679
.unwrap();
@@ -2072,6 +2081,7 @@ mod tests {
20722081
params_fingerprint: 0,
20732082
},
20742083
],
2084+
indexing_plan_id: "01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
20752085
})
20762086
.await
20772087
.unwrap();

quickwit/quickwit-indexing/src/actors/parquet_pipeline/pipeline.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ pub struct MetricsPipeline {
114114
handles_opt: Option<MetricsPipelineHandles>,
115115
kill_switch: KillSwitch,
116116
shard_ids: BTreeSet<ShardId>,
117+
// Id of the last indexing plan assigned to this pipeline. Kept here, like `shard_ids`, so it
118+
// can be re-sent to the source on respawn; the source adopts it as its publish token.
119+
indexing_plan_id: String,
117120
_indexing_pipelines_gauge_guard: GaugeGuard,
118121
}
119122

@@ -163,6 +166,7 @@ impl MetricsPipeline {
163166
..Default::default()
164167
},
165168
shard_ids: Default::default(),
169+
indexing_plan_id: String::new(),
166170
_indexing_pipelines_gauge_guard: indexing_pipelines_gauge_guard,
167171
}
168172
}
@@ -447,6 +451,7 @@ impl MetricsPipeline {
447451
.spawn(actor_source);
448452
let assign_shards_message = AssignShards(Assignment {
449453
shard_ids: self.shard_ids.clone(),
454+
indexing_plan_id: self.indexing_plan_id.clone(),
450455
});
451456
source_mailbox.send_message(assign_shards_message).await?;
452457

@@ -539,6 +544,8 @@ impl Handler<AssignShards> for MetricsPipeline {
539544
) -> Result<(), ActorExitStatus> {
540545
self.shard_ids
541546
.clone_from(&assign_shards_message.0.shard_ids);
547+
self.indexing_plan_id
548+
.clone_from(&assign_shards_message.0.indexing_plan_id);
542549
if let Some(handles) = &self.handles_opt {
543550
info!(
544551
shard_ids=?assign_shards_message.0.shard_ids,

0 commit comments

Comments
 (0)