Skip to content

Commit 515d752

Browse files
authored
Update indexer pool on all node updates; Fix is_indexed for empty shards (#6518)
* Update indexer pool on all node updates; Fix is_indexed for empty shards * Add is_indexed UTs * is_indexed ensures truncation position reaches EOF * Truncate EOF empty shards * Decommission only on shards being empty, PR comments
1 parent af70b73 commit 515d752

8 files changed

Lines changed: 450 additions & 89 deletions

File tree

quickwit/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

quickwit/quickwit-ingest/src/ingest_v2/fetch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ impl FetchStreamTask {
204204
index_uid=%self.index_uid,
205205
source_id=%self.source_id,
206206
shard_id=%self.shard_id,
207-
to_position_inclusive=%to_position_inclusive,
207+
%to_position_inclusive,
208208
"fetch stream reached end of shard"
209209
);
210210
let eof_position = to_position_inclusive.as_eof();

quickwit/quickwit-ingest/src/ingest_v2/ingester.rs

Lines changed: 120 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,6 @@ impl Ingester {
160160
Ok(ingester)
161161
}
162162

163-
/// Checks whether the ingester is fully decommissioned and updates its status accordingly.
164-
async fn check_decommissioning_status(&self, state: &mut InnerIngesterState) {
165-
if state.status() != IngesterStatus::Decommissioning {
166-
return;
167-
}
168-
if state.shards.values().all(|shard| shard.is_indexed()) {
169-
state.set_status(IngesterStatus::Decommissioned).await;
170-
}
171-
}
172-
173163
/// Initializes a primary shard by creating a queue in the write-ahead log and inserting a new
174164
/// [`IngesterShard`] into the ingester state. If replication is enabled, this method will
175165
/// also:
@@ -1039,7 +1029,7 @@ impl Ingester {
10391029
let wal_usage = state_guard.mrecordlog.resource_usage();
10401030
report_wal_usage(wal_usage);
10411031

1042-
self.check_decommissioning_status(&mut state_guard).await;
1032+
state_guard.check_decommissioning_status().await;
10431033
let truncate_response = TruncateShardsResponse {};
10441034
Ok(truncate_response)
10451035
}
@@ -1199,7 +1189,7 @@ impl IngesterService for Ingester {
11991189
.delete_shard(&queue_id, "control-plane-retain-shards-rpc")
12001190
.await;
12011191
}
1202-
self.check_decommissioning_status(&mut state_guard).await;
1192+
state_guard.check_decommissioning_status().await;
12031193
Ok(RetainShardsResponse {})
12041194
}
12051195

@@ -1258,9 +1248,7 @@ impl IngesterService for Ingester {
12581248
for shard in state_guard.shards.values_mut() {
12591249
shard.close();
12601250
}
1261-
self_clone
1262-
.check_decommissioning_status(&mut state_guard)
1263-
.await;
1251+
state_guard.check_decommissioning_status().await;
12641252
});
12651253
Ok(DecommissionResponse {})
12661254
}
@@ -1291,6 +1279,7 @@ impl EventSubscriber<ShardPositionsUpdate> for WeakIngesterState {
12911279
.await;
12921280
}
12931281
}
1282+
state_guard.check_decommissioning_status().await;
12941283
}
12951284
}
12961285

@@ -3298,6 +3287,61 @@ mod tests {
32983287
.assert_records_eq(&queue_id_02, .., &[]);
32993288
}
33003289

3290+
#[tokio::test]
3291+
async fn test_ingester_truncate_empty_shard_to_eof() {
3292+
let (ingester_ctx, ingester) = IngesterForTest::default().build().await;
3293+
3294+
let index_uid = IndexUid::for_test("test-index", 0);
3295+
let source_id = SourceId::from("test-source");
3296+
let queue_id = queue_id(&index_uid, &source_id, &ShardId::from(1));
3297+
3298+
let doc_mapping_uid = DocMappingUid::random();
3299+
let doc_mapping_json = format!(r#"{{ "doc_mapping_uid": "{doc_mapping_uid}" }}"#);
3300+
let shard = Shard {
3301+
index_uid: Some(index_uid.clone()),
3302+
source_id: source_id.clone(),
3303+
shard_id: Some(ShardId::from(1)),
3304+
shard_state: ShardState::Open as i32,
3305+
doc_mapping_uid: Some(doc_mapping_uid),
3306+
..Default::default()
3307+
};
3308+
3309+
let mut state_guard = ingester.state.lock_fully("test").await.unwrap();
3310+
ingester
3311+
.init_primary_shard(
3312+
&mut state_guard.inner,
3313+
&mut state_guard.mrecordlog,
3314+
shard,
3315+
&doc_mapping_json,
3316+
Instant::now(),
3317+
true,
3318+
)
3319+
.await
3320+
.unwrap();
3321+
state_guard.shards.get_mut(&queue_id).unwrap().close();
3322+
drop(state_guard);
3323+
3324+
let truncate_shards_request = TruncateShardsRequest {
3325+
ingester_id: ingester_ctx.node_id.to_string(),
3326+
subrequests: vec![TruncateShardsSubrequest {
3327+
index_uid: Some(index_uid.clone()),
3328+
source_id: source_id.clone(),
3329+
shard_id: Some(ShardId::from(1)),
3330+
truncate_up_to_position_inclusive: Some(Position::Beginning.as_eof()),
3331+
}],
3332+
};
3333+
ingester
3334+
.truncate_shards(truncate_shards_request.clone())
3335+
.await
3336+
.unwrap();
3337+
3338+
let state_guard = ingester.state.lock_fully("test").await.unwrap();
3339+
let shard = state_guard.shards.get(&queue_id).unwrap();
3340+
shard.assert_truncation_position(Position::Beginning.as_eof());
3341+
assert!(state_guard.mrecordlog.queue_exists(&queue_id));
3342+
state_guard.mrecordlog.assert_records_eq(&queue_id, .., &[]);
3343+
}
3344+
33013345
#[tokio::test]
33023346
async fn test_ingester_truncate_shards_deletes_dangling_shards() {
33033347
let (ingester_ctx, ingester) = IngesterForTest::default().build().await;
@@ -3666,7 +3710,9 @@ mod tests {
36663710
let index_uid = IndexUid::for_test("test-index", 0);
36673711
let source_id = SourceId::from("test-source");
36683712

3669-
let shard = IngesterShard::new_solo(index_uid, source_id, ShardId::from(1)).build();
3713+
let shard = IngesterShard::new_solo(index_uid, source_id, ShardId::from(1))
3714+
.with_replication_position_inclusive(Position::offset(12u64))
3715+
.build();
36703716
let queue_id = shard.queue_id();
36713717

36723718
state_guard.shards.insert(queue_id.clone(), shard);
@@ -3701,17 +3747,13 @@ mod tests {
37013747
let (_ingester_ctx, ingester) = IngesterForTest::default().build().await;
37023748
let mut state_guard = ingester.state.lock_fully("test").await.unwrap();
37033749

3704-
ingester
3705-
.check_decommissioning_status(&mut state_guard)
3706-
.await;
3750+
state_guard.check_decommissioning_status().await;
37073751
assert_eq!(state_guard.status(), IngesterStatus::Ready);
37083752

37093753
state_guard
37103754
.set_status(IngesterStatus::Decommissioning)
37113755
.await;
3712-
ingester
3713-
.check_decommissioning_status(&mut state_guard)
3714-
.await;
3756+
state_guard.check_decommissioning_status().await;
37153757
assert_eq!(state_guard.status(), IngesterStatus::Decommissioned);
37163758

37173759
state_guard
@@ -3728,20 +3770,70 @@ mod tests {
37283770
let queue_id = solo_shard.queue_id();
37293771

37303772
state_guard.shards.insert(queue_id.clone(), solo_shard);
3731-
ingester
3732-
.check_decommissioning_status(&mut state_guard)
3733-
.await;
3773+
state_guard.check_decommissioning_status().await;
37343774
assert_eq!(state_guard.status(), IngesterStatus::Decommissioning);
37353775

37363776
let shard = state_guard.shards.get_mut(&queue_id).unwrap();
37373777
shard.truncation_position_inclusive = Position::Beginning.as_eof();
3778+
state_guard.check_decommissioning_status().await;
3779+
assert_eq!(state_guard.status(), IngesterStatus::Decommissioning);
37383780

3739-
ingester
3740-
.check_decommissioning_status(&mut state_guard)
3741-
.await;
3781+
state_guard.shards.remove(&queue_id);
3782+
state_guard.check_decommissioning_status().await;
37423783
assert_eq!(state_guard.status(), IngesterStatus::Decommissioned);
37433784
}
37443785

3786+
#[tokio::test]
3787+
async fn test_decommission_completes_when_empty_shard_deleted_via_gossip() {
3788+
let (_ingester_ctx, ingester) = IngesterForTest::default().build().await;
3789+
3790+
let event_broker = EventBroker::default();
3791+
ingester.subscribe(&event_broker);
3792+
3793+
let index_uid = IndexUid::for_test("test-index", 0);
3794+
let source_id = SourceId::from("test-source");
3795+
let shard_id = ShardId::from(1);
3796+
let queue_id = queue_id(&index_uid, &source_id, &shard_id);
3797+
3798+
let empty_shard =
3799+
IngesterShard::new_solo(index_uid.clone(), source_id.clone(), shard_id.clone())
3800+
.with_state(ShardState::Closed)
3801+
.build();
3802+
3803+
let mut status_rx = ingester.state.status_rx.clone();
3804+
{
3805+
let mut state_guard = ingester.state.lock_fully("test").await.unwrap();
3806+
state_guard.shards.insert(queue_id.clone(), empty_shard);
3807+
state_guard
3808+
.set_status(IngesterStatus::Decommissioning)
3809+
.await;
3810+
}
3811+
3812+
assert_eq!(
3813+
*status_rx.borrow_and_update(),
3814+
IngesterStatus::Decommissioning
3815+
);
3816+
3817+
event_broker.publish(ShardPositionsUpdate {
3818+
source_uid: SourceUid {
3819+
index_uid: index_uid.clone(),
3820+
source_id: source_id.clone(),
3821+
},
3822+
updated_shard_positions: vec![(shard_id, Position::Beginning.as_eof())],
3823+
});
3824+
3825+
tokio::time::timeout(Duration::from_secs(5), async {
3826+
while *status_rx.borrow_and_update() != IngesterStatus::Decommissioned {
3827+
status_rx.changed().await.unwrap();
3828+
}
3829+
})
3830+
.await
3831+
.expect("ingester should reach Decommissioned once its empty shard is deleted via gossip");
3832+
3833+
let state_guard = ingester.state.lock_fully("test").await.unwrap();
3834+
assert!(!state_guard.shards.contains_key(&queue_id));
3835+
}
3836+
37453837
#[tokio::test]
37463838
async fn test_ingester_truncate_on_shard_positions_update() {
37473839
let (_ingester_ctx, ingester) = IngesterForTest::default().build().await;

quickwit/quickwit-ingest/src/ingest_v2/models.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,6 @@ impl IngesterShard {
267267
now.duration_since(self.last_write_instant) >= idle_timeout
268268
}
269269

270-
pub fn is_indexed(&self) -> bool {
271-
self.shard_state.is_closed() && self.truncation_position_inclusive.is_eof()
272-
}
273-
274270
pub fn is_replica(&self) -> bool {
275271
matches!(self.shard_type, IngesterShardType::Replica { .. })
276272
}

0 commit comments

Comments
 (0)