Skip to content

maps: add ring buffer batch API#1479

Open
tamird wants to merge 2 commits into
mainfrom
ring-buf-consume-batch
Open

maps: add ring buffer batch API#1479
tamird wants to merge 2 commits into
mainfrom
ring-buf-consume-batch

Conversation

@tamird
Copy link
Copy Markdown
Member

@tamird tamird commented Feb 17, 2026

This implementation should have lower overhead than RingBuffer::next and
by avoiding the overhead associated with RingBufItem.


This change is Reviewable

Copilot AI review requested due to automatic review settings February 17, 2026 21:48
@netlify
Copy link
Copy Markdown

netlify Bot commented Feb 17, 2026

Deploy Preview for aya-rs-docs ready!

Name Link
🔨 Latest commit 5b3badd
🔍 Latest deploy log https://app.netlify.com/projects/aya-rs-docs/deploys/69f402e6acac3300089757db
😎 Deploy Preview https://deploy-preview-1479--aya-rs-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a batch API for ring buffer consumption that amortizes atomic writes of the consumer position. Instead of committing the position after each item, the batch API commits once when the batch is dropped, improving performance when consuming multiple items.

Changes:

  • Added RingBuf::batch() method to create a batch reader
  • Added RingBufBatch struct that defers consumer position commits
  • Refactored internal logic to support both immediate and batched commits
  • Updated all tests and aya-log to use the new batch API

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
aya/src/maps/ring_buf.rs Core implementation of batch API: new RingBufBatch struct, refactored ProducerData::next() to support deferred commits, split ConsumerPos operations into consume() and commit()
aya-log/src/lib.rs Updated flush() method to use batch API for improved performance
test/integration-test/src/tests/uprobe_cookie.rs Updated test to use batch API, simplified error handling
test/integration-test/src/tests/ring_buf.rs Updated all ring buffer tests to use batch API, removed unused anyhow import, simplified error handling
test/integration-test/src/tests/load.rs Updated test to use batch API with proper scoping
xtask/public-api/aya.txt Added new public API entries for RingBuf::batch() and RingBufBatch

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@tamird tamird force-pushed the ring-buf-consume-batch branch 8 times, most recently from 582a40e to 3e58029 Compare February 19, 2026 00:23
@tamird tamird requested a review from Copilot February 19, 2026 11:09
@tamird
Copy link
Copy Markdown
Member Author

tamird commented Feb 19, 2026

@codex review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

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

@vadorovsky vadorovsky self-requested a review February 19, 2026 15:55
@tamird tamird force-pushed the ring-buf-consume-batch branch 3 times, most recently from 6104cba to 403a44f Compare February 19, 2026 17:26
Copy link
Copy Markdown
Member

@ajwerner ajwerner left a comment

Choose a reason for hiding this comment

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

Did something concrete motivate this change? When I first saw the title I was an expecting a different sort of API that might enable the consumer to do vectorized processing of items and then to selectively choose where to commit from the returned batch. None of the existing APIs let the consumer client examine multiple items with a shared lifetime so you can't really make a tight loop over a batch. Maybe that doesn't matter.

I can see how this patch helps reduce contention between the producer and consumer on the consumer position. I'm okay with this change but would love to see a motivating benchmark or something.

Comment thread aya/src/maps/ring_buf.rs
Item::Discard { len } => consume(consumer, advanced, len),
Item::Data(data) => {
// This must be deferred in case `f` panics.
scopeguard::defer! { consume(consumer, advanced, data.len()) };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If f panics do you definitely want to consume the item? I think it's a reasonable contract for the use case because panicking in a loop is bad, but it is worth documenting and I could see it being pushed into f if needed (like the caller of this function in some way tracks if it panicked)

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.

That's a reasonable thing to call out. It also suggests that you might want control over consumption - you can imagine a combinator like TakeWhile that needs to peek and thus may elect to halt iteration without consuming the last item.

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.

Actually the take_while docs explicitly mention this; the rejected element is itself consumed.

https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.take_while

Still, this is a unlike a normal iterator because it's yielding borrowed data. Do you think it would be useful to allow the caller to explicitly (or implicitly, e.g. via panic) decide to not consume data?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it's not worth doing anything about. If some consumer is so worried about panicking they can copy the data out or something like that.

@tamird
Copy link
Copy Markdown
Member Author

tamird commented Feb 20, 2026

Did something concrete motivate this change? When I first saw the title I was an expecting a different sort of API that might enable the consumer to do vectorized processing of items and then to selectively choose where to commit from the returned batch. None of the existing APIs let the consumer client examine multiple items with a shared lifetime so you can't really make a tight loop over a batch. Maybe that doesn't matter.

It was sort of like that initially, but it ended up having worse performance than the existing API. You're right that there's no API for iterating in batches. I'm also not exactly sure how you're implement such an API because if you field 2 items and the later item is dropped, what do you do? How do you express this ordering in the type system?

I can see how this patch helps reduce contention between the producer and consumer on the consumer position. I'm okay with this change but would love to see a motivating benchmark or something.

In addition to reducing contention it also optimizes the consumer; the consumer code path is up to 75% faster. I need to figure out how to properly integrate the benchmarks into the integration test harness.

@alessandrod
Copy link
Copy Markdown
Collaborator

I haven't reviewed yet, but batching makes sense, I've been working on XDP stuff recently and batching vs non batching makes GB/s of difference

@tamird tamird requested a review from ajwerner April 30, 2026 15:14
Copy link
Copy Markdown
Member Author

@tamird tamird left a comment

Choose a reason for hiding this comment

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

Reviews appreciated! I haven't had time to investigate the bench harness - running benchmarks in a VM is annoying (for collecting results)

@tamird made 2 comments.
Reviewable status: 0 of 14 files reviewed, 1 unresolved discussion (waiting on ajwerner and vadorovsky).

Comment thread aya/src/maps/ring_buf.rs
Item::Discard { len } => consume(consumer, advanced, len),
Item::Data(data) => {
// This must be deferred in case `f` panics.
scopeguard::defer! { consume(consumer, advanced, data.len()) };
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.

That's a reasonable thing to call out. It also suggests that you might want control over consumption - you can imagine a combinator like TakeWhile that needs to peek and thus may elect to halt iteration without consuming the last item.

Copy link
Copy Markdown
Member Author

@tamird tamird left a comment

Choose a reason for hiding this comment

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

@tamird made 1 comment.
Reviewable status: 0 of 14 files reviewed, 1 unresolved discussion (waiting on ajwerner and vadorovsky).

Comment thread aya/src/maps/ring_buf.rs
Item::Discard { len } => consume(consumer, advanced, len),
Item::Data(data) => {
// This must be deferred in case `f` panics.
scopeguard::defer! { consume(consumer, advanced, data.len()) };
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.

Actually the take_while docs explicitly mention this; the rejected element is itself consumed.

https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.take_while

Still, this is a unlike a normal iterator because it's yielding borrowed data. Do you think it would be useful to allow the caller to explicitly (or implicitly, e.g. via panic) decide to not consume data?

@vadorovsky
Copy link
Copy Markdown
Member

I haven't had time to investigate the bench harness - running benchmarks in a VM is annoying (for collecting results)

And also not indicative IMO, we should definitely bench on bare metal Linux hosts. If you tell me how do you intend to benchmark it - or even better - add some benchmarks alongside the tests, I'm happy to check it out on a 24 core Epyc machine.

@tamird
Copy link
Copy Markdown
Member Author

tamird commented Apr 30, 2026

I haven't had time to investigate the bench harness - running benchmarks in a VM is annoying (for collecting results)

And also not indicative IMO, we should definitely bench on bare metal Linux hosts. If you tell me how do you intend to benchmark it - or even better - add some benchmarks alongside the tests, I'm happy to check it out on a 24 core Epyc machine.

Yeah i agree with this of course but we still need to support running the benchmarks in the VM so that we can easily check that they actually work. Maybe that narrows the scope enough to be feasible. I have it in a local branch, I'll try to find time to get this over the finish line.

Copy link
Copy Markdown
Member

@ajwerner ajwerner left a comment

Choose a reason for hiding this comment

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

@ajwerner reviewed 14 files and all commit messages, made 2 comments, and resolved 1 discussion.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on tamird and vadorovsky).


aya/src/maps/ring_buf.rs line 467 at r3 (raw file):

        consumer: &'a mut ConsumerPos,
        mut advanced: Option<&'a mut bool>,
    ) -> Option<RingBufItem<'a>> {

I feel like I must be missing something -- this function was introduced to take this advanced parameter but then as far as I can tell, it's only ever called with None. It seems to me like this function was intended to be used by try_fold but then some AI bot got frustrated with a borrow-checker and decided to back that out and just inline the code from here into try_fold?

Comment thread aya/src/maps/ring_buf.rs
Item::Discard { len } => consume(consumer, advanced, len),
Item::Data(data) => {
// This must be deferred in case `f` panics.
scopeguard::defer! { consume(consumer, advanced, data.len()) };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it's not worth doing anything about. If some consumer is so worried about panicking they can copy the data out or something like that.

@tamird tamird requested a review from ajwerner May 1, 2026 00:52
Copy link
Copy Markdown
Member Author

@tamird tamird left a comment

Choose a reason for hiding this comment

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

@tamird made 1 comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on ajwerner and vadorovsky).


aya/src/maps/ring_buf.rs line 467 at r3 (raw file):

Previously, ajwerner wrote…

I feel like I must be missing something -- this function was introduced to take this advanced parameter but then as far as I can tell, it's only ever called with None. It seems to me like this function was intended to be used by try_fold but then some AI bot got frustrated with a borrow-checker and decided to back that out and just inline the code from here into try_fold?

It's been a while since I did this but I think it was used in one of the earlier API shapes I tried.

I think this function just goes away completely if we go all in on the batch API.

Copy link
Copy Markdown
Member

@ajwerner ajwerner left a comment

Choose a reason for hiding this comment

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

@ajwerner made 1 comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on tamird and vadorovsky).


aya/src/maps/ring_buf.rs line 467 at r3 (raw file):

Previously, tamird (Tamir Duberstein) wrote…

It's been a while since I did this but I think it was used in one of the earlier API shapes I tried.

I think this function just goes away completely if we go all in on the batch API.

fine, but why keep the advanced param if nobody uses it?

@tamird tamird force-pushed the ring-buf-consume-batch branch from 403a44f to 797559b Compare May 1, 2026 01:06
@tamird tamird requested a review from a team as a code owner May 1, 2026 01:06
@tamird tamird requested a review from ajwerner May 1, 2026 01:07
Copy link
Copy Markdown
Member Author

@tamird tamird left a comment

Choose a reason for hiding this comment

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

@tamird made 1 comment.
Reviewable status: 0 of 14 files reviewed, 1 unresolved discussion (waiting on ajwerner and vadorovsky).


aya/src/maps/ring_buf.rs line 467 at r3 (raw file):

Previously, ajwerner wrote…

fine, but why keep the advanced param if nobody uses it?

It's detritus, I'll remove it. I remember now - an earlier shape of this PR tried to yield a batch of items and do the consumer update only once, but that turned out not to be the expensive part, it was just all the bookkeeping of the RingBufItem itself.

I wasn't suggesting that this parameter would stay, I was just trying to redirect the discussion: does this API look reasonable? do we want to keep the old API?

Anyway, this parameter is removed and I reduced the diff a bit by undoing some of the unnecessary movement.

@tamird tamird force-pushed the ring-buf-consume-batch branch from 797559b to 872c9fd Compare May 1, 2026 01:17
Copy link
Copy Markdown
Member

@ajwerner ajwerner left a comment

Choose a reason for hiding this comment

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

:lgtm:

@ajwerner made 2 comments and resolved 1 discussion.
Reviewable status: 0 of 14 files reviewed, all discussions resolved (waiting on vadorovsky).


aya/src/maps/ring_buf.rs line 467 at r3 (raw file):

does this API look reasonable?

Yes, it's reasonable.

do we want to keep the old API?

I think the old API should stay at least with a deprecation for existing users. For use cases that aren't super high throughput what was there was fine.

Not to be overly inspired by bad code, but cilium/ebpf is extremely widely used in Go and it's just always copying out and committing the consumer offset.

Anyway, this parameter is removed and I reduced the diff a bit by undoing some of the unnecessary movement.

Nice, looks good.

tamird added 2 commits April 30, 2026 21:28
This makes it easier to reuse later in new experimental APIs. Put it in
a new impl block to reduce churn.
This implementation should have lower overhead than `RingBuffer::next` and
by avoiding the overhead associated with `RingBufItem`.
@tamird tamird force-pushed the ring-buf-consume-batch branch from 872c9fd to 5b3badd Compare May 1, 2026 01:33
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.

5 participants