Rust SDK: sends to a multi-partition topic bounce between nodes once partition primaries diverge (multi-second stalls)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- rust
- 領域
- backend, networking
調査の方向性
Start with core/sdk/src/poll_routing.rs to understand how polls route to partition primaries, then compare the send retry and routing paths in core/sdk/src/tcp/tcp_client.rs (send_raw_vsr_attempt and send_raw_retaining_header). Run the ignored integration test with the command in the issue; done means alternating sends to partitions with different primaries keep the session on its node without the multi-second refusal stalls.
索引モデルが issue の本文から書いたものです。
説明
Bug description
In a cluster, a Rust SDK producer that writes to several partitions of one topic
stalls for about 4 seconds on nearly every send once those partitions' primaries
are on different nodes. Each send moves the client's only connection to another
node.
Each partition is its own consensus group and elects its own primary. On a fresh
cluster all partition primaries are on one node, so every send is admitted where
the client is connected. After failovers (for example, nodes restarted one at a
time) the primaries end up on different nodes. Polls handle this: PollRouter
(core/sdk/src/poll_routing.rs) sends a poll to the partition's primary over a
separate data connection and keeps the session where it is. Sends have no
equivalent. SendMessages goes out on the one TCP session:
- The client is on node A. It sends to partition 0, whose primary is node B.
Node A's replica is a backup, so it refuses the request with
TransientNotAccepted(never admitted). send_raw_vsr_attemptreplays the refusal on the same connection every 50 ms
untilTRANSIENT_FAILOVER_CHECK_INTERVAL(2 s) expires.send_raw_retaining_headerthen takesrouting_lockandcontinues
("a concurrent refused request may have moved the shared client"), so a
second 2 s replay window runs on the same node.- Only then does it check the metadata leader, walk the roster
(RosterWalk,settle_on_endpoint), disconnect, and reconnect and sign in
on the next node. The send succeeds there. - The next send goes to partition 1, whose primary is node A. Steps 1-4 repeat
in the other direction: 4 s of refusals on node B, then a redirect back to
the metadata leader.
A producer that spreads its sends over the partitions with
Partitioning::messages_key(..) or explicit partition ids hits this on almost
every send. Throughput is capped by the 4 s stall, not by the server, and the
server sits nearly idle.
Expected: a send reaches its partition's primary without the session moving
on every send, the way polls already do. Once the primaries are known, a
producer alternating between partitions should pay no refusal window and no
reconnect.
Actual: every send to a partition whose primary is not the connected node
waits out two transient windows (about 4 s), then moves the session. With two or
more primaries in play this happens on nearly every send.
Affected area / component
Rust SDK, Clustering / replication
Deployment
Compiled from source
Versions
- Rust SDK
iggy0.11.0 (released) against a 3-node cluster built from
f6dfbdbb3with unrelated patches: observed (numbers below). master@43fb44364: failing integration test (PR linked below), 3 of 3
runs.- Tag
iggy-0.11.0(71f29618b): the same test fails the same way.
The Go SDK (foreign/go) looks the same from reading the code, but I didn't
run it. SendMessages resolves the partition client side and then goes through
c.do. On ErrTransientNotAccepted that replays for failoverCheckInterval
(2 s), redirects to the leader or walks the roster with
settleOnNextEndpoint, and reconnects. There is no primary routing for sends.
Hardware / environment
- Observed: 3-node cluster on Kubernetes, 2 CPU / 8 GiB per node. 12 producers,
batches of 100 messages, 45k msg/s offered. - Reproduced: the integration harness (3 nodes on localhost) in a Linux x86_64
container (4 CPUs), pinned toolchain fromrust-toolchain.toml.
Reproduction
A failing test is on branch test/sdk-send-partition-primary-routing (PR
linked below). It is #[ignore]d with this issue's number, so run it with
--ignored:
cargo build --bin iggy-server
cargo test -p integration --test mod -- --ignored --exact \
cluster::partition_primary_routing::given_partitions_on_different_primaries_when_sending_round_robin_should_keep_the_session
It uses the existing seed_split_primaries fixture in
core/integration/tests/cluster/partition_primary_routing.rs. Partition 0 stays
on its primary while a metadata-only election moves the metadata leader. It then
adds partition 1 with create_partitions. A partition created after the
election is seeded from the metadata view, so its primary is the metadata leader.
The test checks this precondition with GET_POLL_ROUTING: the two partitions
report primaries on different nodes. It sends one warm-up message to each
partition. Then it sends 4 messages alternating between partitions 0 and 1, and
asserts that no send moves the session to another node.
Logs
The test's failure on master (partition, node before, node after, send time):
assertion `left == right` failed: a producer alternating between partition 0 (primary node 0) and partition 1 (primary node 2) must keep its session; 4 of 4 sends moved it to another node (partition, from node, to node, send time):
[(0, 2, 0, 4.020694671s), (1, 0, 2, 4.018801233s), (0, 2, 0, 4.019829788s), (1, 0, 2, 4.018055753s)]
The client log during that run, one send per pair of lines (node 2 at :20016
is the metadata leader and partition 1's primary, node 0 at :20000 is
partition 0's primary):
20:54:02.365 iggy::tcp::tcp_client: The request keeps being refused on 127.0.0.1:20016 while the roster names it the metadata leader; trying the next cluster node at 127.0.0.1:20000.
20:54:06.390 iggy::tcp::tcp_client: Current node is not leader, redirecting to leader at: 127.0.0.1:20016
20:54:10.408 iggy::tcp::tcp_client: The request keeps being refused on 127.0.0.1:20016 while the roster names it the metadata leader; trying the next cluster node at 127.0.0.1:20000.
20:54:14.428 iggy::tcp::tcp_client: Current node is not leader, redirecting to leader at: 127.0.0.1:20016
Tag iggy-0.11.0 gives the same pattern: [(0, 2, 0, 4.068s), (1, 0, 2, 4.071s), (0, 2, 0, 4.081s), (1, 0, 2, 4.078s)].
Observed on the real cluster (SDK 0.11.0, 12 producers, batches of 100,
45k msg/s offered). The server logs rejecting client request on non-primary partition replica for the refused sends:
| topic | primaries | achieved | send p50 | send p99 | server CPU |
|---|---|---|---|---|---|
| 6 partitions, fresh cluster | all on one node | 46k msg/s | - | 21 ms | - |
| 6 partitions, after three single-node restarts | spread over nodes | 11,984 msg/s | 2.6 ms | 4,054 ms | 0.1-0.2 cores |
| 1 partition, same load | one primary | 45,830 msg/s | - | 10 ms | - |
The 4,054 ms p99 matches the two 2 s windows per misrouted send.
Code references (master @ 43fb44364)
core/sdk/src/tcp/tcp_client.rs:83:TRANSIENT_FAILOVER_CHECK_INTERVAL = 2s.core/sdk/src/tcp/tcp_client.rs:1699-1704(send_raw_vsr_attempt): replays
TransientNotAcceptedon the same connection until the transient deadline.core/sdk/src/tcp/tcp_client.rs:1436-1445(send_raw_retaining_header): the
first refusal only takesrouting_lockandcontinues, so a second full
window runs on the same node before any routing decision. This is where the
4 s instead of 2 s comes from.core/sdk/src/tcp/tcp_client.rs:1450-1525: the leader check, then
RosterWalkandsettle_on_endpoint(:1032) move the session itself.core/sdk/src/poll_routing.rs: polls and offset writes get the partition
primary (GET_POLL_ROUTING) and use a per-node data connection. Sends have no
such path.
Impact
- A producer writing several partitions loses most of its throughput after
ordinary failovers (here 45k -> 12k msg/s), with 4 s tail latency and an idle
server. - Partition primaries stay where the failovers left them, so the stalls
continue for as long as the primaries stay split. - Every bounce is a disconnect, reconnect and sign-in, which adds session churn
on the server.
Suggested fix
Route sends like polls: resolve the target partition's primary and send over a
data connection to that node, keeping the session where it is. Cache the route
and refresh it on TransientNotAccepted. The existing PollRouter connection
pool, session binding and generation fencing look reusable. A send would need
the dedup identity of the connection it is sent on, as replicated offset writes
already have. A smaller step is to skip the second transient window. A refusal
proves the request was never admitted, so after taking routing_lock the
request could go straight to the routing decision instead of replaying for
another 2 s. That halves the stall but does not stop the bouncing. The Go SDK
would need the same change.
Contribution
- I'm willing to submit a pull request to fix this bug
(the failing test is ready as a PR; the fix is open for discussion)
- 主要言語
- Rust
- スター
- 5.2k
- フォーク
- 502
- 平均マージ
- 3日 4時間
- マージ済み PR(30日)
- 157
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
apache/iggy のほかの issue
-
Go examples: add a message envelope producer and consumer対応中かも @ThunderGod77 が 3 日前に担当しました。 オープンgo good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
apache/iggy#4694 · コメント 3 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
HTTP client treats a consumer group as a plain consumer対応中かも @mohitduhan19 が 21 日前に担当しました。 オープンbug rust server
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
メンテナーはふだん 1 日以内に返信
-
Add the fail-fast connection option to the Rust and Python quick-starts対応中かも @QIW9 が 8 日前に担当しました。 オープンdocs good first issue
難易度 1/5 1〜3時間 初心者へのやさしさ 92/100
メンテナーはふだん 1 日以内に返信
-
bug python sdk
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
bug(partitions): a_removal_cut_short_leaves_the_marker_in_place panics when tests run as root対応中かも @coding-by-sea が今日担当しました。 オープンbug CI/CD
メンテナーはふだん 1 日以内に返信
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 69/100
メンテナーはふだん 1 日以内に返信
-
A match arm above the largest i64 is compared as 0対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 83/100
DioxusLabs/dioxus#6063 ·
メンテナーはふだん 1 日以内に返信
-
`nsh-core` fails to compile when taffy's default `calc` feature is enabled elsewhere in the graphオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
longbridge/nsh#966 ·
メンテナーはふだん 1 日以内に返信
-
Windows docs: pin a tested ConPTY version for Kitty graphics; 1.25.260930003 corrupts large uploadsオープン
難易度 1/5 1〜3時間 初心者へのやさしさ 88/100
raphamorim/rio#2021 ·
メンテナーはふだん 1 日以内に返信