fix(control-plane): Avoid spawning a new control plan loop on every single actor restart.#6600
fix(control-plane): Avoid spawning a new control plan loop on every single actor restart.#6600fulmicoton-dd wants to merge 3 commits into
Conversation
|
does not seem to good to me though |
7ccf3d7 to
fba84d8
Compare
fba84d8 to
2771635
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the control plane to react directly to IngesterPool mutations (via a monotonic pool generation) instead of deriving shard-rebalance triggers from cluster membership change streams, and removes the now-obsolete cluster change stream factory/test abstractions.
Changes:
- Added a monotonic “generation” signal to
Pooland used it to trigger control-plane rebalances. - Implemented debounced rebalance handling in the control plane by tracking the last successfully rebalanced ingester-pool generation.
- Removed
ClusterChangeStreamFactoryand associated test helpers, updating call sites accordingly.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| quickwit/quickwit-serve/src/lib.rs | Updates ControlPlane::spawn call signature. |
| quickwit/quickwit-control-plane/src/tests.rs | Updates test helper to match new control-plane spawn signature. |
| quickwit/quickwit-control-plane/src/ingest/ingest_controller.rs | Tracks last rebalanced pool generation and exposes rebalance_shards_inner to support debouncing decisions. |
| quickwit/quickwit-control-plane/src/control_plane.rs | Switches from cluster-change watching to ingester-pool generation watching and debounced rebalance triggering. |
| quickwit/quickwit-common/src/tower/pool.rs | Adds pool generation watch channel and increments it on mutations. |
| quickwit/quickwit-cluster/src/lib.rs | Stops re-exporting removed cluster-change stream factory trait. |
| quickwit/quickwit-cluster/src/cluster.rs | Removes ClusterChangeStreamFactory impl for Cluster. |
| quickwit/quickwit-cluster/src/change.rs | Removes ClusterChangeStreamFactory trait and its test implementation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let rebalance_performed = match self | ||
| .ingest_controller | ||
| .rebalance_shards(&mut self.model, ctx.mailbox(), ctx.progress()) | ||
| .rebalance_shards_inner(&mut self.model, ctx.mailbox(), ctx.progress()) | ||
| .await | ||
| { | ||
| return convert_metastore_error::<()>(error).map(|_| ()); | ||
| Ok(num_opened_shards_opt) => num_opened_shards_opt.is_some(), | ||
| Err(error) => return convert_metastore_error::<()>(error).map(|_| ()), | ||
| }; | ||
| if !rebalance_performed { | ||
| return Ok(()); | ||
| } |
fb2ba67 to
fe1df52
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fe1df52d91
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
fe1df52 to
354f126
Compare
354f126 to
4705d7b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4705d7bbfd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if observed_pool_generation | ||
| <= self | ||
| .ingest_controller | ||
| .last_rebalanced_ingester_pool_generation | ||
| { | ||
| debug!( | ||
| pool_generation = observed_pool_generation, | ||
| "skipping rebalance: ingester pool is unchanged" | ||
| ); | ||
| return Ok(()); | ||
| } |
There was a problem hiding this comment.
Don't let periodic rebalances consume pool-change notifications
When a scheduled ControlPlanLoop runs after the ingester pool generation advances but before this queued IngesterPoolHasChanged message is handled, rebalance_shards_inner records that generation as last_rebalanced_ingester_pool_generation; this guard then returns before calling rebuild_plan. In that ordering, ready/retiring ingester changes can be rebalanced by the periodic loop while the indexing plan rebuild associated with the pool change is skipped, leaving assignments stale until control_running_plan later notices after its scheduling delay.
Useful? React with 👍 / 👎.
| Ok(num_opened_shards_opt) => num_opened_shards_opt.is_some(), | ||
| Err(error) => return convert_metastore_error::<()>(error).map(|_| ()), | ||
| }; | ||
| if !rebalance_performed { | ||
| return Ok(()); |
There was a problem hiding this comment.
Retry pool-change notifications when rebalance is busy
When an ingester joins or leaves while a previous rebalance is still holding the rebalance lock, including the delayed close phase, rebalance_shards_inner returns Ok(None) and this handler just drops the notification. Since the watch change for that generation has already been consumed, no follow-up IngesterPoolHasChanged is queued, so shards can remain on a retiring/departed ingester and the indexing-plan rebuild can be skipped until a later periodic control loop happens to acquire the lock.
Useful? React with 👍 / 👎.
aa0676f to
e101a83
Compare
e101a83 to
3732ce1
Compare
| } | ||
|
|
||
| /// A factory for creating cluster change streams. | ||
| pub trait ClusterChangeStreamFactory: Clone + Send + 'static { |
| pub struct Pool<K, V> { | ||
| pool: Arc<RwLock<HashMap<K, V>>>, | ||
| /// Publishes a notification after each mutation. | ||
| pub changes_tx: watch::Sender<()>, |
There was a problem hiding this comment.
Shouldn't this be private? Only the pool should sent notifications, no?
| tokio::spawn(watcher_indexers(weak_mailbox, cluster_change_stream)); | ||
| } | ||
|
|
||
| async fn watcher_indexers( |
There was a problem hiding this comment.
I'm glad we're getting rid of this complex function, however:
- we no longer log why we rebalance
- we rebalance on every pool change whereas before we would ignore some changes, for instance an ingester joining in initializing status. Rebalance shards is debounced but it's hard to predict whether this change will have undesirable side-effects.
Description
Bugfix: before a failure of the control plane actor (due to connectivity with the metastore for instance) would trigger a restart of the control plane actor. A chitchat watching task is monitoring for node membership changes and triggering rebalance shards on this change. That task was spawned in initialized, on a recycled mailbox. As a result, every single restart would start a new watching loop.
This PR starts a loop once and for all.
Also, instead of looking for changes in the cluster membership, it looks for change in the ingester pool itself.
Each pool owns a monotonic generation sender that is updated after mutations are applied. The control plane subscribes once for the lifetime of its supervised mailbox, and the ingest controller tracks the last successfully rebalanced generation so bulk or duplicate notifications are coalesced. Periodic control-loop rebalances remain unconditional.
This also removes the obsolete cluster change stream factory and test stream abstractions from the control-plane API.
How was this PR tested?
cargo test -p quickwit-common tower::pool --libcargo test -p quickwit-control-plane --libcargo check -p quickwit-serve --libcargo +nightly fmt --all -- --check