Skip to content

Use generation ID for routing table entries, and react to the ingester pool - #6791

Merged
nadav-govari merged 2 commits into
mainfrom
nadav/gen_id
Sep 16, 2026
Merged

nadav-govari merged 2 commits into
mainfrom
nadav/gen_id

Conversation

@nadav-govari

@nadav-govari nadav-govari commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Description

Builds on #6680.

Currently, we rely on the ingester pool to gate ingester availability in the routing table. We never remove entries. As a result, we don't react when ingesters leave and rejoin the cluster. This mostly resolves itself on one-index clusters, because shards are more or less interchangeable. However, there's a logical problem with this on multi-index clusters: an ingester leaves the cluster, and rejoins, but might not get assigned any shards for a source it had previously, in which case it becomes a valid, inaccurate candidate for persist requests for that source.

In my testing with 9 source of varying size from "huge" to "very tiny", I observed a persistent 1% ingest error rate, that upon further investigation was due to this bug- requests were arriving at ingesters that simply didn't host any shards for that source, because they had done so in a previous incarnation.

The fix is twofold:

  1. Ingesters leaving the ingester pool trigger a removal of their routing entries.
  2. To ensure this is accurate, and that out of order events and other related race conditions don't occur, we now track generation ID on the ingester pool. This way, an ingester is only a candidate for routing if this exact incarnation is known to host shards for a source.

How was this PR tested?

Unit testing. Live cluster testing incoming.

@nadav-govari
nadav-govari marked this pull request as ready for review September 9, 2026 15:17
@nadav-govari
nadav-govari requested a review from a team as a code owner September 9, 2026 15:17
@nadav-govari nadav-govari changed the title [Draft] Use generation ID for routing table entries Use generation ID for routing table entries Sep 9, 2026
@nadav-govari nadav-govari changed the title Use generation ID for routing table entries Use generation ID for routing table entries, and react to the ingester pool Sep 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fae0c7f35

ℹ️ 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".

.sum();

for (node_id, open_shard_count) in per_ingester_count {
let Some(generation_id) = ingester_pool.get(&node_id).map(|entry| entry.generation_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not stamp stale shard responses with the current generation

When GetOrCreateOpenShards computes a response for generation A but the ingester restarts before the router merges that response, the pool can already contain generation B; this lookup then labels A's shard assignments as belonging to B. For a newly inserted routing node, the default positive capacity makes B immediately eligible even if it has none of those shards, and because B never broadcasts updates for a source it does not host and later CP merges do not remove absent nodes, this inaccurate route can persist and recreate the NoShardsAvailable failures this change is intended to prevent. The generation must be supplied by the authoritative response or the merge must detect intervening membership churn and retry rather than inferring it from the current pool.

AGENTS.md reference: AGENTS.md:L21-L22

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the exact TODO that comes a line later. This is its own race condition. Unfortunately the fix for this is sending generation ID from the control plane, which becomes much more complicated, because it uses the shard proto which is also in the metastore model. I'll re-explore how invasive that is but when I looked into it it seemed like a mess.

Comment on lines +330 to +332
else {
// TODO: decide what you want to do here exactly. This might be important
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve shard assignments while the ingester pool catches up

When the control plane returns an open shard before this router's independent cluster-change listener has inserted that ingester into the local pool, this branch silently drops the authoritative assignment and the method still marks the routing entry as seeded from the control plane. The current request then burns through its five retries with NoShardsAvailable if the pool update is delayed, whereas previously the assignment remained in the table and became usable as soon as the ingester appeared. Retain an unresolved assignment for later generation reconciliation, or leave the entry explicitly unseeded and wait/retry rather than treating this merge as successful.

AGENTS.md reference: AGENTS.md:L21-L22

Useful? React with 👍 / 👎.

.unwrap_or(false);

if !is_ready {
.filter(|ingester| ingester.status.is_ready())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nitpick: I'd put that in a separate if statement

let Some(ingester) = ... else { return false; }
ingester.generation_id != self.generation_id
&& ingest.is_ready()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the logic unfortunately- if it's not ready, we need to add it to unavailable ingesters.

Comment thread quickwit/quickwit-ingest/src/ingest_v2/routing_table.rs
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-15T20:59:40.083211Z 89181a2 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89181a2250

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 414 to 417
let persist_summary = PersistRequestSummary {
ingester_id: ingester_id.clone(),
generation_id: pool_entry.generation_id,
subrequest_ids,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recheck the selected generation before dispatch

When an ingester restarts after pick_node validates generation A but before this second pool lookup, pool_entry belongs to generation B, so the router sends subrequests chosen from A's routing entry to B and records B in the summary. If B does not host that source, this produces the same NoShardsAvailable failure the generation check is intended to prevent. Carry the selected node's generation into per_ingester_persist_subrequests and require it to match this pool entry before dispatching.

AGENTS.md reference: AGENTS.md:L21-L22

Useful? React with 👍 / 👎.

@nadav-govari
nadav-govari merged commit 1593273 into main Sep 16, 2026
9 checks passed
@nadav-govari
nadav-govari deleted the nadav/gen_id branch September 16, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants