HikariCP version: 2.6.0
JDK version : 1.8.0_40-b26
Database : Oracle
Driver version : 11.2.0.3.0
Hi,
We've recently upgraded to 2.6.0 from 2.4.7 and noticed that when our pool saturates and requests start to queue up, then our app CPU usage spikes pretty high (from 10%-15% to 90%). We've traced it back to this change (https://github.com/brettwooldridge/HikariCP/commit/f0b3c520c9d8f5cce3ff04b811165ec61d9ef364#diff-0dac8ca2574085e5192a8188ca22d42eR181) in 2.6.0 where the ConcurrentBag implementation has a yield() that causes a tight spin loop that will eat CPU until either a timeout or a connection is handed off. In our case, all of our pending threads spinned and ate CPU until the timeout whilst slowing down the application recovery and GC. The app went into a death spiral until it recovered. Our application is very high traffic and dropped requests are not good. We are going to revert back to 2.4.7 to be safe.
Is this something that can be looked at again? We're thinking that:
Interested in your thoughts. Thanks!
Would you mind cloning the repo and trying 2.6.1-SNAPSHOT? Building is easy: mvn package. The jar will be in the target subdirectory.
EDIT: v2.6.1-SNAPSHOT contains a change to reduce "barging" of active threads over waiting threads. We actually have a better fix in mind, but would like to test it more before committing it.
Snapshot JAR available here. Can you test it can give us feedback as to whether the CPU usage declines?
Thanks, we'll give it a try on Monday or Tuesday. We'll try a load test to reproduce it on 2.6.0 and then rerun on 2.6.1 to see what happens.
FYI, here are some profiler snapshots during peak load this past week with 2.6.0 in production:


@wvuong how's 2.6.1.SNAPSHOT look?
@wvuong We are holding the 2.6.1 release pending the results of your load testing. Any results that you can share?
Hi all, I've been working on it when I can. I can't do this with live bullets in production, of course. :) I spent some time yesterday trying to construct an artificial test to resurface the CPU issue. I'll spend some time today working on it too.
Did some testing. 2.6.1 is a bit better overall than 2.6.0 (didn't have time tonight to use 2.4.x as my baseline). I can't say definitively that it solved our problem without real production loads but it looks like a good step in that direction. Thanks all!
@wvuong I have deployed a new snapshot, available at the same download link as that above.
Here is a brief description of the change.
In v2.6.0 the code in ConcurrentBag.requite() looked like this (basically):
public void requite(final T bagEntry) {
bagEntry.setState(STATE_NOT_IN_USE);
while (waiters.get() > 0) {
if (handoffQueue.offer(bagEntry)) {
return;
}
yield();
}
...
First, the bagEntry is set as not-in-use. Then, as long as there are any threads waiting in borrow(), the requiting thread attempts a direct hand-off, and spins until it is successful.
Several observations on this approach and its problems:
handoffQueue.poll(). This is because borrowing threads first scan the pool for a not-in-use entry, before entering the poll(). This turns out to be far more efficient under load (on the borrow() side).In v2.6.1 the code was changed as follows:
public void requite(final T bagEntry) {
bagEntry.setState(STATE_NOT_IN_USE);
for (int i = 0; waiters.get() > 0; i++) {
if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
return;
}
else if ((i & 0x100) == 0x100) {
parkNanos(MICROSECONDS.toNanos(10));
}
else {
yield();
}
}
...
This code improves the efficiency in several ways:
handoffQueue.poll(), then the requiting thread will exit the loop because the state of the bagEntry transitions from not-in-use to in-use.yield() which is a hint to the scheduler (heeded or not), the parkNanos() call will unconditionally de-schedule the thread.I would expect this change to result in a fairly substantial decrease in CPU consumption when the pool is over-saturated.
@brettwooldridge good for release or still working on the "better" fix?
@johnou HikariCP v2.6.1 has been published.
@brettwooldridge thanks!
@brettwooldridge problems with the release?
@johnou Should be there now. Takes a while for Sonatype to process sometimes.
@brettwooldridge for Java 9 https://docs.oracle.com/javase/9/docs/api/java/lang/Thread.html#onSpinWait-- might make more sense?
It is certainly a possibility for Java 11, neither Java 9 nor Java 10 are LTS releases.
This issue is still presented, any fix for that?
Most helpful comment
This issue is still presented, any fix for that?