java-topology/defects/nginx/patch/nginx-0004-upstream-keepalive-cache-linear-scan.md
russell@unturf.com 25c2bafdee undf: assign 694-720; stamp patches; ruby-0003/elixir-0002/r-source-0002/victoria-metrics-0002
New UNDF assignments (693→720):
  elixir-0002 → UNDF-2026-000000698 (typespec used_type_pairs O(T²))
  r-source-0002 → UNDF-2026-000000711 (.walkClassGraph match dedup O(S²))
  ruby-0003 → UNDF-2026-000000712 (RubyGems dependent_gems O(N²×D))
  victoria-metrics-0002 → UNDF-2026-000000717 (MetricName tag-filter O(T×I))

Total: 720 UNDF assigned
2026-03-29 22:28:31 -04:00

94 lines
3.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UNDF: UNDF-2026-000000707
# nginx-0004 — ngx_http_upstream_keepalive: O(C) linear cache scan per upstream request
## Ecosystem
nginx (C)
## Severity
MEDIUM — hot path: executed on every upstream request that can reuse a
keepalive connection; scales with `keepalive` directive value
## Location
`src/http/modules/ngx_http_upstream_keepalive_module.c`
- Function: `ngx_http_upstream_keepalive_get_peer` (~line 212)
- Inner loop: `for (q = ngx_queue_head(cache); q != ngx_queue_sentinel(cache); ...)` (~line 229)
## Description
When an upstream request needs a connection, `ngx_http_upstream_keepalive_get_peer`
scans the entire keepalive cache queue to find a cached connection matching
the upstream's `sockaddr`:
```c
/* search cache for suitable connection */
cache = &kp->conf->cache;
for (q = ngx_queue_head(cache);
q != ngx_queue_sentinel(cache);
q = ngx_queue_next(q)) // O(C) scan
{
item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
c = item->connection;
if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr,
item->socklen, pc->socklen) == 0)
{
ngx_queue_remove(q);
goto found;
}
}
```
With `keepalive N` set to a large value (e.g., `keepalive 1000` or `keepalive 10000`
for backends with many servers), this scan processes up to N entries on every upstream
request. Under high throughput, this O(C) scan becomes a bottleneck:
- 10,000 RPS × C=1000 keepalive cache entries = 10 million comparisons/second
just for connection reuse lookups
## Fix
Index the keepalive cache by sockaddr using a hash table keyed on the
(family, addr, port) tuple, giving O(1) lookup per upstream request:
```c
--- a/src/http/modules/ngx_http_upstream_keepalive_module.c
+++ b/src/http/modules/ngx_http_upstream_keepalive_module.c
@@ struct ngx_http_upstream_keepalive_srv_conf_s {
ngx_queue_t cache; /* LRU queue of cached connections */
ngx_queue_t free; /* free items */
+ ngx_hash_t cache_hash; /* sockaddr → cached items list */
- /* search cache for suitable connection */
- for (q = ngx_queue_head(cache);
- q != ngx_queue_sentinel(cache);
- q = ngx_queue_next(q))
- {
- item = ngx_queue_data(q, ...);
- if (ngx_memn2cmp(&item->sockaddr, pc->sockaddr, ...) == 0)
- goto found;
- }
+ /* O(1) hash lookup by sockaddr */
+ ngx_http_upstream_keepalive_cache_t *item =
+ ngx_hash_find(&kp->conf->cache_hash,
+ ngx_crc32_long(pc->sockaddr->sa_data, pc->socklen),
+ pc->sockaddr, pc->socklen);
+ if (item) goto found;
```
## Complexity
| Variant | Cost per upstream connection attempt |
|---------|-------------------------------------|
| Before | O(C) — full scan of keepalive cache |
| After | O(1) — hash map lookup |
| Speedup | C× (keepalive pool size) |
## Notes
- Default `keepalive` is typically 32-100 for simple setups; some deployments
use 1000+ for microservice backends with many upstream servers
- nginx's existing LRU queue serves eviction; the hash provides fast lookup
- Multiple cached connections to the same server are valid; the hash can
return a head-of-list and then remove from the LRU queue
- `ngx_queue_insert_head(&kp->conf->free, q)` after successful lookup
must also update the hash