-
Notifications
You must be signed in to change notification settings - Fork 463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use native Rust support for async traits in LogExporter::export() method (11% improvement) #2374
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2374 +/- ##
=======================================
- Coverage 76.8% 76.7% -0.2%
=======================================
Files 122 122
Lines 22217 22266 +49
=======================================
+ Hits 17082 17093 +11
- Misses 5135 5173 +38 ☔ View full report in Codecov by Sentry. |
OK, have updated this PR after removing the Before this PR (the regression introduced by #2380 because of inclusion of exporter ~44 -> ~40):
After this PR:
The cons of introducing the native-async in trait would be
Current export method could be simplified a bit for lifetime specifiers. I will look into that. But in general, open for discussion if we should bump the msrv. |
Based on today's SIG discussion: Awaiting more feedback. |
This is ready for review now, with #2417 to bump msrv is merged |
Have updated the description now to reflect the improvement seen through stress test, which is ~11% |
stress/src/logs.rs
Outdated
@@ -37,14 +39,15 @@ pub struct MockLogProcessor { | |||
impl LogProcessor for MockLogProcessor { | |||
fn emit(&self, record: &mut opentelemetry_sdk::logs::LogRecord, scope: &InstrumentationScope) { | |||
let log_tuple = &[(record as &LogRecord, scope)]; | |||
let _ = futures_executor::block_on(self.exporter.export(LogBatch::new(log_tuple))); | |||
let log_batch = LogBatch::new(log_tuple); | |||
let _ = futures_executor::block_on(self.exporter.export(&log_batch)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The overall flow looks complex..
emit gets a mutable ref to LogRecord. It is ref by design (and mut by design.)
processor, if need to batch it and send, can clone and keep the copy. When it calls the exporter, can it pass ownership the exporter? Wondering why we only pass a ref to the exporter? The processor has no reason to retain ownership anyway, so why not give it up, to the exporter?
Thinking out loud to see if this can be simplified without sacrificing perf..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good point. We are passing LogBatch
as reference. LogBatch
is a lightweight wrapper around the slice of tuple of reference to LogRecord
and InstrumentationScope
. Passing it by value won't change the ownership of this borrowed data (record and scope). We can change batch to be passed as value (though record and scope would still remain as reference). This can simplify the design (remove 'a need). However, reference just shows the intent better, and need to test the perf (theoretically it shouldn't affect).
ps - Need to dig the reason for passing record and scope as reference, I remember we discussed and agreed for it in earlier discussion. Here, we are only taking about passing batch as reference or value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The processor has no reason to retain ownership anyway, so why not give it up, to the exporter?
Probably it was not very clear from my above message. Just to clarify - this PR is not changing the ownership model. The exporter always gets the reference to the LogRecord
, and it clones if the LogRecord
needs to be used beyond the export function lifetime. Are we now discussing the existing design?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reference just shows the intent better,
Not so sure, since we are passing a custom structure LogBatch
, which is defined to contains refs only.
Or we can pass slice of refs only, avoiding LogBatch structure entirely. This is better discussed separately.
Are we now discussing the existing design?
This PR is changing existing design - previously owned LogBatch was passed, now we pass a &LogBatch only. It is not clear if that is required for using async native or it is unrelated to that.
If there is no need to change from passing LogBatch to &LogBatch to make async-native work, then lets revert that part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is no need to change from passing LogBatch to &LogBatch to make async-native work, then lets revert that part.
This is reverted now.
@@ -81,7 +79,11 @@ pub trait LogExporter: Send + Sync + Debug { | |||
/// A `LogResult<()>`, which is a result type indicating either a successful export (with | |||
/// `Ok(())`) or an error (`Err(LogError)`) if the export operation failed. | |||
/// | |||
async fn export(&self, batch: LogBatch<'_>) -> LogResult<()>; | |||
fn export( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add a changelog entry as this affects custom exporter authors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
The perf gains are only applicable for concurrently exporting log processors that exports without any batching (like etw exporting).
For components in this repo (like OTLP), export is not in hot path and won't see perf gains.
But irrespective, this still is an improvement and also eliminates one more dependency!
@TommyCpp would you have time to another look? |
Merging to unblock other works that'd otherwise have to deal with merge conflicts. Please continue reviewing, and we can address feedbacks in followups. |
Changes
Continuation to #2143, more to show feasibility, performance improvements, and have discussion before raising the PR for eventual review, this PR demonstrate using async traits support in Rust exporter. The support was added in Rust v1.75, while the msrv as of now is v1.70 for otel-sdk
The change is:
Existing:
PR:
There is 11% improvement as seen from stress test:
Before PR:
Number of threads: 16
Throughput: 40,468,186 iterations/sec
Throughput: 40,792,480 iterations/sec
Throughput: 40,456,227 iterations/sec
Throughput: 40,455,461 iterations/sec
After this PR:
Throughput: 44,793,174 iterations/sec
Throughput: 45,321,168 iterations/sec
Throughput: 45,387,969 iterations/sec
Throughput: 45,629,648 iterations/sec
Throughput: 45,607,326 iterations/sec
Throughput: 45,303,331 iterations/sec
Throughput: 45,634,108 iterations/sec
Thanks,
Lalit
Merge requirement checklist
CHANGELOG.md
files updated for non-trivial, user-facing changes