Welcome! Log In Create A New Profile

Advanced

[PATCH] HTTP/2 autotuning request body buffer

Junho Choi
August 26, 2020 08:26PM
# HG changeset patch
# User Junho Choi <junho@cloudflare.com>
# Date 1598482301 25200
# Wed Aug 26 15:51:41 2020 -0700
# Node ID 1739da077a8e27425867d4804ffc39f8b9bd59ac
# Parent 7015f26aef904e2ec17b4b6f6387fd3b8298f79d
HTTP/2 autotuning request body buffer

Currently when unbuffered proxy of request buffer is set
(proxy_request_buffering=off), H2 receiver window size is
set to client_body_buffer_size and http2_body_preread_size
(which is greater). This can result in a suboptimal performance
when a large request body is uploaded and the connection's
BDP (bandwidth x delay) is much larger than current request
body buffer.

This patch is try to resize request body buffer based on the
measured BDP, allowing to have bigger H2 receiver window for
faster upload.

Our result showed it's effective when the connection
has a high BDP much larger than client_body_buffer_size.
With a small BDP close to client_body_buffer_size,
the improvement may be smaller, but still not worse than
current fixed buffer size. When there is a large gap between
current BDP and client_body_buffer_size, it can archive
2x or higher upload speed. This is effective when an end user's
upload link is very fast.

To enable, you need to add --with-http_v2_module (requirement) and
--with-http_v2_autotune_upload to configure script when building.

./configure ... \
--with-http_v2_module --with-http_v2_autotune_upload ...

To use this feature, add http2_max_client_body_buffer_size directive
in nginx.conf (same level of client_body_buffer_size). By default
this is 0 (disabled and no optimizing). This value is still capped
to a hard coded default of NGX_HTTP_V2_MAX_CLIENT_BODY_BUFFER_SIZE
(64MB). For example this value can be set to 8m, to allow request
buffer growing up to 8MB but no more, to prevent from overusing
memory in a very high BDP.

Example nginx.conf:
http2_body_preread_size 64k
client_body_buffer_size 128k
http2_max_client_body_buffer_size 8m

This feature requires TCP_INFO.

For more information, please see:
https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/

diff -r 7015f26aef90 -r 1739da077a8e auto/modules
--- a/auto/modules Wed Jul 29 13:28:04 2020 +0300
+++ b/auto/modules Wed Aug 26 15:51:41 2020 -0700
@@ -420,6 +420,15 @@
ngx_module_libs=
ngx_module_link=$HTTP_V2

+ if [ $HTTP_V2 = YES -a $HTTP_V2_AUTOTUNE_UPLOAD = YES ]; then
+ have=NGX_HTTP_V2_AUTOTUNE_UPLOAD . auto/have
+
+ ngx_module_deps="$ngx_module_deps \
+ src/http/v2/ngx_autotune_upload.h"
+ ngx_module_srcs="$ngx_module_srcs \
+ src/http/v2/ngx_autotune_upload.c"
+ fi
+
. auto/module
fi

diff -r 7015f26aef90 -r 1739da077a8e auto/options
--- a/auto/options Wed Jul 29 13:28:04 2020 +0300
+++ b/auto/options Wed Aug 26 15:51:41 2020 -0700
@@ -59,6 +59,7 @@
HTTP_GZIP=YES
HTTP_SSL=NO
HTTP_V2=NO
+HTTP_V2_AUTOTUNE_UPLOAD=NO
HTTP_SSI=YES
HTTP_REALIP=NO
HTTP_XSLT=NO
@@ -224,6 +225,7 @@

--with-http_ssl_module) HTTP_SSL=YES ;;
--with-http_v2_module) HTTP_V2=YES ;;
+ --with-http_v2_autotune_upload) HTTP_V2_AUTOTUNE_UPLOAD=YES;;
--with-http_realip_module) HTTP_REALIP=YES ;;
--with-http_addition_module) HTTP_ADDITION=YES ;;
--with-http_xslt_module) HTTP_XSLT=YES ;;
diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_autotune_upload.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/v2/ngx_autotune_upload.c Wed Aug 26 15:51:41 2020 -0700
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 Cloudflare, Inc.
+ */
+
+#include <ngx_http.h>
+#include <ngx_http_v2_module.h>
+#include <ngx_autotune_upload.h>
+
+static void *ngx_prealloc(ngx_pool_t *pool, void *p, size_t size);
+static void *ngx_realloc(void *oldp, size_t size, ngx_log_t *log);
+
+static ngx_int_t ngx_resize_buf(ngx_pool_t *pool, ngx_buf_t *buf, size_t nsize);
+
+
+static void *
+ngx_prealloc(ngx_pool_t *pool, void *p, size_t size)
+{
+ ngx_pool_large_t *l;
+ void *newp;
+
+ for (l = pool->large; l; l = l->next) {
+ if (p == l->alloc) {
+ ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
+ "prealloc: %p", l->alloc);
+
+ newp = ngx_realloc(l->alloc, size, pool->log);
+ if (newp) {
+ l->alloc = newp;
+
+ return newp;
+ } else {
+ return NULL;
+ }
+ }
+ }
+
+ /* not found */
+ return NULL;
+}
+
+
+static void *
+ngx_realloc(void *oldp, size_t size, ngx_log_t *log)
+{
+ void *newp;
+
+ newp = realloc(oldp, size);
+ if (newp == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ "realloc(%uz) failed", size);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "realloc: %p:%uz", newp, size);
+
+ return newp;
+}
+
+
+/* resize the buffer to the new size */
+static ngx_int_t
+ngx_resize_buf(ngx_pool_t *pool, ngx_buf_t *buf, size_t nsize)
+{
+ void *nbuf = ngx_prealloc(pool, buf->start, nsize);
+
+ if (!nbuf) {
+ return NGX_ERROR;
+ }
+
+ /* if buf->start is moved to a new location */
+ if (nbuf != buf->start) {
+ buf->pos = (u_char *)nbuf + (buf->pos - buf->start);
+ buf->last = (u_char *)nbuf + (buf->last - buf->start);
+ }
+
+ /* resize buffer */
+ buf->start = nbuf;
+ buf->end = (u_char *)nbuf + nsize;
+
+ return NGX_OK;
+}
+
+
+/* get current TCP RTT (ms) of the connection */
+ngx_int_t
+ngx_tcp_rtt_ms(int fd)
+{
+#if (NGX_HAVE_TCP_INFO)
+ struct tcp_info ti;
+ socklen_t len;
+
+ len = sizeof(struct tcp_info);
+ if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &len) == 0) {
+ return ti.tcpi_rtt / 1000;
+ }
+#endif
+
+ return NGX_ERROR;
+}
+
+
+/* return current timestamp (ms) */
+ngx_msec_int_t
+ngx_timestamp_ms()
+{
+ ngx_time_t *tp = ngx_timeofday();
+
+ return tp->sec * 1000 + tp->msec;
+}
+
+
+/*
+ * double the buffer size based on the current BDP.
+ * returns the new window size if resized.
+ * returns the current window size if not resized.
+ * if resizing fails, returns 0.
+ */
+size_t
+ngx_autotune_client_body_buffer(ngx_http_request_t *r,
+ size_t window)
+{
+ ngx_buf_t *buf;
+ ngx_http_v2_stream_t *stream;
+ ngx_msec_int_t ts_now;
+ ngx_http_v2_loc_conf_t *h2lcf;
+ size_t max_window;
+
+ h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
+ max_window = h2lcf->max_client_body_buffer_size;
+
+ /* no autotuning configured */
+ if (!max_window) {
+ return window;
+ }
+
+ /* if max_window is smaller than the current window, do nothing */
+ if (window >= max_window) {
+ return window;
+ }
+
+ stream = r->stream;
+ buf = r->request_body->buf;
+
+ /* if rtt is not available, do nothing */
+ if (stream->rtt == NGX_ERROR) {
+ return window;
+ }
+
+ ts_now = ngx_timestamp_ms();
+
+ if (ts_now >= (stream->ts_checkpoint + stream->rtt)) {
+ size_t cur_win = (buf->end - buf->start);
+ size_t new_win = ngx_min(cur_win * 2 , max_window);
+
+ /* if already on the max size, do nothing */
+ if (cur_win >= max_window) {
+ return window;
+ }
+
+ /* min rtt is 1ms to prevent BDP from becoming zero. */
+ ngx_uint_t rtt = ngx_max(stream->rtt, 1);
+
+ /*
+ * elapsed time (ms) from last checkpoint. mininum value is 1 to
+ * prevent from dividing by zero in BDP calculation
+ */
+ ngx_uint_t elapsed = ngx_max(ts_now - stream->ts_checkpoint, 1);
+
+ /* calculate BDP (bytes) = rtt * bw */
+ ngx_uint_t bdp = rtt * stream->bytes_body_read / elapsed;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->connection->connection->log, 0,
+ "http2 autotune sid:%ui rtt:%z bdp:%z win:%z",
+ stream->node->id, stream->rtt, bdp, window);
+
+ stream->bytes_body_read = 0;
+ stream->ts_checkpoint = ts_now;
+
+ /*
+ * check if we need to bump the buffer size
+ * based on the heuristic condition
+ */
+ if (bdp > (window / 4)) {
+ if (ngx_resize_buf(r->pool, buf, new_win) != NGX_OK) {
+ return 0;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP,
+ stream->connection->connection->log, 0,
+ "http2 autotune sid:%ui rtt:%z resized:%z->%z",
+ stream->node->id, stream->rtt, window,
+ window + (new_win - cur_win));
+
+ return window + (new_win - cur_win);
+ }
+ }
+
+ return window;
+}
diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_autotune_upload.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/v2/ngx_autotune_upload.h Wed Aug 26 15:51:41 2020 -0700
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 Cloudflare, Inc.
+ */
+
+#ifndef _NGX_AUTOTUNE_UPLOAD_H_INCLUDED_
+#define _NGX_AUTOTUNE_UPLOAD_H_INCLUDED_
+
+#include <ngx_core.h>
+
+
+/* the maximum size of the receiver window */
+#define NGX_HTTP_V2_MAX_CLIENT_BODY_BUFFER_SIZE (64*1024*1024)
+
+
+/* get current TCP RTT (ms) of the connection */
+ngx_int_t ngx_tcp_rtt_ms(int fd);
+
+/* return current timestamp (ms) */
+ngx_msec_int_t ngx_timestamp_ms();
+
+/* auto resize the buffer */
+size_t ngx_autotune_client_body_buffer(ngx_http_request_t *r, size_t window);
+
+
+#endif
diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c Wed Jul 29 13:28:04 2020 +0300
+++ b/src/http/v2/ngx_http_v2.c Wed Aug 26 15:51:41 2020 -0700
@@ -11,6 +11,10 @@
#include <ngx_http_v2_module.h>


+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+#include <ngx_autotune_upload.h>
+#endif
+
typedef struct {
ngx_str_t name;
ngx_uint_t offset;
@@ -1122,6 +1126,10 @@
pos += size;
h2c->state.length -= size;

+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+ stream->bytes_body_read += size;
+#endif
+
if (h2c->state.length) {
return ngx_http_v2_state_save(h2c, pos, end,
ngx_http_v2_state_read_data);
@@ -3211,6 +3219,12 @@

h2c->priority_limit += h2scf->concurrent_streams;

+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+ stream->bytes_body_read = 0;
+ stream->rtt = ngx_tcp_rtt_ms(r->connection->fd);
+ stream->ts_checkpoint = ngx_timestamp_ms();
+#endif
+
return stream;
}

@@ -4323,6 +4337,15 @@
return NGX_AGAIN;
}

+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+ window = ngx_autotune_client_body_buffer(r, window);
+
+ /* resizing failed */
+ if (!window) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+#endif
+
if (ngx_http_v2_send_window_update(h2c, stream->node->id,
window - stream->recv_window)
== NGX_ERROR)
diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h Wed Jul 29 13:28:04 2020 +0300
+++ b/src/http/v2/ngx_http_v2.h Wed Aug 26 15:51:41 2020 -0700
@@ -210,6 +210,15 @@

ngx_pool_t *pool;

+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+ /* how much client request body read */
+ ngx_uint_t bytes_body_read;
+ /* timestamp of next checkpoint */
+ ngx_msec_int_t ts_checkpoint;
+ /* rtt(ms) of the connection */
+ ngx_int_t rtt;
+#endif
+
unsigned waiting:1;
unsigned blocked:1;
unsigned exhausted:1;
diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_http_v2_module.c
--- a/src/http/v2/ngx_http_v2_module.c Wed Jul 29 13:28:04 2020 +0300
+++ b/src/http/v2/ngx_http_v2_module.c Wed Aug 26 15:51:41 2020 -0700
@@ -10,6 +10,9 @@
#include <ngx_http.h>
#include <ngx_http_v2_module.h>

+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+#include <ngx_autotune_upload.h>
+#endif

static ngx_int_t ngx_http_v2_add_variables(ngx_conf_t *cf);

@@ -38,6 +41,10 @@
static char *ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data);
static char *ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+static char *ngx_http_v2_max_client_body_buffer_size(ngx_conf_t *cf, void *post,
+ void *data);
+#endif


static ngx_conf_post_t ngx_http_v2_recv_buffer_size_post =
@@ -50,6 +57,10 @@
{ ngx_http_v2_streams_index_mask };
static ngx_conf_post_t ngx_http_v2_chunk_size_post =
{ ngx_http_v2_chunk_size };
+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+static ngx_conf_post_t ngx_http_v2_max_client_body_buffer_size_post =
+ { ngx_http_v2_max_client_body_buffer_size };
+#endif


static ngx_command_t ngx_http_v2_commands[] = {
@@ -208,6 +219,15 @@
0,
NULL },

+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+ { ngx_string("http2_max_client_body_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_v2_loc_conf_t, max_client_body_buffer_size),
+ &ngx_http_v2_max_client_body_buffer_size_post },
+#endif
+
ngx_null_command
};

@@ -423,6 +443,10 @@
h2lcf->push_preload = NGX_CONF_UNSET;
h2lcf->push = NGX_CONF_UNSET;

+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+ h2lcf->max_client_body_buffer_size = NGX_CONF_UNSET_SIZE;
+#endif
+
return h2lcf;
}

@@ -443,6 +467,12 @@

ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0);

+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+ /* default is 0: no auto tuning */
+ ngx_conf_merge_size_value(conf->max_client_body_buffer_size,
+ prev->max_client_body_buffer_size, 0);
+#endif
+
return NGX_CONF_OK;
}

@@ -608,3 +638,19 @@

return NGX_CONF_OK;
}
+
+
+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+static char *
+ngx_http_v2_max_client_body_buffer_size(ngx_conf_t *cf, void *post,
+ void *data)
+{
+ size_t *sp = data;
+
+ if (*sp > NGX_HTTP_V2_MAX_CLIENT_BODY_BUFFER_SIZE) {
+ *sp = NGX_HTTP_V2_MAX_CLIENT_BODY_BUFFER_SIZE;
+ }
+
+ return NGX_CONF_OK;
+}
+#endif
diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_http_v2_module.h
--- a/src/http/v2/ngx_http_v2_module.h Wed Jul 29 13:28:04 2020 +0300
+++ b/src/http/v2/ngx_http_v2_module.h Wed Aug 26 15:51:41 2020 -0700
@@ -41,6 +41,10 @@

ngx_flag_t push;
ngx_array_t *pushes;
+
+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
+ size_t max_client_body_buffer_size;
+#endif
} ngx_http_v2_loc_conf_t;



_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
http://mailman.nginx.org/mailman/listinfo/nginx-devel
Subject Author Views Posted

[PATCH] HTTP/2 autotuning request body buffer

Junho Choi 104 August 26, 2020 08:26PM



Sorry, you do not have permission to post/reply in this forum.

Online Users

Guests: 64
Record Number of Users: 6 on February 13, 2018
Record Number of Guests: 421 on December 02, 2018
Powered by nginx      Powered by FreeBSD      PHP Powered      Powered by MariaDB      ipv6 ready