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

Poll for messages using TaskExecutor #178

Merged

Conversation

mimischi
Copy link
Member

We currently sleep for pollInterval when no new messages have been polled from the cluster. This leads to unnecessary slowness of the client. Instead of doing that, we now break up the polling of messages into two distinct approaches:

  1. Attempt to poll synchronously: if there a message is polled, we return it. If there is no message, we immediately go to step 2.
  2. We create a DispatchQueue and run the consumerPoll on it using withTaskExecutorPreference. We make the consumerPoll call wait for up to pollInterval before bailing.

This prevents us from sleeping on the running thread, and frees up cycles to do other work if required.

Resolves #165

@mimischi mimischi added the 🔨 semver/patch No public API change. label Nov 14, 2024
We currently sleep for `pollInterval` when no new messages have been
polled from the cluster. This leads to unnecessary slowness of the
client. Instead of doing that, we now break up the polling of messages
into two distinct approaches:

1. Attempt to poll synchronously: if there a message is polled, we
   return it. If there is no message, we immediately go to step 2.
2. We create a `DispatchQueue` and run the `consumerPoll` on it using
   `withTaskExecutorPreference`. We make the `consumerPoll` call wait
   for up to `pollInterval` before bailing.

This prevents us from sleeping on the running thread, and frees up
cycles to do other work if required.

Resolves swift-server#165
Can't use the current implementation before Swift 6.
@mimischi mimischi force-pushed the issue-165-nonblocking-poll-task-executor branch from 9c4b7dd to a84f1c0 Compare November 14, 2024 20:00
@mimischi
Copy link
Member Author

Haven't done any benchmarks just yet. Would like to do some, before we go about merging this.

@mimischi
Copy link
Member Author

The benchmark looks better, but the benchmark suite says all differences are negative—and it's setting a positive difference as the threshold, so while this PR is better, the benchmark ends up failing?


=====================================================================================================
Threshold deviations for SwiftKafkaConsumerBenchmarks:SwiftKafkaConsumer_basic_consumer_messages_1000
=====================================================================================================
╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Time (wall clock) (ms, %)                │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │             642 │             149 │             -76 │              35 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Time (total CPU) (ms, %)                 │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │              30 │              17 │             -42 │              35 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Throughput (# / s) (#, %)                │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │               2 │               7 │            -250 │              35 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Context switches (#, %)                  │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │            1230 │             392 │             -68 │              35 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ (Alloc + Retain) - Release Δ (#, %)      │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │            2004 │            1223 │             -39 │              20 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Releases (K, %)                          │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │              14 │              11 │             -22 │              20 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

=========================================================================================================
Threshold deviations for SwiftKafkaConsumerBenchmarks:SwiftKafkaConsumer_with_offset_commit_messages_1000
=========================================================================================================
╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Time (wall clock) (ms, %)                │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │             635 │             119 │             -81 │              35 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Time (total CPU) (ms, %)                 │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │              38 │              15 │             -62 │              35 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Throughput (# / s) (#, %)                │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │               2 │               8 │            -300 │              35 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ Context switches (#, %)                  │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │            1432 │             507 │             -64 │              35 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

╒══════════════════════════════════════════╤═════════════════╤═════════════════╤═════════════════╤═════════════════╕
│ (Alloc + Retain) - Release Δ (#, %)      │            main │              PR │    Difference % │     Threshold % │
╞══════════════════════════════════════════╪═════════════════╪═════════════════╪═════════════════╪═════════════════╡
│ p90                                      │            1994 │            1186 │             -40 │              20 │
╘══════════════════════════════════════════╧═════════════════╧═════════════════╧═════════════════╧═════════════════╛

New baseline 'PR' is BETTER than the 'main' baseline thresholds.

error: benchmarkThresholdImprovement
Retcode is 1
Benchmark failed

@@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import Dispatch
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't think we need this import here

Copy link
Member Author

@mimischi mimischi Nov 15, 2024

Choose a reason for hiding this comment

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

Whoops. Left over from a refactor. Is there a linter that can help point out unused imports?

Sources/Kafka/KafkaConsumer.swift Show resolved Hide resolved
#if swift(>=6.0)
// Wait on a separate thread for the next message.
return try await withTaskExecutorPreference(queue) {
try client.consumerPoll(for: Int32(self.pollInterval.inMilliseconds))
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens after the time out?

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 attempt to retrieve a message for self.pollInterval. If there's still no message, we return nil—the same behavior as in the above if let. I'd expect we get caught up in the while-loop on line 100 until we do receive a message eventually.

Sources/Kafka/Utilities/NaiveQueueExecutor.swift Outdated Show resolved Hide resolved
Sources/Kafka/Utilities/NaiveQueueExecutor.swift Outdated Show resolved Hide resolved
public func enqueue(_ _job: consuming ExecutorJob) {
let job = UnownedJob(_job)
queue.async {
job.runSynchronously(
Copy link
Contributor

Choose a reason for hiding this comment

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

@ktoso Should we call this runSynchronously or the one that also takes isolatedTo?

@FranzBusch
Copy link
Contributor

The benchmark looks better, but the benchmark suite says all differences are negative—and it's setting a positive difference as the threshold, so while this PR is better, the benchmark ends up failing?

Yes the benchmarks improved significantly! Negative numbers means fewer allocations.

@mimischi
Copy link
Member Author

Negative numbers means fewer allocations.

It's just funny that the benchmarking suite thinks the benchmark failed, because we are below the positive threshold with our negative numbers :)

@mimischi
Copy link
Member Author

Also, should this PR—once approved—update the benchmark baseline as well?

@FranzBusch
Copy link
Contributor

Also, should this PR—once approved—update the benchmark baseline as well?

Yes. We intentionally fail the benchmarks when we improve so we set a new thresholds to make sure we don't regress again.

@hassila
Copy link

hassila commented Nov 15, 2024

Negative numbers means fewer allocations.

It's just funny that the benchmarking suite thinks the benchmark failed, because we are below the positive threshold with our negative numbers :)

There are different return codes so CI can choose how to handle that - for an expected improvement one would check in new baselines manually anyway - for an unexpected improvement something may be wrong with eg. The benchmark setup.

But super nice improvements here 👍🏻

@mimischi
Copy link
Member Author

@hassila Oh, fair enough. I've not thought about the unexpected improvement situation. Thanks for bringing that up!

let job = UnownedJob(_job)
queue.async {
job.runSynchronously(
on: self.asUnownedTaskExecutor()
Copy link
Contributor

Choose a reason for hiding this comment

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

if you're able to use the

public func runSynchronously(isolatedTo serialExecutor: UnownedSerialExecutor,
                             taskExecutor: UnownedTaskExecutor) {

here and pass the queue's UnownedSerialExecutor that would be preferable as it would AFAIR be more correct in tracking where this is isolated to.

If this is a pain because the queue does not conform to SerialExecutor on some platforms still... then perhaps conditionalize it to platforms where it is? Or leave as is and let me know and we'll chase fixing the conformance.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got you. Since we own the underlying queue here is owned by us nothing should be isolated to it.

Copy link
Contributor

@ktoso ktoso left a comment

Choose a reason for hiding this comment

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

This looks good to me, check the run func but either way I think this is looking good

@FranzBusch FranzBusch merged commit 4a74297 into swift-server:main Nov 18, 2024
23 checks passed
@mimischi mimischi deleted the issue-165-nonblocking-poll-task-executor branch November 18, 2024 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use blocking poll and task executor preferences
4 participants