feat: add jitter to max lifetime in connection pool - #1496
Conversation
dedff31 to
3629f5d
Compare
|
@vietj Changes for the jitter have been added. I have some doubts regarding the test cases. One way to verify this is by creating a pool and fetching a connection. Then, using a timer, check every few milliseconds whether the connection is still active. Once it closes, record the time. Repeat this process three times and ensure that each recorded time is different. |
3629f5d to
2235e03
Compare
|
how can we test this ? |
Signed-off-by: priyanshu-d11 <priyanshu.vaya@dream11.com>
2235e03 to
61d0455
Compare
|
@priyanshu-d11 I think we could test with a TCP server that just does nothing and create a pool of 100 connections and perform a connect storm and measure the connection time between the client and server and ensure that we get a spread of the collected latencies ? we could add a min jitter time that could be helpful for testing, e.g. if we set 30 milliseconds then we can check that there are no latencies under 30 milliseconds |
…ertions Signed-off-by: priyanshu-d11 <priyanshu.vaya@dream11.com>
|
I have added a test case. Here's what the test does:
Let me know if you want me to explain any part in more detail or if you have any other idea to test this scenario. |
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.*; |
| private boolean shared = DEFAULT_SHARED_POOL; | ||
| private String name = DEFAULT_NAME; | ||
| private int eventLoopSize = DEFAULT_EVENT_LOOP_SIZE; | ||
| private int jitter = DEFAULT_JITTER; |
There was a problem hiding this comment.
we also need a jitterTimeUnit property which should be by default Willis
|
I am looking at the contribution, how does it work initially when the pool is empty and many connections attempts can be made simultaneously ? |
| long randomizedJitter = (maxLifetime > 0 && jitter > 0 && maxLifetime > jitter) ? ThreadLocalRandom.current().nextLong(-jitter, jitter + 1) : 0; | ||
| this.lifetimeEvictionTimestamp = maxLifetime > 0 ? System.currentTimeMillis() + maxLifetime + randomizedJitter : Long.MAX_VALUE; |
There was a problem hiding this comment.
| long randomizedJitter = (maxLifetime > 0 && jitter > 0 && maxLifetime > jitter) ? ThreadLocalRandom.current().nextLong(-jitter, jitter + 1) : 0; | |
| this.lifetimeEvictionTimestamp = maxLifetime > 0 ? System.currentTimeMillis() + maxLifetime + randomizedJitter : Long.MAX_VALUE; | |
| this.lifetimeEvictionTimestamp = maxLifetime > 0 ? System.currentTimeMillis() + maxLifetime + Math.max(0, ThreadLocalRandom.current().nextLong(0, jitter)) : Long.MAX_VALUE; |
There was a problem hiding this comment.
added this with nextLong(0, jitter+1)
|
@vietj Added a test case which does the following: Acquiring 50 connections in parallel.
|
|
@vietj While reviewing the test cases, I came across one for a |
|
@vietj Have you checked the test case? |
|
@vietj Have you got time to review the test case added? |
|
@vietj A polite bump up for the review. Please let us know if the above implementation looks good. Thank you for your time. |
| this.conn = conn; | ||
| this.listener = listener; | ||
| this.lifetimeEvictionTimestamp = maxLifetime > 0 ? System.currentTimeMillis() + maxLifetime : Long.MAX_VALUE; | ||
| this.lifetimeEvictionTimestamp = maxLifetime > 0 ? System.currentTimeMillis() + maxLifetime + Math.max(0, ThreadLocalRandom.current().nextLong(0, jitter + 1)) : Long.MAX_VALUE; |
There was a problem hiding this comment.
@priyanshu-d11 Do you think this value should be subtracted?
Since, the total eviction time should never exceed maxLifeTime (users expectation)
If yes, need to update corresponding test cases.
|
Not trying to add noise to a review that is already long, but @vietj asked something on 2025-03-20 that I do not think got answered, and it looks load-bearing for whether this closes #1495:
I think the honest answer is that it does not, and that this is fine — because there are two distinct storms in the original report and this PR addresses one of them. Recycle storm. Every connection in a pool is created at roughly the same moment and shares one Initial-connect storm. At startup the pool is empty and N acquisitions arrive together, so N connects are attempted together. Worth separating them explicitly, because they need different mechanisms and have different severity. The initial storm is bounded and self-resolving: it happens once per process start, and DNS-based reader selection re-balances as connections are recycled thereafter — which is precisely what the jitter in this PR enables. The recycle storm is unbounded: without jitter, the skew re-establishes itself on every generation, so a pool that started unlucky stays unlucky forever. That ordering seems like an argument for merging this rather than widening it. If the initial-connect case still matters afterwards, the mechanism for it is different anyway — a small spread on the acquisition path rather than on expiry — and would be much easier to reason about once lifetimes are no longer synchronised. On the test difficulty in this thread: measuring elapsed time between connection changes is inherently flaky under CI load. An alternative that avoids wall-clock assertions is to compute the scheduled expiry deltas directly and assert that the set of values is spread rather than identical — no timing, no sleeping, and it fails for the right reason if jitter regresses. Whether that is reachable depends on how the expiry is stored, which I have not checked. The PR also currently shows as conflicting, so it likely needs a rebase before it can be looked at either way. |
Connection Storm Prevention with Jitter in Connection Pool #1495
Introduce connection jitter to randomize connection timing:
Fix: #1495