Skip to content

feat(streaming): build a byte item in place - #157

Merged
ryanolson merged 1 commit into
mainfrom
feat/byte-item-builder
Oct 27, 2026
Merged

ryanolson merged 1 commit into
mainfrom
feat/byte-item-builder

Conversation

@ryanolson

@ryanolson ryanolson commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A producer that encodes each item into its own buffer pays a copy when send_bytes puts the payload into the frame. This PR lets the encoder write the payload straight into the frame.

  • ByteItemBuilder::with_capacity(n) makes a frame buffer that already holds the item header. buffer() hands out the Vec<u8>, so an encoder that writes to a Vec needs no velo type. The builder needs no sender.
  • finish() writes the payload length, and refuses a frame whose header was changed (a cleared buffer could otherwise hold a terminal sentinel).
  • StreamSender<Bytes>::send_frame(frame) sends it with no copy, under the rules of send_bytes for a cancel, a full channel, and the item limit.
  • The wire format does not change. The length is always a bin32, so an item under 64 KiB is up to 3 bytes longer than a send_bytes frame.

Additive. main is at 0.21.0 and crates.io at 0.20.0, so no version bump.

Performance-sensitive (per item, producer). A new opt-in path beside send_bytes. send and send_bytes do the same work as before.

Measured

bench_byte_item_send (ignored lib test) times both arms in one process: encode into an own buffer then send_bytes, against build in place then send_frame. Median of 9 rounds of 200,000 items, three runs. gb300 (Neoverse-V2, CPU 9, load 2.3):

Payload send_bytes send_frame
1 KiB 168.4 ns 97.4 ns
16 KiB 476.2 ns 252.8 ns

The three runs agree to 0.4 ns. The 16 B row was lost in the runner's log extraction; on a busier spark host it was 54.0 against 47.1 ns.

timeout 300 cargo test --release -p velo --lib bench_byte_item_send -- --ignored --nocapture --test-threads=1

Tests

  • built_items_decode_as_encoded_items_do: every bin width, through the byte decode and serde; the payload stays in its buffer.
  • building_an_item_that_fits_allocates_once, building_and_sending_an_item_allocates_once: one allocation. Both fail if finish copies.
  • finish_refuses_a_changed_header, a_payload_over_the_bin_limit_is_refused.
  • send_frame_sends_the_built_frame_and_follows_the_send_rules, send_frame_refuses_an_item_over_the_limit_as_send_bytes_does.
  • byte_items_round_trip, byte_and_typed_ends_interoperate: built frames over the mux (ticket, attach) and on the same worker, through next and next_bytes.

cargo test -p velo --lib: 1151 passed. Clippy with --all-features and the book build pass.

Review

One adversarial pass by a reviewer from another model family: no findings, with probes over every header byte, per-stream TCP and gRPC, and the 16 MiB limit. perf-review: no regression on send or send_bytes.

Summary by CodeRabbit

  • New Features
    • Added support for building byte-item frames directly in a reusable buffer and sending them without copying the payload into another buffer.
    • Added public access to the frame builder and completed frame types.
  • Bug Fixes
    • Oversized byte-item payloads now return a serialization error instead of being encoded.
  • Documentation
    • Added guidance on writing items in place, including frame sizing, validation, and differences from send_bytes.
    • Expanded streaming examples and coverage for byte-item write and read combinations.

A producer that encodes an item into its own buffer pays a copy when
send_bytes puts it into the frame. ByteItemBuilder hands out the
frame's buffer with the item header already in it, so an encoder writes
the payload straight into the frame. finish writes the length, and
StreamSender<Bytes>::send_frame sends the frame with no copy, under
the send_bytes rules for a cancel, a full channel and the item limit.
The builder needs no sender.

The frame decodes as the send_bytes frame does. Its length is always a
bin32, so an item under 64 KiB is up to 3 bytes longer.

Performance-sensitive: adds a send path beside send_bytes (per item).
send and send_bytes do the same work; the bin length check of
encode_bytes_item moved into a helper that finish shares.

Signed-off-by: Ryan Olson <rolson@nvidia.com>
@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between af20c70 and e892f87.

Files selected for processing: 6
  • .claude/skills/perf-review/references/hot-paths.md
  • docs/src/concepts/streaming.md
  • lib/velo/src/streaming.rs
  • lib/velo/src/streaming/frame.rs
  • lib/velo/src/streaming/sender.rs
  • lib/velo/tests/streaming/byte_items.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.

Walkthrough

The streaming API adds ByteItemBuilder and ByteItemFrame for in-place byte-item construction, plus StreamSender::send_frame to send completed frames. Tests cover frame validation, transport behavior, round trips, and interoperability.

Changes

In-place byte-item sending

Layer / File(s) Summary
Build byte-item frames
lib/velo/src/streaming/frame.rs, lib/velo/src/streaming.rs
Adds ByteItemBuilder and ByteItemFrame, shared payload-length validation, and public re-exports. Tests cover decoding, allocation, header validation, and size limits.
Send completed frames
lib/velo/src/streaming/sender.rs, .claude/skills/perf-review/references/hot-paths.md
Adds send_frame, which uses the shared item-send path. Tests cover equivalence, cancellation, allocation, and item limits; an ignored benchmark compares sending methods. The producer list now includes send_frame.
Exercise streaming paths
lib/velo/tests/streaming/byte_items.rs, docs/src/concepts/streaming.md
Adds round-trip and interoperability coverage for in-place frames. The guide describes frame construction, sending behavior, allocation and copy costs, and the bin32 length difference.

Priority: Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Application
  participant ByteItemBuilder
  participant StreamSender
  participant StreamReceiver
  Application->>ByteItemBuilder: Write payload into frame buffer
  ByteItemBuilder-->>Application: finish returns ByteItemFrame
  Application->>StreamSender: send_frame(ByteItemFrame)
  StreamSender->>StreamReceiver: Send encoded item through shared item-send path
  StreamReceiver-->>Application: Decode byte item
Loading

Suggested reviewers: jthomson04

Merge Risk: Minimal · up to e892f

No merge-blocking risk is established by the available evidence.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: adding in-place construction for byte items in streaming.
Docstring Coverage Passed Docstring coverage is 81.48% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviv…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 81.48% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 27 functions across 4 files. (2 skipped: 2 unsupported.)

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.23256% with 64 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/velo/src/streaming/sender.rs 60.34% 43 Missing and 3 partials ⚠️
lib/velo/src/streaming/frame.rs 81.81% 14 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@ryanolson
ryanolson marked this pull request as ready for review October 27, 2026 07:59
@ryanolson
ryanolson requested a balanced review from Copilot October 27, 2026 08:00

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@ryanolson

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@ryanolson
ryanolson requested a review from jthomson04 October 27, 2026 08:25
@ryanolson
ryanolson merged commit 9f73b9e into main Oct 27, 2026
17 of 18 checks passed
@ryanolson
ryanolson deleted the feat/byte-item-builder branch October 27, 2026 19:41
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