- tune.sndbuf.client
- tune.sndbuf.frontend
- tune.sndbuf.server
+ - tune.streams-elasticity
- tune.stick-counters
- tune.ssl.cachesize
- tune.ssl.capture-buffer-size
dynamically is expensive, they are cached. The default cache size is set to
1000 entries.
+tune.streams-elasticity <number>
+ Defines a target percentage of streams per frontend connection relative to
+ the maximum number of concurrent connections (maxconn) when all connections
+ are established. This metric applies to multiplexed protocols like HTTP/2 or
+ QUIC, where each connection may receive multiple streams. At least one is
+ always guaranteed, so the percentage must be at least 100%. During connection
+ setup, HAProxy dynamically advertises additional streams up to the configured
+ limit, maintaining the target ratio. At connection establishment, every
+ frontend connection receives at least one stream; extra streams are assigned
+ based on the target percentage and configured stream limits. This ensures
+ efficient stream allocation under varying load conditions (more streams at
+ low loads, fewer at high loads).
+
+ Highly dynamic sites with many objects per page benefit from high ratios,
+ enabling many streams per connection. Sites using fewer streams on average
+ (WebSocket, application code) may prefer small ratios closer to 120 or 150
+ (20 to 50% more streams than connections) preventing excessive stream counts
+ under sustained loads.
+
+ The default value is 0, meaning no enforcement at this level, so only H2 and
+ QUIC configurations apply (with the default setting of 100 streams per
+ connection, this corresponds to 10000%). This remains the recommended setting
+ for small deployments (maxconn around a thousand). Moderately sized setups
+ (few thousands to tens of thousands connections) typically set the ratio
+ between 1000 and 5000, allowing 10 to 50 streams per connection at full load.
+ Large-scale deployments (hundreds of thousands to millions connections) might
+ use lower values (120 to 200) to support 1.2 to 2 streams per connection on
+ average at full load.
+
+ Monitoring the total number of active streams on backends, including queues,
+ provides a practical indicator of a sustainable target load and helps avoid
+ over-provisioning.
+
tune.stick-counters <number>
Sets the number of stick-counters that may be tracked at the same time by a
connection or a request via "track-sc*" actions in "tcp-request" or
return ret;
}
+/* Calculates the approximate number of streams permitted for an already
+ * established frontend connection based on the number of active connections
+ * (including this one), the number of already committed streams in the current
+ * thread group, the limit, and the desired limit (a ratio of which will be
+ * applied as the budget permits). May return 0 for no limit. The minimum value
+ * when a limit is set will be 1 as a minimum.
+ */
+static inline uint conn_calc_max_streams(uint desired)
+{
+ uint per_conn_left;
+ uint avg_per_conn;
+ uint conn_curr;
+ int conn_left;
+ uint extra;
+ uint curr;
+
+ /* check for infinite */
+ if (!global.tune.streams_elasticity)
+ return 0;
+
+ /* check for none (0% overcommit) */
+ if (global.tune.streams_elasticity == 100)
+ return 1;
+
+ if (desired <= 1)
+ return 1;
+
+ conn_curr = _HA_ATOMIC_LOAD(&actconn) - 1;
+ conn_left = global.hardmaxconn - conn_curr;
+ if (conn_left <= 0)
+ return 1;
+
+ /* the limit is per process, we're working per group. Since we're
+ * counting extra streams max, we subtract 100% from elasticity.
+ */
+ extra = (((ullong)global.hardmaxconn * (global.tune.streams_elasticity - 100) / 100));
+ curr = _HA_ATOMIC_LOAD(&tg_ctx->committed_extra_streams) * global.nbtgroups;
+ if (curr >= extra)
+ return 1;
+
+ /* this is the average per conn left that we can allocate */
+ per_conn_left = ((extra - curr) + conn_left - 1) / conn_left;
+
+ /* OK so we know we can still allocate (extra - curr) streams per
+ * tgroup, that will be shared across conn_left connections, but ought
+ * to be fairly shared between all conn_curr ones. This allows to
+ * provide at least up to <desired> as long as we leave enough for all
+ * remaining connections left.
+ */
+ avg_per_conn = ((ullong)(extra - curr) * (desired - 1)) / extra;
+
+ /* both values are permitted since they respect the global limit,
+ * so let's deliver the best option to better serve first conns
+ * so that the limit degrades smoothly with the number of conns.
+ */
+ return 1 + MAX(per_conn_left, avg_per_conn);
+}
+
/* Retrieves any valid stream connector from this connection, preferably the first
* valid one. The purpose is to be able to figure one other end of a private
* connection for purposes like source binding or proxy protocol header
uint max_checks_per_thread; /* if >0, no more than this concurrent checks per thread */
uint ring_queues; /* if >0, #ring queues, otherwise equals #thread groups */
uint cli_max_payload_sz; /* The max payload size for the CLI */
+ int streams_elasticity; /* percent of advertised streams to connection; 0=no limit */
enum threadgroup_takeover tg_takeover; /* Policy for threadgroup takeover */
} tune;
struct {
return -1;
}
}
+ else if (strcmp(args[0], "tune.streams-elasticity") == 0) {
+ char *stop;
+
+ global.tune.streams_elasticity = strtol(args[1], &stop, 10);
+ if (!*args[1] || *stop ||
+ (global.tune.streams_elasticity && global.tune.streams_elasticity < 100)) {
+ memprintf(err, "'%s' expects 0 or a positive percentage value of 100 or above", args[0]);
+ return -1;
+ }
+ }
else if (strcmp(args[0], "tune.takeover-other-tg-connections") == 0) {
if (*(args[1]) == 0) {
memprintf(err, "'%s' expects 'none', 'restricted', or 'full'", args[0]);
{ CFG_GLOBAL, "tune.runqueue-depth", cfg_parse_global_tune_opts },
{ CFG_GLOBAL, "tune.sndbuf.client", cfg_parse_global_tune_opts },
{ CFG_GLOBAL, "tune.sndbuf.server", cfg_parse_global_tune_opts },
+ { CFG_GLOBAL, "tune.streams-elasticity", cfg_parse_global_tune_opts },
{ CFG_GLOBAL, "tune.takeover-other-tg-connections", cfg_parse_global_tune_opts },
{ CFG_GLOBAL, "unsetenv", cfg_parse_global_env_opts, KWF_DISCOVERY },
{ CFG_GLOBAL, "zero-warning", cfg_parse_global_mode, KWF_DISCOVERY },