Deemwar Monads & Abstractions

When time went back a second

Source: How and why the leap second affected Cloudflare DNS, by John Graham-Cumming

At midnight UTC on 1 January 2017, a leap second made a duration in Cloudflare's RRDNS go negative. The smoothed value reached a random-number call that panics on negative input, and some CNAME lookups failed. The fix stopped negative values from being recorded.

102 data centers in the network (a small number of machines hit)
~0.2% DNS queries at peak
<1% HTTP requests errored
06:45 UTC impact ends

Today's trigger: the Hacker News thread BIPM decides to stop adding leap seconds to UTC. The Cloudflare postmortem shows what one inserted second did to production code.

What happened

At midnight UTC on New Year's Day, a number deep inside RRDNS, Cloudflare's custom DNS software, went negative when it should have been, at worst, zero. A little later the negative value made RRDNS panic. The panic was caught with Go's recover, and some DNS resolutions for Cloudflare-managed sites failed.

Who was hitOnly customers using CNAME records with Cloudflare.
WhereA small number of machines across 102 data centers.
How muchAbout 0.2% of DNS queries at peak; less than 1% of HTTP requests errored.
How fastMost affected machines patched in 90 minutes; fix worldwide by 06:45 UTC.

The mechanism

For a CNAME, Cloudflare sometimes has to look up the origin's real IP address. RRDNS asks internal resolvers, tracks how well each performs, and picks one with a weighted selection. RRDNS is written in Go and used time.Now(), which at the time did not guarantee monotonicity; Go had no monotonic time source (issue 12914). The timing code from the post:

// Update upstream sRTT on UDP queries, penalize it if it fails
if !start.IsZero() {
    rtt := time.Now().Sub(start)
    if success && rcode != dns.RcodeServerFailure {
        s.updateRTT(rtt)
    } else {
        // The penalty should be a multiple of actual timeout
        // as we don't know when the good message was supposed to arrive,
        // but it should not put server to backoff instantly
        s.updateRTT(TimeoutPenalty * s.timeout)
    }
}

The resolvers are tuned to answer in a few milliseconds. If time goes back a second during a resolution, rtt is negative. RRDNS smooths many measurements, so one negative sample did not flip the resolver's value, but after a few the smoothed value became negative. The weighted selection feeds these values to rand.Int63n(), which panics if its argument is negative.

One second backwards, four steps to a failed lookup.

The fix

The post calls it "the one character fix". With a non-monotonic clock, check whether the difference between two timestamps is negative; until the clock stops rewinding, the real difference cannot be known.

Timeline

2017-01-01 (UTC)Event
00:00Impact starts
00:10Escalated to engineers
00:34Issue confirmed
00:55Mitigation deployed to one canary node and confirmed
01:03Mitigation deployed to canary data center and confirmed
01:23Fix deployed in most impacted data center
01:45Fix being deployed to major data centers
01:48Fix being deployed everywhere
02:50Fix rolled out to most of the affected data centers
06:45Impact ends
00:34confirmed
00:55one canary node
01:03canary data center
01:48everywhere

What changed in Go

Go 1.9 added transparent monotonic time: the time package tracks monotonic time in each Time value, which makes computing durations between two Time values safe when the wall clock is adjusted. The package docs state the rule:

"The general rule is that the wall clock is for telling time and the monotonic clock is for measuring time."

Since then, time.Now() carries both readings, and subtractions such as t.Sub(start), time.Since and time.Until use the monotonic one.

The takeaway

Compute durations from a monotonic source. Where only a wall clock is available, check the difference for a negative value before it reaches code that assumes time moves forward.

Sources

Sources: all numbers, times and code from the Cloudflare post; Go 1.9 and the clock rule from the Go release notes and package docs.