Repository navigation
RATIS-2787. Spread appendEntries between two servers over more than one connection - #1679
Conversation
…ne connection Generated-by: Claude Code (Claude Opus 5.5)
amaliujia
left a comment
There was a problem hiding this comment.
Overall LGTM. Just want to lower the complexity of the assignment algorithm to have a balance between complexity and performance improvement then we good to merge.
|
|
||
| @VisibleForTesting | ||
| int getAppendEntriesIndex() { | ||
| try (AutoCloseableLock readLock = lock.readLock(caller, LOG::trace)) { |
There was a problem hiding this comment.
This is a testing only call, so the readlock has no performance impact for production.
szetszwo
left a comment
There was a problem hiding this comment.
@yandrey321 , thanks for working on this!
The idea of PR is good but the implementation is quite messy. We need to polish it. Filed RATIS-2804.
| @SuppressWarnings({"squid:S3077"}) // Suppress volatile for generic type | ||
| private volatile StreamObservers appendLogRequestObserver; | ||
| // the appendEntries channel of this appender, taken when it creates its first stream; guarded by the write lock | ||
| private int appendEntriesIndex = -1; |
There was a problem hiding this comment.
- Why appendEntriesIndex is in GrpcLogAppender but not together with the lists in GrpcServerProtocolClient?
- Also, it is really a bad name. It confuses with the log index in appendEntries.
| } | ||
|
|
||
| @VisibleForTesting | ||
| int getAppendEntriesIndex() { |
There was a problem hiding this comment.
It is better to call it getAppendEntriesIndexForTesting instead of using VisibleForTesting.
| * them, each group always using the same one, so that the replication between two servers is not limited to one | ||
| * connection and so one event-loop thread at each end. Heartbeats keep their own channel. | ||
| */ | ||
| String LOG_APPENDER_CONNECTIONS_KEY = PREFIX + ".log-appender.connections"; |
There was a problem hiding this comment.
We already has raft.grpc.server.log.appender.listener.factory. This new conf should use the same raft.grpc.server.log.appender prefix.
|
@yandrey321 , have you used any AI tool in this PR? If yes, please stay it in the beginning of the description and the commit message. This is ASF policy. |
@szetszwo there is |
@yandrey321 , Just saw this line. Please put it in the beginning. It is hidden in the middle of a very long description. Thanks. BTW, please shorten the description first. It saves everyone's time. I understand that the contributor can type a few words using AI to generate a long description and long PR with many comments/javadocs. It saves a lot of the contributor's time. However, the review is very hard and very time consuming.
|

What changes were proposed in this pull request?
Problem. All Raft groups that two servers share replicate over one gRPC connection.
GrpcServicesImplkeeps oneGrpcServerProtocolClientper peer, shared by every division on the server.GrpcServerProtocolClient.appendEntries()always uses the stub on its main channel. Heartbeats move to a separatechannel only when
raft.grpc.server.heartbeat.channelis set.Netty pins a connection to one event-loop thread. So with TLS, a single thread at each end encrypts, frames and
decrypts the entire log stream between the two servers, state machine data included. That caps replication between
two servers at about one core of TLS work.
Apache Ozone hits this limit. A datanode is one RaftServer hosting 20–40 RATIS/THREE groups (pipelines), and each
WriteChunkentry carries up to 4 MB of data. The measurements below come from a large 3-way write on a 12-datanodecluster with gRPC TLS on (TestDFSIO 1000 × 1 GB):
of 78–96%.
Change.
raft.grpc.server.log-appender.connections(K):GrpcConfigKeys.Server.GrpcServerProtocolClient:Math.floorMod(groupId.hashCode(), K).close()andresetConnectBackoff()cover all K channels.GrpcLogAppender: passes its group id when it opens the appendEntries stream.GrpcServicesImpl: reads the key and passes it to each per-peer client.Key decisions.
entries in order on one connection while different groups spread over up to K event-loop threads at each end. No
ordering or retry logic changes.
readIndexcalls. AppendEntries streams are long-lived and ordered, so they need a fixed channel per group.heartbeat.channelon, they keep their own channel; otherwise they share thegroup's appendEntries stream as before.
peer.
Side effects.
session and a handshake when it connects.
K = 4 in the microbenchmark below. The profile puts the increase mostly in waiting for socket events: the same bytes
arrive over more connections and threads, so each wake-up carries less data. So K should be raised only where one
connection's event-loop thread is the limit.
Generated-by: Claude Code (Claude Opus 5.5)
What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/RATIS-2787
How was this patch tested?
New unit tests.
TestGrpcServerProtocolClientChannels:testOneConnectionUsesMainChannel: K = 1 has one appendEntries channel, which is the main channel, and every groupmaps to index 0.
testGroupsSpreadOverConnections: with K = 4:close()shuts down every channel.Benchmark on a Linux server. The benchmark is a standalone harness, not part of this change.
taskset, all on NUMA node 0. The leader has 10 physical cores, and each follower and the clienthave 5.
WriteChunk, and not cached in the log;data was checked against the leader's by checksum, entry by entry, and all matched.
/proc, during the timed writes.of the two leader–follower connections, one sending on the leader and one receiving on each follower, run at
82–89%.
more, and no thread is above 79%.
9.4 cores. Then K = 2 and K = 4 gained only 17–18% over K = 1, at 700, 820 and 827 MB/s.
The flow-control window also matters: a bigger window raises K = 1 as well, as shown above. At Ratis' 1 MB default,
K = 4 was +17% to +30% over K = 1, depending on the leader's cores.