Vikas Kumar created HADOOP-19996:
------------------------------------

             Summary: With Java 17 KeepAlive caching not working when 
KMSClientProvider makes requests to KMS
                 Key: HADOOP-19996
                 URL: https://issues.apache.org/jira/browse/HADOOP-19996
             Project: Hadoop Common
          Issue Type: Bug
          Components: hadoop-common
            Reporter: Vikas Kumar
            Assignee: Vikas Kumar


Recently after upgrading from Java 8 to Java 17 on one existing cluster, we 
observed more than 70% drop in the throughput between client app using 
KMSClientProvider to make requests to Ranger-KMS.

Apart from throughput, average latency also increased. As soon as we rollback 
to Java 8, it was back to normal.

Further we found, Ranger-KMS with Java 17 and client using hadoop-common's 
KMSClientProvider  with Java 8 is working fine. If we switch to Java 17 on the 
client side, issue starts occuring.

*My observation and debugging:*

I suspected the new features of Java 17 like GC and default TLSv1.3 might be 
impacting the throughput. I tried with parallel GC as well, it didn't help.

Ranger-KMS and client app were explicitly configured to use TLSv1.2 only. Still 
issue persists.

Enabled tls debug log and can see very frequent *Connection reset* warning 
stack trace. I know that it may happen when server closes the connection due to 
idleTimeout. But frequency was high in Java 17 env in comparison to very few 
occurrences or zero occurrences in env with Java 8.

Apart from Connection reset, many "Broken pipe"  traces were there.

*Thread dump analysis on the Ranger-KMS side:*

 At the time when client app was getting Connection reset/broken pipe, many of 
the KMS worker threads were occupied in I/O op with underlying socket's stream. 
Load on KMS was not increasing, many of the threads were idle waiting for the 
new task.

I observed one difference in Java 17 & Java 8 Socket close stack trace on 
client app side:

*Following is with Java 8:*

 
{code:java}
at sun.net.www.http.HttpClient.closeServer(HttpClient.java:1075)
at sun.net.www.protocol.https.HttpsClient.closeServer(HttpsClient.java:428)
at sun.net.www.http.KeepAliveCache.put(KeepAliveCache.java:196)
at 
sun.net.www.protocol.https.HttpsClient.putInKeepAliveCache(HttpsClient.java:667)
at sun.net.www.http.HttpClient.finished(HttpClient.java:397)
at 
sun.net.www.http.ChunkedInputStream.closeUnderlying(ChunkedInputStream.java:219)
at sun.net.www.http.ChunkedInputStream.processRaw(ChunkedInputStream.java:455)
at 
sun.net.www.http.ChunkedInputStream.readAheadNonBlocking(ChunkedInputStream.java:520)
at sun.net.www.http.ChunkedInputStream.readAhead(ChunkedInputStream.java:611)
at sun.net.www.http.ChunkedInputStream.hurry(ChunkedInputStream.java:768)
at 
sun.net.www.http.ChunkedInputStream.closeUnderlying(ChunkedInputStream.java:221)
 {code}
*And following is with Java 17:*

 

 
{code:java}
at java.base/sun.net.www.http.HttpClient.closeServer(HttpClient.java:1155)
at 
java.base/sun.net.www.protocol.https.HttpsClient.closeServer(HttpsClient.java:445)
at 
java.base/sun.net.www.http.ChunkedInputStream.closeUnderlying(ChunkedInputStream.java:222)
at 
java.base/sun.net.www.http.ChunkedInputStream.close(ChunkedInputStream.java:769)
at java.base/java.io.FilterInputStream.close(FilterInputStream.java:179) {code}
Note the difference,

 

In Java 8 when client calls close() on underlying stream, it tries to drain any 
unread bytes left on the inputStream. And there it succeeds , stream is clean 
and retuned to the KeepAlive cache. You can observe 
*HttpsClient.putInKeepAliveCache*

Same is not happening in Java 17 cluster. Here also it tries to drain the 
unread bytes but fails. hence connection destroyed and connection caching is 
not happening.

This behaviour indicated towards possibility of connection leaks. 

Check the code 
[here|https://github.com/apache/hadoop/blob/997fa7c12a407c441855f1a409767f198db68cd3/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java#L585]
 , In KMSClientProvider.java, when it gets 401 it goes to KMS again with 
required tokens. And for this it creates a new Connection , assign it to 
existing "con" reference variable. It missed to close the older connection. In 
the same method, in finally block it calls 
IOUtils.closeStream(is) for new connection.
 
*To prove this finding,* If I simply drain the unread bytes (if exists) 
explicitly before calling close(), I start getting similar throughput that I 
was getting with Java 8, now I can't see any Connection reset or errors in the 
log.
 
For example: Average latency without patch was around 225 ms and after patch it 
reduced to around 60-65 ms.
 
PS: I read that in higher versions of Java, {{SLSocketImpl}} was completely 
rewritten around {{SSLEngine}} record boundaries. I suspect some behaviour 
difference.
 
*Proposed Fix:* Irrespective of any behaviour difference, it would be better to 
drain the stream before closing.  
 
Request community members to review this and suggest. If this finding is 
correct, I can raise the PR.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to