Skip to content
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

Add SimpleConcurrentProcessor for Logs, clarify existing export concurrency #4163

Closed

Conversation

cijothomas
Copy link
Member

@cijothomas cijothomas commented Jul 25, 2024

Fixes most of #4134 (Tracing signal not addressed in this PR)

Changes

  1. Clarifies explicitly that the existing built-in processors should not invoke Export() concurrently. This was already the intention (from what I gather!), but not explicitly listed.
  2. Adds SimpleConcurrentProcessor (happy to get alternative name suggestions), which can call Export() concurrently.

.NET, Rust, C++ have their SimpleProcessor already matching this.
Java, Go, Python does not. I believe they can fix the SimpleProcessor implementation to match this. This should be treated as a bug fix. Since SimpleProcessors were meant to be used in test/debug scenarios (stdout exporters), the extra perf hit should not be a concern. If it is indeed a concern, then users can be advised to switch to SimpleConcurrentProcessor.

(I also considered the possibility of adding extra capability to existing SimpleProcessor, but I believe it is better to have a dedicated one to avoid any confusion about "Simple" being in used for high-perf, prod scenarios. Also, adding more capabilities may make SimpleProcessor more complex without much gains.)

Additional background/context:
.NET, Rust, C++ had the equivalent of SimpleConcurrentProcessors for quite a while and was used when exporting to OS native tracing systems like ETW (Windows), Linux user_events. Exporting to these systems are done for scenarios requiring very high performance, and the existing Simple/Batch processors does not allow such high performance, necessitating the need of an official exporting processor.

(Note: A similar PR can be done for tracing sdk as well, but want to first try out in Log SDK. Once this is done, will make a follow up PR to trace sdk as well.)

@cijothomas cijothomas requested review from a team July 25, 2024 15:27
specification/logs/sdk.md Outdated Show resolved Hide resolved
@@ -427,6 +429,21 @@ the configured `processor`.

* `processor` - processor to be isolated.

#### Simple Concurrent processor
Copy link
Member

Choose a reason for hiding this comment

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

I strongly suggest NOT using the term "concurrent" as usually it conveys that there is some of synchronization behind. E.g. "concurrent collections" are collections that can be used in multithreaded code. In some languages there is an idiom that if the type has Concurrent in its name, the method calls are synchronized.

My naming proposal is "Passthrough processor"

Copy link
Contributor

Choose a reason for hiding this comment

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

This resonates with me too -- the use of "concurrent" suggests some kind of attention to concurrency, a limit of some sort in addition to offering concurrency. Maybe the name could be AsynchronousProcessor -- starts the export and returns success immediately.

I admit this worries me a bit -- is the exporter expected to implement some kind of limit, to avoid running out of memory? Can this be composed with the BatchSpanProcessor? What will control whether the BatchSpanProcessor drops spans vs. this processor consuming unlimited amounts of data?

Here, we have an OTel Collector processor that offers unlimited concurrency, but subject to a limit on the total amount of pending data, I consider it a potential solution to the problems posed above: https://github.com/open-telemetry/otel-arrow/tree/main/collector/processor/concurrentbatchprocessor

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe the name could be AsynchronousProcessor -- starts the export and returns success immediately.

That is not the intend. The intended exporters here not just 'starts' the export. It does everything inline (i.e serialization, writing to destination.), and then return success/failure. (from what I know, such exporters are typically writing to ETW, user-events)

Copy link
Member Author

Choose a reason for hiding this comment

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

is the exporter expected to implement some kind of limit, to avoid running out of memory?

No. Such exporters (etw/user-events) does not buffer anything. The logRecord is serialized and handed over to destination inline. ETW/user-events system is backed by OperatingSystem kernel memory, and they have ample mechanisms to keep memory in control, but those are outside the scope of the exporter.

Copy link
Member

@pellared pellared Jul 29, 2024

Choose a reason for hiding this comment

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

@cijothomas any thoughts about my naming proposal?

My other proposals are

  • "Direct processor" meaning that it directly calls the exporter.

  • "Adapter processor" meaning that it is only an adapter and has no logic/ synchronization

Copy link
Member Author

Choose a reason for hiding this comment

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

naming is hard 🤣 I am not sure if any of the alternate suggestions are significantly better. I'll keep thinking and hope we get more suggestions.

specification/logs/sdk.md Outdated Show resolved Hide resolved
Copy link
Member

@pellared pellared left a comment

Choose a reason for hiding this comment

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

👍

specification/logs/sdk.md Outdated Show resolved Hide resolved
specification/logs/sdk.md Outdated Show resolved Hide resolved
specification/logs/sdk.md Outdated Show resolved Hide resolved
specification/logs/sdk.md Outdated Show resolved Hide resolved
Depending on the implementation the result of the export may be returned to the
`Export` MAY be called concurrently for the same exporter instance if paired
with [Simple Concurrent Processor](#simple-concurrent-processor). Each exporter
implementation MUST document whether it supports concurrent `Export` calls, and
Copy link
Member

Choose a reason for hiding this comment

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

  1. We cannot force custom implementation on how they document their implementations
  2. It is not only about exporting but also ForceFlush and Shutdown
Suggested change
implementation MUST document whether it supports concurrent `Export` calls, and
implementation provided by the SDK MUST document whether it is concurrent safe, and

Copy link
Member Author

Choose a reason for hiding this comment

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

We cannot enforce, but the spec wording is to make sure any person authoring own exporter should follow this spec. If they don't follow, then there could be undesired behavior.
Alternatively, we can word it in such a way that "It must be documented to the exporter authors that they should document the exporters' concurrency characteristics....

Copy link
Member

Choose a reason for hiding this comment

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

I believe this was part of why it was went with that the processor should called all exporters the same and not have to worry about concurrency.

Copy link
Member

Choose a reason for hiding this comment

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

@tsloughter, the users could still use processors that does not require the exporters to be concurrent safe. But we would give a more performant option for cases where exporter are concurrent safe and can be used synchronously.

@cijothomas, the alternative could be that such exporter packages (like ETW or user_events exporters) would provide their own processors which are "tailored" to their exporters. It can give more flexibility and not increase the SDK functionality surface.

Copy link
Member Author

Choose a reason for hiding this comment

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

the alternative could be that such exporter packages (like ETW or user_events exporters) would provide their own processors which are "tailored" to their exporters

You are right! That is exactly what we have been doing for years. But that does not mean spec shouldn't support them.
The increase in SDK functionality surface is necessary, to support such scenarios. The spec already has wording that state implementations must have simple and batch (meaning others are optional), so additional processors don't put extra burden on implementations, unless they chose to support.

Copy link
Member

Choose a reason for hiding this comment

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

What is more (I have forgotten tot call it out explictly) I think that e.g. for ETW we can simply provide a single EtwProcessor which does the exporting. There is no need for batching or synchronization so there is no need to provide an Exporter interface implementation.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that e.g. for ETW we can simply provide a single EtwProcessor which does the exporting

Yes that is also possible. (In certain ways, OTel Rust does that. Its etwexporter is not really an exporter, but a ~processor).
But I think it is best to model exporters (the thing which does serialization, export telemetry to outside the process) as exporters itself consistently.

Copy link
Member

@pellared pellared Aug 1, 2024

Choose a reason for hiding this comment

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

But I think it is best to model exporters

I am not really sure as these exporters are emitting batches. And for use cases like ETW and user_events you probably prefer to operate on "single" log record.

Copy link
Member Author

Choose a reason for hiding this comment

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

They are exporters! (They do the job of serializing and transferring the telemetry to an external entity.) Whether it gets a batch of 1 item or multiple is purely based on choice of processor used. (when used with SimpleProcessor, even OTLPExporter gets a batch of single item only).

specification/logs/sdk.md Outdated Show resolved Hide resolved
@tsloughter
Copy link
Member

Deleted my last comment because I had misread what was and wasn't in the current spec for Logs and don't want to confuse anyone reading these later.

Exporting can be done concurrently, what is the reason for needing a "ConcurrentProcessor"?

@cijothomas
Copy link
Member Author

@tsloughter

Exporting can be done concurrently, what is the reason for needing a "ConcurrentProcessor"?

I don't think it is possible to achieve what we want today. We need exporter.export() method called synchronously from the processor. More specifically, when logger.logsomething("hello", foo=bar); statement returns, the LogRecord for that is already sent to processor, and to exporter and serialized and exported! Put differently, if the program crashes the moment after logsomething() returns, the user can be assured that no telemetry is lost due to being stuck in in-memory buffers.

@tsloughter
Copy link
Member

@cijothomas right, and it is called synchronously. It is the exporter that handles concurrency, or lack there of. By default SimpleProcessor and builtin exporter must not return from export for spans and logs in order to support cases like you describe and it is relied on by users of like aws lambda, but it remains the exporter where concurrency is handled, not the processor.

@tsloughter
Copy link
Member

Talking on Slack I now see what SimpleConcurrentProcessor is meant to achieve and will give here my description of it in case anyone else messed up like me and jumped to the conclusion it was meant to achieve the already hashed out "concurrent export" functionality:

The issue arises when you have N processes/threads each calling logger.log(record) concurrently and not wanting to block on any others data being sent but does want to block on their record being sent.

To repeat here what I said on slack, I would not do this with a SimpleConcurrentProcessor. I want to look at the existing examples given of exporters before I describe what I'd do in Erlang since it may just be more confusing than useful, but it would keep the restriction on Export intact and not add a SimpleConcurrentProcessor -- as well as have the ability to build on top a way to alleviate lock contention between threads on use of the SimpleProcessor through a SimpleProcessorPool.

@cijothomas
Copy link
Member Author

alleviate lock contention between threads on use of the SimpleProcessor through a SimpleProcessorPool.

I am not sure what is SimpleProcessorPool.

@arminru arminru changed the title Add SimpleConcurrentProcessor, clarify existing export concurrency Add SimpleConcurrentProcessor for Logs, clarify existing export concurrency Jul 30, 2024
@jmacd
Copy link
Contributor

jmacd commented Jul 31, 2024

Relates to #3616

@pellared
Copy link
Member

pellared commented Aug 1, 2024

My own previous suggestion:

It is not only about exporting but also ForceFlush and Shutdown

I double-checked and this would be a a breaking change in the specification.

Right now, it is allowed to call Shutdown or ForceFlush in parallel with Export. However, calls to Export has to be synchronized.

I created #4173 based on this PR to separate clarification of the export concurrency from proposing a new exporting processor.

Copy link

This PR was marked stale due to lack of activity. It will be closed in 7 days.

@github-actions github-actions bot added the Stale label Aug 13, 2024
Copy link

Closed as inactive. Feel free to reopen if this PR is still being worked on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants