plugin/forward: Increase minimum read timeout to 200ms (#1889)

After several experiments at SoundCloud we found that the current
minimum read timeout of 10ms is too low. A single request against a
slow/unavailable authoritative server can cause all TCP connections to
get closed. We record a 50th percentile forward/proxy latency of <5ms,
and a 99th percentile latency of 60ms. Using a minimum timeout of 200ms
seems to be a fair trade-off between avoiding unnecessary high
connection churn and reacting to upstream failures in a timely manner.

This change also renames hcDuration to hcInterval to reflect its usage,
and removes the duplicated timeout constant to make code comprehension
easier.
This commit is contained in:
Tobias Schmidt
2018-06-21 12:40:19 +02:00
committed by Miek Gieben
parent e3534205c7
commit 422aec5f5f
5 changed files with 10 additions and 10 deletions

View File

@@ -83,8 +83,9 @@ Also note the TLS config is "global" for the whole forwarding proxy if you need
`tls-name` for different upstreams you're out of luck. `tls-name` for different upstreams you're out of luck.
On each endpoint, the timeouts of the communication are set by default and automatically tuned depending early results. On each endpoint, the timeouts of the communication are set by default and automatically tuned depending early results.
- dialTimeout by default is 30 sec, and can decrease automatically down to 100ms
- readTimeout by default is 2 sec, and can decrease automatically down to 10ms * dialTimeout by default is 30 sec, and can decrease automatically down to 100ms
* readTimeout by default is 2 sec, and can decrease automatically down to 200ms
## Metrics ## Metrics

View File

@@ -97,7 +97,7 @@ func (p *Proxy) Connect(ctx context.Context, state request.Request, forceTCP, me
conn.UDPSize = 512 conn.UDPSize = 512
} }
conn.SetWriteDeadline(time.Now().Add(timeout)) conn.SetWriteDeadline(time.Now().Add(maxTimeout))
reqTime := time.Now() reqTime := time.Now()
if err := conn.WriteMsg(state.Req); err != nil { if err := conn.WriteMsg(state.Req); err != nil {
conn.Close() // not giving it back conn.Close() // not giving it back
@@ -110,7 +110,7 @@ func (p *Proxy) Connect(ctx context.Context, state request.Request, forceTCP, me
conn.SetReadDeadline(time.Now().Add(p.readTimeout())) conn.SetReadDeadline(time.Now().Add(p.readTimeout()))
ret, err := conn.ReadMsg() ret, err := conn.ReadMsg()
if err != nil { if err != nil {
p.updateRtt(timeout) p.updateRtt(maxTimeout)
conn.Close() // not giving it back conn.Close() // not giving it back
if err == io.EOF && cached { if err == io.EOF && cached {
return nil, ErrCachedClosed return nil, ErrCachedClosed

View File

@@ -39,7 +39,7 @@ type Forward struct {
// New returns a new Forward. // New returns a new Forward.
func New() *Forward { func New() *Forward {
f := &Forward{maxfails: 2, tlsConfig: new(tls.Config), expire: defaultExpire, p: new(random), from: ".", hcInterval: hcDuration} f := &Forward{maxfails: 2, tlsConfig: new(tls.Config), expire: defaultExpire, p: new(random), from: ".", hcInterval: hcInterval}
return f return f
} }

View File

@@ -34,7 +34,7 @@ func NewProxy(addr string, tlsConfig *tls.Config) *Proxy {
fails: 0, fails: 0,
probe: up.New(), probe: up.New(),
transport: newTransport(addr, tlsConfig), transport: newTransport(addr, tlsConfig),
avgRtt: int64(timeout / 2), avgRtt: int64(maxTimeout / 2),
} }
p.client = dnsClient(tlsConfig) p.client = dnsClient(tlsConfig)
runtime.SetFinalizer(p, (*Proxy).finalizer) runtime.SetFinalizer(p, (*Proxy).finalizer)
@@ -106,8 +106,7 @@ func (p *Proxy) start(duration time.Duration) {
} }
const ( const (
timeout = 2 * time.Second
maxTimeout = 2 * time.Second maxTimeout = 2 * time.Second
minTimeout = 10 * time.Millisecond minTimeout = 200 * time.Millisecond
hcDuration = 500 * time.Millisecond hcInterval = 500 * time.Millisecond
) )

View File

@@ -27,7 +27,7 @@ func TestProxyClose(t *testing.T) {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
p := NewProxy(s.Addr, nil) p := NewProxy(s.Addr, nil)
p.start(hcDuration) p.start(hcInterval)
go func() { p.Connect(ctx, state, false, false) }() go func() { p.Connect(ctx, state, false, false) }()
go func() { p.Connect(ctx, state, true, false) }() go func() { p.Connect(ctx, state, true, false) }()