[BUG?]fail_timeout/max_fails: code doesn't do what doc says
Maxim Dounin
mdounin at mdounin.ru
Tue May 21 13:23:08 UTC 2013
Hello!
On Tue, May 21, 2013 at 04:16:05PM +0400, Dmitry Popov wrote:
> On Mon, 20 May 2013 21:11:03 +0400
> Maxim Dounin <mdounin at mdounin.ru> wrote:
>
> > Patch:
> >
> > diff --git a/src/http/modules/ngx_http_upstream_least_conn_module.c b/src/http/modules/ngx_http_upstream_least_conn_module.c
> > --- a/src/http/modules/ngx_http_upstream_least_conn_module.c
> > +++ b/src/http/modules/ngx_http_upstream_least_conn_module.c
> > @@ -282,7 +282,10 @@ ngx_http_upstream_get_least_conn_peer(ng
> > }
> >
> > best->current_weight -= total;
> > - best->checked = now;
> > +
> > + if (now - best->checked > best->fail_timeout) {
> > + best->checked = now;
> > + }
> >
> > pc->sockaddr = best->sockaddr;
> > pc->socklen = best->socklen;
> > diff --git a/src/http/ngx_http_upstream_round_robin.c b/src/http/ngx_http_upstream_round_robin.c
> > --- a/src/http/ngx_http_upstream_round_robin.c
> > +++ b/src/http/ngx_http_upstream_round_robin.c
> > @@ -523,7 +523,10 @@ ngx_http_upstream_get_peer(ngx_http_upst
> > rrp->tried[n] |= m;
> >
> > best->current_weight -= total;
> > - best->checked = now;
> > +
> > + if (now - best->checked > best->fail_timeout) {
> > + best->checked = now;
> > + }
> >
> > return best;
> > }
>
> Still a bug exists because of this code:
>
> if (state & NGX_PEER_FAILED) {
> /* ... */
> peer->fails++;
> peer->accessed = now;
> peer->checked = now;
> /* .... */
> } else {
> if (peer->accessed < peer->checked) {
> peer->fails = 0;
> }
> }
>
> Consider upstream which fails at least once each second (actually it's enough
> to fail each fail_timeout seconds). This upstream will be disabled as soon as it
> will fail max_fails times no matter what fail_timeout is.
This is expected behaviour. Documentation is a bit simplified
here, and fail_timeout is used like session time limit - the
peer->fails counter is reset once there are no failures within
fail_timeout.
While this might be non-ideal for some use cases, it's certainly
not a bug.
> I propose the following solution:
> let's have two bits per peer:
> 1) avail - set if this peer is available for usage (fails < max_fails)
> 2) avail_check - set if this peer was disabled, but its fail_timeout is expired and
> we sent one request to check if it's alive
> and two timestamps:
> 3) first_fail - we'll count number of fails only inside
> [first_fail, first_fail + fail_timeout) interval
> 4) unavail_from - peer is unavailable inside
> [unavail_from, unavail_from + fail_timeout) interval
>
> first_fail will only be used if avail == 1, unavail_from only if avail == 0,
> so we may use union here.
>
> Simplified code:
> get:
> if (!avail
> && (now < unavail_from + fail_timeout || avail_check))
> {
> continue;
> }
>
> if (!avail)
> avail_check =1;
> free:
> if (avail && now >= first_fail + fail_timeout)
> fails = 0;
>
> if (request_failed) {
> if (fails == 0)
> first_fail = now;
> fails++;
> if (fails >= max_fails) {
> avail = 0;
> unavail_from = now;
> }
> avail_check = 0;
> } else {
> if (avail_check) {
> fails = 0;
> avail = 1;
> avail_check = 0;
> }
> }
Such algorithm forget everything about previous failures once per
fail_timeout, and won't detect bursts of failures split across
two fail_timeout intervals.
Consider:
- max_fails = 3, fail_timeout = 10s
- failure at 0s
- failure at 9s
- at 10s peer->fails counter is reset
- failure at 11s
- failure at 12s
While 3 failures are only 3 seconds away from each other, this
is not detected due to granularity introduced by the algorithm.
--
Maxim Dounin
http://nginx.org/en/donation.html
More information about the nginx-devel
mailing list