Add MINTTL parameter to cache configuration. (#2055)

* Add success min TTL parameter to cache.

* Add MINTTL to README.

* Update README.

* Add MINTTL to negative cache.

* Remove unnecessary variable name.

* Address review comments.

* Configure cache in TestCacheZeroTTL to have 0 min ttl.
This commit is contained in:
Aaron Riekenberg 2018-09-03 14:26:02 -05:00 committed by Tobias Schmidt
parent 4c6c9d4b27
commit b42eae7a04
5 changed files with 111 additions and 31 deletions

View file

@ -32,8 +32,8 @@ If you want more control:
~~~ txt ~~~ txt
cache [TTL] [ZONES...] { cache [TTL] [ZONES...] {
success CAPACITY [TTL] success CAPACITY [TTL] [MINTTL]
denial CAPACITY [TTL] denial CAPACITY [TTL] [MINTTL]
prefetch AMOUNT [[DURATION] [PERCENTAGE%]] prefetch AMOUNT [[DURATION] [PERCENTAGE%]]
} }
~~~ ~~~
@ -41,8 +41,10 @@ cache [TTL] [ZONES...] {
* **TTL** and **ZONES** as above. * **TTL** and **ZONES** as above.
* `success`, override the settings for caching successful responses. **CAPACITY** indicates the maximum * `success`, override the settings for caching successful responses. **CAPACITY** indicates the maximum
number of packets we cache before we start evicting (*randomly*). **TTL** overrides the cache maximum TTL. number of packets we cache before we start evicting (*randomly*). **TTL** overrides the cache maximum TTL.
**MINTTL** overrides the cache minimum TTL, which can be useful to limit queries to the backend.
* `denial`, override the settings for caching denial of existence responses. **CAPACITY** indicates the maximum * `denial`, override the settings for caching denial of existence responses. **CAPACITY** indicates the maximum
number of packets we cache before we start evicting (LRU). **TTL** overrides the cache maximum TTL. number of packets we cache before we start evicting (LRU). **TTL** overrides the cache maximum TTL.
**MINTTL** overrides the cache minimum TTL, which can be useful to limit queries to the backend.
There is a third category (`error`) but those responses are never cached. There is a third category (`error`) but those responses are never cached.
* `prefetch` will prefetch popular items when they are about to be expunged from the cache. * `prefetch` will prefetch popular items when they are about to be expunged from the cache.
Popular means **AMOUNT** queries have been seen with no gaps of **DURATION** or more between them. Popular means **AMOUNT** queries have been seen with no gaps of **DURATION** or more between them.

41
plugin/cache/cache.go vendored
View file

@ -22,13 +22,15 @@ type Cache struct {
Next plugin.Handler Next plugin.Handler
Zones []string Zones []string
ncache *cache.Cache ncache *cache.Cache
ncap int ncap int
nttl time.Duration nttl time.Duration
minnttl time.Duration
pcache *cache.Cache pcache *cache.Cache
pcap int pcap int
pttl time.Duration pttl time.Duration
minpttl time.Duration
// Prefetch. // Prefetch.
prefetch int prefetch int
@ -47,9 +49,11 @@ func New() *Cache {
pcap: defaultCap, pcap: defaultCap,
pcache: cache.New(defaultCap), pcache: cache.New(defaultCap),
pttl: maxTTL, pttl: maxTTL,
minpttl: minTTL,
ncap: defaultCap, ncap: defaultCap,
ncache: cache.New(defaultCap), ncache: cache.New(defaultCap),
nttl: maxNTTL, nttl: maxNTTL,
minnttl: minNTTL,
prefetch: 0, prefetch: 0,
duration: 1 * time.Minute, duration: 1 * time.Minute,
percentage: 10, percentage: 10,
@ -100,6 +104,17 @@ func hash(qname string, qtype uint16, do bool) uint64 {
return h.Sum64() return h.Sum64()
} }
func computeTTL(msgTTL, minTTL, maxTTL time.Duration) time.Duration {
ttl := msgTTL
if ttl < minTTL {
ttl = minTTL
}
if ttl > maxTTL {
ttl = maxTTL
}
return ttl
}
// ResponseWriter is a response writer that caches the reply message. // ResponseWriter is a response writer that caches the reply message.
type ResponseWriter struct { type ResponseWriter struct {
dns.ResponseWriter dns.ResponseWriter
@ -154,14 +169,12 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
// key returns empty string for anything we don't want to cache. // key returns empty string for anything we don't want to cache.
hasKey, key := key(res, mt, do) hasKey, key := key(res, mt, do)
duration := w.pttl
if mt == response.NameError || mt == response.NoData {
duration = w.nttl
}
msgTTL := dnsutil.MinimalTTL(res, mt) msgTTL := dnsutil.MinimalTTL(res, mt)
if msgTTL < duration { var duration time.Duration
duration = msgTTL if mt == response.NameError || mt == response.NoData {
duration = computeTTL(msgTTL, w.minnttl, w.nttl)
} else {
duration = computeTTL(msgTTL, w.minpttl, w.pttl)
} }
if hasKey && duration > 0 { if hasKey && duration > 0 {
@ -226,7 +239,9 @@ func (w *ResponseWriter) Write(buf []byte) (int, error) {
const ( const (
maxTTL = dnsutil.MaximumDefaulTTL maxTTL = dnsutil.MaximumDefaulTTL
minTTL = dnsutil.MinimalDefaultTTL
maxNTTL = dnsutil.MaximumDefaulTTL / 2 maxNTTL = dnsutil.MaximumDefaulTTL / 2
minNTTL = dnsutil.MinimalDefaultTTL
defaultCap = 10000 // default capacity of the cache. defaultCap = 10000 // default capacity of the cache.

View file

@ -205,6 +205,8 @@ func TestCache(t *testing.T) {
func TestCacheZeroTTL(t *testing.T) { func TestCacheZeroTTL(t *testing.T) {
c := New() c := New()
c.minpttl = 0
c.minnttl = 0
c.Next = zeroTTLBackend() c.Next = zeroTTLBackend()
req := new(dns.Msg) req := new(dns.Msg)
@ -270,3 +272,23 @@ func zeroTTLBackend() plugin.Handler {
return dns.RcodeSuccess, nil return dns.RcodeSuccess, nil
}) })
} }
func TestComputeTTL(t *testing.T) {
tests := []struct {
msgTTL time.Duration
minTTL time.Duration
maxTTL time.Duration
expectedTTL time.Duration
}{
{1800 * time.Second, 300 * time.Second, 3600 * time.Second, 1800 * time.Second},
{299 * time.Second, 300 * time.Second, 3600 * time.Second, 300 * time.Second},
{299 * time.Second, 0 * time.Second, 3600 * time.Second, 299 * time.Second},
{3601 * time.Second, 300 * time.Second, 3600 * time.Second, 3600 * time.Second},
}
for i, test := range tests {
ttl := computeTTL(test.msgTTL, test.minTTL, test.maxTTL)
if ttl != test.expectedTTL {
t.Errorf("Test %v: Expected ttl %v but found: %v", i, test.expectedTTL, ttl)
}
}
}

22
plugin/cache/setup.go vendored
View file

@ -101,6 +101,17 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", pttl) return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", pttl)
} }
ca.pttl = time.Duration(pttl) * time.Second ca.pttl = time.Duration(pttl) * time.Second
if len(args) > 2 {
minpttl, err := strconv.Atoi(args[2])
if err != nil {
return nil, err
}
// Reserve < 0
if minpttl < 0 {
return nil, fmt.Errorf("cache min TTL can not be negative: %d", minpttl)
}
ca.minpttl = time.Duration(minpttl) * time.Second
}
} }
case Denial: case Denial:
args := c.RemainingArgs() args := c.RemainingArgs()
@ -122,6 +133,17 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", nttl) return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", nttl)
} }
ca.nttl = time.Duration(nttl) * time.Second ca.nttl = time.Duration(nttl) * time.Second
if len(args) > 2 {
minnttl, err := strconv.Atoi(args[2])
if err != nil {
return nil, err
}
// Reserve < 0
if minnttl < 0 {
return nil, fmt.Errorf("cache min TTL can not be negative: %d", minnttl)
}
ca.minnttl = time.Duration(minnttl) * time.Second
}
} }
case "prefetch": case "prefetch":
args := c.RemainingArgs() args := c.RemainingArgs()

View file

@ -14,54 +14,67 @@ func TestSetup(t *testing.T) {
expectedNcap int expectedNcap int
expectedPcap int expectedPcap int
expectedNttl time.Duration expectedNttl time.Duration
expectedMinNttl time.Duration
expectedPttl time.Duration expectedPttl time.Duration
expectedMinPttl time.Duration
expectedPrefetch int expectedPrefetch int
}{ }{
{`cache`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, {`cache`, false, defaultCap, defaultCap, maxNTTL, minNTTL, maxTTL, minTTL, 0},
{`cache {}`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, {`cache {}`, false, defaultCap, defaultCap, maxNTTL, minNTTL, maxTTL, minTTL, 0},
{`cache example.nl { {`cache example.nl {
success 10 success 10
}`, false, defaultCap, 10, maxNTTL, maxTTL, 0}, }`, false, defaultCap, 10, maxNTTL, minNTTL, maxTTL, minTTL, 0},
{`cache example.nl {
success 10 1800 30
}`, false, defaultCap, 10, maxNTTL, minNTTL, 1800 * time.Second, 30 * time.Second, 0},
{`cache example.nl { {`cache example.nl {
success 10 success 10
denial 10 15 denial 10 15
}`, false, 10, 10, 15 * time.Second, maxTTL, 0}, }`, false, 10, 10, 15 * time.Second, minNTTL, maxTTL, minTTL, 0},
{`cache example.nl {
success 10
denial 10 15 2
}`, false, 10, 10, 15 * time.Second, 2 * time.Second, maxTTL, minTTL, 0},
{`cache 25 example.nl { {`cache 25 example.nl {
success 10 success 10
denial 10 15 denial 10 15
}`, false, 10, 10, 15 * time.Second, 25 * time.Second, 0}, }`, false, 10, 10, 15 * time.Second, minNTTL, 25 * time.Second, minTTL, 0},
{`cache aaa example.nl`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, {`cache 25 example.nl {
success 10
denial 10 15 5
}`, false, 10, 10, 15 * time.Second, 5 * time.Second, 25 * time.Second, minTTL, 0},
{`cache aaa example.nl`, false, defaultCap, defaultCap, maxNTTL, minNTTL, maxTTL, minTTL, 0},
{`cache { {`cache {
prefetch 10 prefetch 10
}`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 10}, }`, false, defaultCap, defaultCap, maxNTTL, minNTTL, maxTTL, minTTL, 10},
// fails // fails
{`cache example.nl { {`cache example.nl {
success success
denial 10 15 denial 10 15
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
{`cache example.nl { {`cache example.nl {
success 15 success 15
denial aaa denial aaa
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
{`cache example.nl { {`cache example.nl {
positive 15 positive 15
negative aaa negative aaa
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
{`cache 0 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, {`cache 0 example.nl`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
{`cache -1 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, {`cache -1 example.nl`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
{`cache 1 example.nl { {`cache 1 example.nl {
positive 0 positive 0
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
{`cache 1 example.nl { {`cache 1 example.nl {
positive 0 positive 0
prefetch -1 prefetch -1
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
{`cache 1 example.nl { {`cache 1 example.nl {
prefetch 0 blurp prefetch 0 blurp
}`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, }`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
{`cache {`cache
cache`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, cache`, true, defaultCap, defaultCap, maxTTL, minNTTL, maxTTL, minTTL, 0},
} }
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("dns", test.input) c := caddy.NewTestController("dns", test.input)
@ -86,9 +99,15 @@ func TestSetup(t *testing.T) {
if ca.nttl != test.expectedNttl { if ca.nttl != test.expectedNttl {
t.Errorf("Test %v: Expected nttl %v but found: %v", i, test.expectedNttl, ca.nttl) t.Errorf("Test %v: Expected nttl %v but found: %v", i, test.expectedNttl, ca.nttl)
} }
if ca.minnttl != test.expectedMinNttl {
t.Errorf("Test %v: Expected minnttl %v but found: %v", i, test.expectedMinNttl, ca.minnttl)
}
if ca.pttl != test.expectedPttl { if ca.pttl != test.expectedPttl {
t.Errorf("Test %v: Expected pttl %v but found: %v", i, test.expectedPttl, ca.pttl) t.Errorf("Test %v: Expected pttl %v but found: %v", i, test.expectedPttl, ca.pttl)
} }
if ca.minpttl != test.expectedMinPttl {
t.Errorf("Test %v: Expected minpttl %v but found: %v", i, test.expectedMinPttl, ca.minpttl)
}
if ca.prefetch != test.expectedPrefetch { if ca.prefetch != test.expectedPrefetch {
t.Errorf("Test %v: Expected prefetch %v but found: %v", i, test.expectedPrefetch, ca.prefetch) t.Errorf("Test %v: Expected prefetch %v but found: %v", i, test.expectedPrefetch, ca.prefetch)
} }