Skip to content

Conversation

@merlimat
Copy link
Contributor

As outlined in https://lists.apache.org/thread.html/102383ea42f473f36720637e41af0ee83fc38d9f992736e0d1a7f985@%3Cdev.bookkeeper.apache.org%3E

right now OrderedSafeExecutor is implemented on top of OrderedScheduler. There are few problems with this approach that are causing impact on performance:

  1. OrderedScheduler is a ScheduledExecutorService which uses a priority queue for tasks. The priority queue has a single mutex for both publishers/consumers on the queue
  2. There are many objects created for each task submission, due to listenable future decorators

Since in all cases in critical write/read path we don't need delay task execution or futures, we should try to have a light weight execution for that.

Modifications

  • Inverted the hierarchy between OrderedSafeExecutor and OrderedScheduler. Now the base class is OrderedSafeExecutor and the other extends from it, since it provides additional methods.
  • Moved OrderedSafeExecutor in bookkeeper-common since OrderedScheduler was already there.
  • Moved OrderedSafeGenericCallback in its own file, since it needs to be in bookkeeper-server module at this point.
  • Changed some method names from submitOrdered() into executeOrdered() to be consistent with JDK name (submit() returns a future while execute() returns void).
  • Changed BookKeeper instance of scheduler into OrderedScheduler so that the few cases which were using the mainWorkerPool could be easily converted to use the scheduler instead.

@merlimat merlimat added this to the 4.7.0 milestone Mar 29, 2018
@merlimat merlimat self-assigned this Mar 29, 2018
* method.
*/
@Slf4j
public class OrderedSafeExecutor implements ExecutorService {
Copy link
Member

Choose a reason for hiding this comment

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

Since we are in a new package, can you rename it to "OrderedExecutor" to be consistent with "OrderedScheduler"? This also make a distinguish from the old OrderedSafeExecutor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, makes sense.

Also, regarding SafeRunnable, I think we should be doing that internally, just taking a Runnable, though that's another story from this PR.

Copy link
Contributor

@eolivelli eolivelli left a comment

Choose a reason for hiding this comment

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

Great idea!
I left some questions but overall is very good


@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
checkQueue();
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't you pass something like the size of the list? checkQueue assumes you are going to add 1 item.
I mean something like
checkQueue(tasks.size())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

/**
* Abstract builder class to build {@link OrderedScheduler}.
*/
public abstract static class AbstractBuilder<T extends OrderedExecutor> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it could be package private

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, it needs to be public because the builder methods are returning AbstractBuilder<T> so that needs to be visible to callers.

}

public <T> ListenableFuture<T> submitOrdered(long orderingKey, Callable<T> task) {
SettableFuture<T> future = SettableFuture.create();
Copy link
Contributor

Choose a reason for hiding this comment

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

This is more lightweight than CompletableFuture or is there any other reason to use this instead of CF?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The existing code for submitting a Callable was already expecting ListenableFuture. We can (and should) change to CompletableFuture though I didn't want to cram too many changes in a single PR.

@jvrao
Copy link
Contributor

jvrao commented Mar 30, 2018

@dlg99 Please review.

@jvrao
Copy link
Contributor

jvrao commented Mar 30, 2018

@merlimat As this is perf focused change, any data / micro benchmarks to validate our assumptions?

/**
* A builder class for an OrderedSafeExecutor.
*/
public static class Builder extends AbstractBuilder<OrderedExecutor> {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use lombok's @builder to replace this? sounds like one can override build() method: projectlombok/lombok#1144

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I didn't change it here to limit the scope of this already big PR. I think we should do in separate change.

@sijie
Copy link
Member

sijie commented Apr 1, 2018

@eolivelli can you review this again? @merlimat already addressed your comments.

Copy link
Contributor

@eolivelli eolivelli left a comment

Choose a reason for hiding this comment

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

+1 great work @merlimat !

@merlimat
Copy link
Contributor Author

merlimat commented Apr 2, 2018

@jvrao I have added a simple microbenchmark (22eba18)

Results:

Benchmark                                 (executorName)   Mode  Cnt    Score     Error   Units
OrderedExecutorBenchmark.submitAndWait    JDK-ThreadPool  thrpt    3  417.014 ±  54.313  ops/ms
OrderedExecutorBenchmark.submitAndWait   OrderedExecutor  thrpt    3  406.132 ± 260.011  ops/ms
OrderedExecutorBenchmark.submitAndWait  OrderedScheduler  thrpt    3  337.974 ± 325.934  ops/ms

@dlg99
Copy link
Contributor

dlg99 commented Apr 2, 2018

@merlimat @jvrao "337.974 ± 325.934" is a smell. it's too much of a range.
Results of "JDK-ThreadPool" and "OrderedExecutor" are close except OE's have wide range.
It is either a problem with test or with the change itself. Change looks ok to me so let's assume the test is at fault.

looking at microbenchmark, it always uses 1 threaded executor and 16 threads that submit/wait for tasks. I think it hits bottleneck in that one thread's rather than in the queue passing tasks between threads. I'd create executor with 8 (half or producer's) threads an give it a shot. or run the test with 1/2/4/8/16 threads in executor, use num threads as another Param for the test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants