Move cache Keys to 64bit for a better dispersion and lower collision frequency (#2077)

* - change Key for cache to 64bits.

* - change Key for cache to 64bits.
This commit is contained in:
Francois Tur 2018-08-31 17:26:43 -04:00 committed by Yong Tang
parent d00e8c3918
commit 4c6c9d4b27
6 changed files with 43 additions and 41 deletions

30
plugin/cache/cache.go vendored
View file

@ -60,24 +60,24 @@ func New() *Cache {
// Return key under which we store the item, -1 will be returned if we don't store the
// message.
// Currently we do not cache Truncated, errors zone transfers or dynamic update messages.
func key(m *dns.Msg, t response.Type, do bool) int {
func key(m *dns.Msg, t response.Type, do bool) (bool, uint64) {
// We don't store truncated responses.
if m.Truncated {
return -1
return false, 0
}
// Nor errors or Meta or Update
if t == response.OtherError || t == response.Meta || t == response.Update {
return -1
return false, 0
}
return int(hash(m.Question[0].Name, m.Question[0].Qtype, do))
return true, hash(m.Question[0].Name, m.Question[0].Qtype, do)
}
var one = []byte("1")
var zero = []byte("0")
func hash(qname string, qtype uint16, do bool) uint32 {
h := fnv.New32()
func hash(qname string, qtype uint16, do bool) uint64 {
h := fnv.New64()
if do {
h.Write(one)
@ -97,7 +97,7 @@ func hash(qname string, qtype uint16, do bool) uint32 {
h.Write([]byte{c})
}
return h.Sum32()
return h.Sum64()
}
// ResponseWriter is a response writer that caches the reply message.
@ -152,7 +152,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
}
// key returns empty string for anything we don't want to cache.
key := key(res, mt, do)
hasKey, key := key(res, mt, do)
duration := w.pttl
if mt == response.NameError || mt == response.NoData {
@ -164,7 +164,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
duration = msgTTL
}
if key != -1 && duration > 0 {
if hasKey && duration > 0 {
if w.state.Match(res) {
w.set(res, key, mt, duration)
cacheSize.WithLabelValues(w.server, Success).Set(float64(w.pcache.Len()))
@ -195,19 +195,17 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
return w.ResponseWriter.WriteMsg(res)
}
func (w *ResponseWriter) set(m *dns.Msg, key int, mt response.Type, duration time.Duration) {
if key == -1 || duration == 0 {
return
}
func (w *ResponseWriter) set(m *dns.Msg, key uint64, mt response.Type, duration time.Duration) {
// duration is expected > 0
// and key is valid
switch mt {
case response.NoError, response.Delegation:
i := newItem(m, w.now(), duration)
w.pcache.Add(uint32(key), i)
w.pcache.Add(key, i)
case response.NameError, response.NoData:
i := newItem(m, w.now(), duration)
w.ncache.Add(uint32(key), i)
w.ncache.Add(key, i)
case response.OtherError:
// don't cache these

View file

@ -167,9 +167,11 @@ func TestCache(t *testing.T) {
state := request.Request{W: nil, Req: m}
mt, _ := response.Typify(m, utc)
k := key(m, mt, state.Do())
valid, k := key(m, mt, state.Do())
if valid {
crr.set(m, k, mt, c.pttl)
}
i, _ := c.get(time.Now().UTC(), state, "dns://:53")
ok := i != nil

View file

@ -7,8 +7,8 @@ import (
)
// hash serializes the RRset and return a signature cache key.
func hash(rrs []dns.RR) uint32 {
h := fnv.New32()
func hash(rrs []dns.RR) uint64 {
h := fnv.New64()
buf := make([]byte, 256)
for _, r := range rrs {
off, err := dns.PackRR(r, buf, 0, nil, false)
@ -17,6 +17,6 @@ func hash(rrs []dns.RR) uint32 {
}
}
i := h.Sum32()
i := h.Sum64()
return i
}

View file

@ -110,9 +110,9 @@ func (d Dnssec) sign(rrs []dns.RR, signerName string, ttl, incep, expir uint32,
return sigs.([]dns.RR), err
}
func (d Dnssec) set(key uint32, sigs []dns.RR) { d.cache.Add(key, sigs) }
func (d Dnssec) set(key uint64, sigs []dns.RR) { d.cache.Add(key, sigs) }
func (d Dnssec) get(key uint32, server string) ([]dns.RR, bool) {
func (d Dnssec) get(key uint64, server string) ([]dns.RR, bool) {
if s, ok := d.cache.Get(key); ok {
// we sign for 8 days, check if a signature in the cache reached 3/4 of that
is75 := time.Now().UTC().Add(sixDays)

View file

@ -9,10 +9,10 @@ import (
)
// Hash returns the FNV hash of what.
func Hash(what []byte) uint32 {
h := fnv.New32()
func Hash(what []byte) uint64 {
h := fnv.New64()
h.Write(what)
return h.Sum32()
return h.Sum64()
}
// Cache is cache.
@ -22,7 +22,7 @@ type Cache struct {
// shard is a cache with random eviction.
type shard struct {
items map[uint32]interface{}
items map[uint64]interface{}
size int
sync.RWMutex
@ -45,19 +45,19 @@ func New(size int) *Cache {
}
// Add adds a new element to the cache. If the element already exists it is overwritten.
func (c *Cache) Add(key uint32, el interface{}) {
func (c *Cache) Add(key uint64, el interface{}) {
shard := key & (shardSize - 1)
c.shards[shard].Add(key, el)
}
// Get looks up element index under key.
func (c *Cache) Get(key uint32) (interface{}, bool) {
func (c *Cache) Get(key uint64) (interface{}, bool) {
shard := key & (shardSize - 1)
return c.shards[shard].Get(key)
}
// Remove removes the element indexed with key.
func (c *Cache) Remove(key uint32) {
func (c *Cache) Remove(key uint64) {
shard := key & (shardSize - 1)
c.shards[shard].Remove(key)
}
@ -72,10 +72,10 @@ func (c *Cache) Len() int {
}
// newShard returns a new shard with size.
func newShard(size int) *shard { return &shard{items: make(map[uint32]interface{}), size: size} }
func newShard(size int) *shard { return &shard{items: make(map[uint64]interface{}), size: size} }
// Add adds element indexed by key into the cache. Any existing element is overwritten
func (s *shard) Add(key uint32, el interface{}) {
func (s *shard) Add(key uint64, el interface{}) {
l := s.Len()
if l+1 > s.size {
s.Evict()
@ -87,7 +87,7 @@ func (s *shard) Add(key uint32, el interface{}) {
}
// Remove removes the element indexed by key from the cache.
func (s *shard) Remove(key uint32) {
func (s *shard) Remove(key uint64) {
s.Lock()
delete(s.items, key)
s.Unlock()
@ -95,26 +95,28 @@ func (s *shard) Remove(key uint32) {
// Evict removes a random element from the cache.
func (s *shard) Evict() {
key := -1
hasKey := false
var key uint64
s.RLock()
for k := range s.items {
key = int(k)
key = k
hasKey = true
break
}
s.RUnlock()
if key == -1 {
if !hasKey {
// empty cache
return
}
// If this item is gone between the RUnlock and Lock race we don't care.
s.Remove(uint32(key))
s.Remove(key)
}
// Get looks up the element indexed under key.
func (s *shard) Get(key uint32) (interface{}, bool) {
func (s *shard) Get(key uint64) (interface{}, bool) {
s.RLock()
el, found := s.items[key]
s.RUnlock()

View file

@ -31,17 +31,17 @@ type call struct {
// units of work can be executed with duplicate suppression.
type Group struct {
mu sync.Mutex // protects m
m map[uint32]*call // lazily initialized
m map[uint64]*call // lazily initialized
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
func (g *Group) Do(key uint32, fn func() (interface{}, error)) (interface{}, error) {
func (g *Group) Do(key uint64, fn func() (interface{}, error)) (interface{}, error) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[uint32]*call)
g.m = make(map[uint64]*call)
}
if c, ok := g.m[key]; ok {
g.mu.Unlock()