Skip to content

Artery: inbound lanes for parallel inbound message processing (ships at 1) - #8356

Merged
Aaronontheweb merged 2 commits into
akkadotnet:devfrom
Aaronontheweb:feature/artery-inbound-lanes
Jul 11, 2026
Merged

Aaronontheweb merged 2 commits into
akkadotnet:devfrom
Aaronontheweb:feature/artery-inbound-lanes

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Member

Adds inbound lanes to Artery's ordinary stream. When akka.remote.artery.advanced.inbound-lanes is raised above 1, each accepted connection fans user messages out across N bounded queues, each drained by its own consumer task, so payload deserialization, actor-ref resolution, and dispatch run in parallel. The lane is picked by a stable hash of the recipient path, so all traffic to the same actor stays on one lane and arrives in send order. ActorSelectionMessages hash on the selection's own target path elements, since their envelope recipient is always the remote root guardian. A new inbound-lane-buffer-size setting (default 4096) caps each lane's queue; a full lane backpressures the connection instead of dropping or growing unbounded.

The default is 1, where none of the lane machinery is materialized and the inbound pipeline is unchanged from what's on dev today. Control-stream traffic (handshake, heartbeats, system messages) is unaffected at any setting. We'll consider flipping the default to Pekko's 4 after the lane-scaling runs on a quiet box.

Verification at the lanes=1 default (RemotePingPong --artery echo, 9900X): warm plateau 1.80-1.88M msgs/s, peak 1.875M vs the 1.843M same-night dev anchor, zero drops. Full Artery suite green on three consecutive runs, plus MNTR sanity (NewRemoteActorSpec, SunnyWeatherSpec) over the Artery transport.

… dispatch

Fans ordinary-stream messages across N bounded per-lane channels within one accepted connection, keyed by recipient path hash so per-recipient ordering is preserved. Ships with inbound-lanes = 1, which keeps the existing fused single-lane pipeline and materializes no lane machinery.
@Aaronontheweb

Copy link
Copy Markdown
Member Author

Ran the C.1 lane-scaling sweep against this branch (plus the #8355 CLI flags) on the quiet 9900X box: RemotePingPong --artery, toy payload, echo and oneway, inbound-lanes at 1/2/4/8, three sweeps per config, warm rows only, zero drops everywhere. Baseline gate passed (lanes=1 echo peak 1,878,993 vs the 1,843,318 same-night dev anchor).

Echo (plateau median across 15-30 clients, warm sweeps):

lanes median msgs/s vs lanes=1
1 1,813,238 —
2 1,865,222 +2.9%
4 1,791,152 -1.2%
8 1,826,394 +0.7%

Oneway:

lanes median msgs/s vs lanes=1
1 1,104,058 —
2 1,230,912 +11.5%
4 1,246,010 +12.9%
8 1,243,946 +12.7%

Reading: echo is flat within run-to-run noise across 1/2/4/8 lanes — the whole band sits inside the ~1.9% between-run drift envelope, ordering is non-monotonic, and lanes=4 lands slightly below lanes=1. Oneway shows a clean, reproducible jump at lanes=2 and then saturates completely; 4 and 8 lanes add nothing over 2. That asymmetry is the diagnosis rather than a problem: echo actually runs higher than oneway (~1.82M vs ~1.25M) because echo drives two outbound encode islands while oneway drives one, so the binding constraint is the single outbound encode island (this build has no outbound lanes) plus toy messages being too cheap to deserialize for the inbound station to matter beyond the first doubling. The oneway gain proves the lane machinery genuinely parallelizes when the inbound side is the loaded one, which clears the partition seams of the pre-registered "flat curve means we added a serialized station" suspicion.

Bottom line for this PR: shipping default stays 1 (lowest latency for round-trip traffic, matching Pekko's own caveat), and inbound-lanes stands as a tunable knob that delivers a real ~13% on ingest-heavy unidirectional workloads. Any future default change should look at 2, not 4, and only after a heavy-payload real-serializer run shows the inbound station rewarding more lanes. Two follow-up measurements are queued: real-payload runs at lanes {1,4} (tests the cheap-deserialize explanation) and a combined inbound+outbound build sweep (tests the single-outbound-island explanation).

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

lgtm

// Ships at 1 (single-lane, fused pipeline -- byte-identical to pre-lanes processing)
// until the scaling gates pass -- see ArterySettings.InboundLanes's remarks.
settings.InboundLanes.Should().Be(1);
settings.InboundLaneBufferSize.Should().Be(4096);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Buffer size == msg count here, not bytes.

int maxLargeFrameLength,
Akka.Serialization.Serialization serialization,
int inboundLanes = 1,
int inboundLaneBufferSize = 0,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

0 == no buffer size limitation

// match a real wire SerializerId, so the defensive checks below simply never trigger
// rather than throwing.
//
// LOAD-BEARING INVARIANT: lane mode classifies a frame as "control" by comparing its

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

for future reference

Push(_stage.Out, _pending.Dequeue());
}
else if (IsClosed(_stage.In))
else if (IsClosed(_stage.In) && !_writeInFlight)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

don't complete the stage if there are still pending writes

@Aaronontheweb
Aaronontheweb merged commit 62c6def into akkadotnet:dev Jul 11, 2026
11 checks passed
@Aaronontheweb
Aaronontheweb deleted the feature/artery-inbound-lanes branch July 11, 2026 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

akka-remote artery Akka.Remote Artery Protocol perf

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant