Improvements for TLS with I/O threads (#1271)

Main thread profiling revealed significant overhead in TLS operations,
even with read/write offloaded to I/O threads:

Perf results:

**10.82%** 8.82% `valkey-server libssl.so.3 [.] SSL_pending` # Called by
main thread after I/O completion

**10.16%** 5.06% `valkey-server libcrypto.so.3 [.] ERR_clear_error` #
Called for every event regardless of thread handling

This commit further optimizes TLS operations by moving more work from
the main thread to I/O threads:

Improve TLS offloading to I/O threads with two main changes:

1. Move `ERR_clear_error()` calls closer to SSL operations
   - Currently, error queue is cleared for every TLS event
   - Now only clear before actual SSL function calls
   - This prevents unnecessary clearing in main thread when operations
     are handled by I/O threads

2. Optimize `SSL_pending()` checks
   - Add `TLS_CONN_FLAG_HAS_PENDING` flag to track pending data
   - Move pending check to follow read operations immediately
   - I/O thread sets flag when pending data exists
   - Main thread uses flag to update pending list

Performance improvements:
Testing setup based on
https://valkey.io/blog/unlock-one-million-rps-part2/

Before:
- SET: 896,047 ops/sec
- GET: 875,794 ops/sec

After:
- SET: 985,784 ops/sec (+10% improvement)
- GET: 1,066,171 ops/sec (+22% improvement)

Signed-off-by: Uri Yagelnik <uriy@amazon.com>
This commit is contained in:
uriyage 2024-11-18 07:52:35 +02:00 committed by GitHub
parent aa2dd3ecb8
commit 94113fde7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -446,6 +446,7 @@ typedef enum {
#define TLS_CONN_FLAG_WRITE_WANT_READ (1 << 1) #define TLS_CONN_FLAG_WRITE_WANT_READ (1 << 1)
#define TLS_CONN_FLAG_FD_SET (1 << 2) #define TLS_CONN_FLAG_FD_SET (1 << 2)
#define TLS_CONN_FLAG_POSTPONE_UPDATE_STATE (1 << 3) #define TLS_CONN_FLAG_POSTPONE_UPDATE_STATE (1 << 3)
#define TLS_CONN_FLAG_HAS_PENDING (1 << 4)
typedef struct tls_connection { typedef struct tls_connection {
connection c; connection c;
@ -614,7 +615,7 @@ static void updatePendingData(tls_connection *conn) {
/* If SSL has pending data, already read from the socket, we're at risk of not calling the read handler again, make /* If SSL has pending data, already read from the socket, we're at risk of not calling the read handler again, make
* sure to add it to a list of pending connection that should be handled anyway. */ * sure to add it to a list of pending connection that should be handled anyway. */
if (SSL_pending(conn->ssl) > 0) { if (conn->flags & TLS_CONN_FLAG_HAS_PENDING) {
if (!conn->pending_list_node) { if (!conn->pending_list_node) {
listAddNodeTail(pending_list, conn); listAddNodeTail(pending_list, conn);
conn->pending_list_node = listLast(pending_list); conn->pending_list_node = listLast(pending_list);
@ -625,6 +626,14 @@ static void updatePendingData(tls_connection *conn) {
} }
} }
void updateSSLPendingFlag(tls_connection *conn) {
if (SSL_pending(conn->ssl) > 0) {
conn->flags |= TLS_CONN_FLAG_HAS_PENDING;
} else {
conn->flags &= ~TLS_CONN_FLAG_HAS_PENDING;
}
}
static void updateSSLEvent(tls_connection *conn) { static void updateSSLEvent(tls_connection *conn) {
if (conn->flags & TLS_CONN_FLAG_POSTPONE_UPDATE_STATE) return; if (conn->flags & TLS_CONN_FLAG_POSTPONE_UPDATE_STATE) return;
@ -653,8 +662,6 @@ static void tlsHandleEvent(tls_connection *conn, int mask) {
TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d", fd, conn->c.state, mask, TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d", fd, conn->c.state, mask,
conn->c.read_handler != NULL, conn->c.write_handler != NULL, conn->flags); conn->c.read_handler != NULL, conn->c.write_handler != NULL, conn->flags);
ERR_clear_error();
switch (conn->c.state) { switch (conn->c.state) {
case CONN_STATE_CONNECTING: case CONN_STATE_CONNECTING:
conn_error = anetGetError(conn->c.fd); conn_error = anetGetError(conn->c.fd);
@ -662,6 +669,7 @@ static void tlsHandleEvent(tls_connection *conn, int mask) {
conn->c.last_errno = conn_error; conn->c.last_errno = conn_error;
conn->c.state = CONN_STATE_ERROR; conn->c.state = CONN_STATE_ERROR;
} else { } else {
ERR_clear_error();
if (!(conn->flags & TLS_CONN_FLAG_FD_SET)) { if (!(conn->flags & TLS_CONN_FLAG_FD_SET)) {
SSL_set_fd(conn->ssl, conn->c.fd); SSL_set_fd(conn->ssl, conn->c.fd);
conn->flags |= TLS_CONN_FLAG_FD_SET; conn->flags |= TLS_CONN_FLAG_FD_SET;
@ -690,6 +698,7 @@ static void tlsHandleEvent(tls_connection *conn, int mask) {
conn->c.conn_handler = NULL; conn->c.conn_handler = NULL;
break; break;
case CONN_STATE_ACCEPTING: case CONN_STATE_ACCEPTING:
ERR_clear_error();
ret = SSL_accept(conn->ssl); ret = SSL_accept(conn->ssl);
if (ret <= 0) { if (ret <= 0) {
WantIOType want = 0; WantIOType want = 0;
@ -747,10 +756,7 @@ static void tlsHandleEvent(tls_connection *conn, int mask) {
conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE; conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
if (!callHandler((connection *)conn, conn->c.read_handler)) return; if (!callHandler((connection *)conn, conn->c.read_handler)) return;
} }
updatePendingData(conn);
if (mask & AE_READABLE) {
updatePendingData(conn);
}
break; break;
} }
@ -941,6 +947,7 @@ static int connTLSRead(connection *conn_, void *buf, size_t buf_len) {
if (conn->c.state != CONN_STATE_CONNECTED) return -1; if (conn->c.state != CONN_STATE_CONNECTED) return -1;
ERR_clear_error(); ERR_clear_error();
ret = SSL_read(conn->ssl, buf, buf_len); ret = SSL_read(conn->ssl, buf, buf_len);
updateSSLPendingFlag(conn);
return updateStateAfterSSLIO(conn, ret, 1); return updateStateAfterSSLIO(conn, ret, 1);
} }
@ -992,7 +999,7 @@ static int connTLSBlockingConnect(connection *conn_, const char *addr, int port,
* which means the specified timeout will not be enforced accurately. */ * which means the specified timeout will not be enforced accurately. */
SSL_set_fd(conn->ssl, conn->c.fd); SSL_set_fd(conn->ssl, conn->c.fd);
setBlockingTimeout(conn, timeout); setBlockingTimeout(conn, timeout);
ERR_clear_error();
if ((ret = SSL_connect(conn->ssl)) <= 0) { if ((ret = SSL_connect(conn->ssl)) <= 0) {
conn->c.state = CONN_STATE_ERROR; conn->c.state = CONN_STATE_ERROR;
return C_ERR; return C_ERR;
@ -1023,6 +1030,7 @@ static ssize_t connTLSSyncRead(connection *conn_, char *ptr, ssize_t size, long
setBlockingTimeout(conn, timeout); setBlockingTimeout(conn, timeout);
ERR_clear_error(); ERR_clear_error();
int ret = SSL_read(conn->ssl, ptr, size); int ret = SSL_read(conn->ssl, ptr, size);
updateSSLPendingFlag(conn);
ret = updateStateAfterSSLIO(conn, ret, 0); ret = updateStateAfterSSLIO(conn, ret, 0);
unsetBlockingTimeout(conn); unsetBlockingTimeout(conn);
@ -1041,6 +1049,7 @@ static ssize_t connTLSSyncReadLine(connection *conn_, char *ptr, ssize_t size, l
ERR_clear_error(); ERR_clear_error();
int ret = SSL_read(conn->ssl, &c, 1); int ret = SSL_read(conn->ssl, &c, 1);
updateSSLPendingFlag(conn);
ret = updateStateAfterSSLIO(conn, ret, 0); ret = updateStateAfterSSLIO(conn, ret, 0);
if (ret <= 0) { if (ret <= 0) {
nread = -1; nread = -1;