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

Pool Wrapper with Queue #9320

Merged
merged 1 commit into from
Feb 27, 2023
Merged

Pool Wrapper with Queue #9320

merged 1 commit into from
Feb 27, 2023

Conversation

gregw
Copy link
Contributor

@gregw gregw commented Feb 7, 2023

For #9311
This adds a QueuedPool that can wrap any other pool and provided a queue of Entries as a kind of cache.
This is an alternative to #9319

@gregw gregw requested review from lorban and sbordet February 7, 2023 05:37
@gregw gregw changed the title ByteBufferPool with Queue Pool Wrapper with Queue Feb 15, 2023
@gregw
Copy link
Contributor Author

gregw commented Feb 20, 2023

@lorban do you want me to polish this whilst you work on benchmarks, or do you wish to take over the branch?
@lorban @sbordet thoughts on this approach vs #9319 ? Should we just close the other one and only resurrect it if this approach doesn't work for some reason?

@lorban
Copy link
Contributor

lorban commented Feb 20, 2023

@gregw Benchmarks should be working again by tomorrow, in which case taking over this branch is my next step. If I'm late restoring benchmarks, I'll let you know.

I find this approach superior to #9319 so IMHO the latter can be closed unless we find a showstopper on the way.

@gregw gregw mentioned this pull request Feb 20, 2023
@lorban lorban marked this pull request as ready for review February 22, 2023 17:11
@lorban
Copy link
Contributor

lorban commented Feb 22, 2023

I've introduced two Pool implementations:

  • QueuedPool which is based on a ConcurrentLinkedQueue, takes some liberties with the Pool contract and cannot multiplex.
  • CompoundPool which wraps ConcurrentPool and QueuedPool, using a heuristic to figure out the ConcurrentPool size. It also cannot multiplex.

ArrayByteBufferPool simply uses CompoundPool instead of ConcurrentPool, and MultiPartServletTest.testLargePart() now "only" is ~30% slower with ArrayByteBufferPool vs ByteBufferPool.NonPooling which IMHO is reasonable.

I still believe tackling this problem at the Pool level is the right way, but I'm much less convinced about the details (3 Pool impl with one wrapping the other 2, naming, the fact that QueuedPool doesn't strictly follow the Pool contract...).

Copy link
Contributor Author

@gregw gregw left a comment

Choose a reason for hiding this comment

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

I think the basic approach shows promise and I don't think we should hold up a merge too much considering how pathological the current pool can be.

It would be nice to see how we could configure some alternative compound pools in XML.

private final ConcurrentPool<P> concurrentPool;
private final Pool<P> secondBestPool;

public CompoundPool(ConcurrentPool.StrategyType strategyType, int maxSize, boolean cache)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd rather a constructor that just takes 2 other pools as that will allow the full configuration options to be available. Perhaps have some static builder methods that can make some common combinations, but we shouldn't bake only one combo into the class.

Copy link
Contributor

Choose a reason for hiding this comment

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

I considered such constructor, but how to make sure both underlying pools are compatible, especially w.r.t multiplexing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I still think this is the right thing to do, even if we cannot have isMultiplexing.
IT would be better even if the constructor was private an only a static factory method with this signature was available

{
this.concurrentPool = new ConcurrentPool<>(strategyType, ConcurrentPool.MAX_RECOMMENDED_SIZE, cache);
this.secondBestPool = new QueuedPool<>(maxSize - ConcurrentPool.MAX_RECOMMENDED_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.

We need someway of checking that both pools have compatible multiplexing capabilities. Do we need to be able to ask a Pool what it's max multiple is? Or perhaps just ask it if it has a non unary max multiple function in a isMultiplexing method. If one pool is multiplexing, then the other one should be as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a can of worms. I considered adding a isMultiplexing method to Pool but then I realized that multiplexing is based on a constructor-provided ToIntFunction which makes it impossible to be 100% certain both pools are going to use the same, or compatible multiplexing functions.

The best I can think of is to have such isMultiplexing method in Pool and make ConcurrentPool return false only when the constructor that doesn't take a ToIntFunction has been used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that works for me.

Copy link
Contributor

Choose a reason for hiding this comment

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

But does not work, as I can pass a ToIntFunction that returns 1.
I think this class is better as a private inner class of ArrayByteBufferPool, as it's not really reusable that much.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we introduce another pool that can handle queued multiplexed pools, then it will get more usage. It is a util class and I see no problem exposing it. All util classes can be used badly. If we provide some static factory methods that easily build combinations that work well together, then that will encourage good usage. I am OK with a protected constructor an only public static factory methods. Then if somebody really wanted to do something strange, they could extend.

@gregw
Copy link
Contributor Author

gregw commented Feb 23, 2023

Would it be possible to have a multiplexing TreeSetPool? Pool Entries would be stored in a TreeSet that sorted on the remaining multiplex capability of each entry. Thus an acquire would take the most allocated entry (until it had no capacity left and was taken out of the set). Perhaps a TreeSet is not the right data structure as it is hard to remove and then re-add, but some sorted queue could work.
We would need this only if, for example, in the client we have need of more that 256 multiplexed connections to the same destination. That is plausible!

@gregw
Copy link
Contributor Author

gregw commented Feb 23, 2023

Perhaps a PriorityQueue could be used. Entries would be taken from the queue on every acquire, have their multiplex count incremented and then returned to the queue to be re-sorted. On release, we need to be able also remove and re-add the entry, so the remove(Object) operation on the queue needs to be good... which it might be as there is a heap underneath it? Besides, for the multiple use case of connection pools, it does not need to by hyper efficient.

@lorban
Copy link
Contributor

lorban commented Feb 23, 2023

@gregw finding a sorted and concurrent structure with adequate perf to hold the multiplexed entries sounds challenging. I do not think we currently have a good use-case for it, as only H2 connections in the client currently are pooled and using the multiplexing support. But maybe we just found a good reason to not warn when a too large ConcurrentPool is configured?

@lorban lorban force-pushed the jetty-12-QueuedPool branch from 5931d91 to 62d3764 Compare February 23, 2023 11:44
@gregw
Copy link
Contributor Author

gregw commented Feb 23, 2023

It should be fairly simple to find a O(log(n)) sorted queue that will outperform an O(N) search for a connection with remaining capacity. For these kind of use-cases, we are not looking for stunning performance, we just want to avoid the lack of graceful degradation we currently see. Not sucking will be good enough for now.

@gregw
Copy link
Contributor Author

gregw commented Feb 23, 2023

@sbordet, as this was originally my PR, I can't review. I'm OK with it in it's current form (other than my niggle about a better constructor).

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

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

I'm not convinced that CompoundPool and QueuedPool should be public in jetty-util, as they are ok for ABBP, but that's basically it.

Also, I am worried about ConnectionPool performance too now, so I would like to see a more generic solution that supports multiplexing.

* free for other data. A quick test has also shown that ConcurrentPool's perf starts dipping
* noticeably when more than 512 entries are used.
*/
static final int MAX_RECOMMENDED_SIZE = 256;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a biggie, but I'd rename this to OPTIMAL_MAX_SIZE, or RECOMMENDED_MAX_SIZE as "max" refers to the size so they should be closer.

Copy link
Contributor

Choose a reason for hiding this comment

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

WIth such as low value, I am now worried about ConnectionPool having a bad performance, in particular for load testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then let's add a priority queue implementation that will sort entries by multiplexed availability. It should not be too hard to write and may even be OK as the primary pool for the connections, as the concurrent pool has to use a poor strategy to work with multiplexing anyway.

{
this.concurrentPool = new ConcurrentPool<>(strategyType, ConcurrentPool.MAX_RECOMMENDED_SIZE, cache);
this.secondBestPool = new QueuedPool<>(maxSize - ConcurrentPool.MAX_RECOMMENDED_SIZE);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

But does not work, as I can pass a ToIntFunction that returns 1.
I think this class is better as a private inner class of ArrayByteBufferPool, as it's not really reusable that much.

*
* @param <P> the type of the pooled objects
*/
public class QueuedPool<P> implements Pool<P>
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like this class being public in jetty-util, but have the big restriction of not being multiplexed.
For example I would not be able to use it for ConnectionPool.

I think this implementation should be private to ArrayByteBufferPool, but we do need an implementation that supports multiplexing for ConnectionPool.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think we need to make it private. It is a perfectly good pool implementation that just happens not to take a multiplex int function, so it can't do multiplexing.

@lorban
Copy link
Contributor

lorban commented Feb 24, 2023

I've renamed the CompoundPool to ConcurrentOverflowingToQueuedPool and moved it closer to ArrayByteBufferPool as it specifically designed to solve that issue and it's fairly trivial.

QueuedPool can stay in jetty-util IMHO as it's usable the way it is, even if it does not support multiplexing. We can add an extra Pool implementation that is both queue-based and supports multiplexing.

@lorban lorban requested a review from sbordet February 24, 2023 11:46
@gregw
Copy link
Contributor Author

gregw commented Feb 24, 2023

@lorban I'm sorry but ConcurrentOverflowingToQueuedPool is a silly name. The only thing about that class which that name applies to is the constructor. Everything else about that class is just CompoundPool.

We should name the class for what it does, not for hours we use it.

@lorban
Copy link
Contributor

lorban commented Feb 27, 2023

Okay, I've renamed the class back to CompoundPool, explained the overflow in the javadoc instead of the name but made it a private inner class of ArrayByteBufferPool to make it more apparent that it's specific to this use-case only.

I've tried to stick to the "best is the enemy of good" mindset when working on this because:

  • we have a proven perf problem with pooled buffers
  • we have not (yet) a proven perf problem with pooled connections
  • buffers don't need multiplexing
  • QueuedPool is generic, but writing a multiplexing version would require non-trivial extra work
  • Compounding ConcurrentPool with a QueuedPool overflow is very specific to the problem at hand, but solves it well

I'm not against working on a multiplexed QueuedPool nor genericizing the above, it it's IMHO outside the scope of this PR, and I'm not even convinced there's a real need for a generic overflowing pool.

Copy link
Contributor Author

@gregw gregw left a comment

Choose a reason for hiding this comment

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

I agree with the approach of making this PR about fixing the known problems in the ABBP. We can investigate later if we need to generalise more and come up with solutions for multiplexed pools.

I still don't like the constructor of CompoundPool, but since it is private, that is a niggle rather than a show stopper

@lorban lorban force-pushed the jetty-12-QueuedPool branch from 8639a0c to 71506fc Compare February 27, 2023 09:35
@lorban lorban requested review from sbordet and lorban and removed request for lorban February 27, 2023 09:35
Copy link
Contributor Author

@gregw gregw left a comment

Choose a reason for hiding this comment

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

LGTM

This adds a QueuedPool that can wrap any other pool and provided a queue of Entries as a kind of cache.
@lorban lorban force-pushed the jetty-12-QueuedPool branch from d2517c5 to 1122b79 Compare February 27, 2023 12:37
@lorban lorban merged commit 474136d into jetty-12.0.x Feb 27, 2023
@lorban lorban deleted the jetty-12-QueuedPool branch February 27, 2023 12:37
@lorban lorban added Bug For general bugs on Jetty side Performance Jetty 12 labels Feb 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug For general bugs on Jetty side Performance
Projects
None yet
3 participants