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

View file

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

View file

@ -7,8 +7,8 @@ import (
) )
// hash serializes the RRset and return a signature cache key. // hash serializes the RRset and return a signature cache key.
func hash(rrs []dns.RR) uint32 { func hash(rrs []dns.RR) uint64 {
h := fnv.New32() h := fnv.New64()
buf := make([]byte, 256) buf := make([]byte, 256)
for _, r := range rrs { for _, r := range rrs {
off, err := dns.PackRR(r, buf, 0, nil, false) 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 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 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 { 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 // we sign for 8 days, check if a signature in the cache reached 3/4 of that
is75 := time.Now().UTC().Add(sixDays) is75 := time.Now().UTC().Add(sixDays)

View file

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

View file

@ -31,17 +31,17 @@ type call struct {
// units of work can be executed with duplicate suppression. // units of work can be executed with duplicate suppression.
type Group struct { type Group struct {
mu sync.Mutex // protects m 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 // 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 // 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 // time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results. // 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() g.mu.Lock()
if g.m == nil { if g.m == nil {
g.m = make(map[uint32]*call) g.m = make(map[uint64]*call)
} }
if c, ok := g.m[key]; ok { if c, ok := g.m[key]; ok {
g.mu.Unlock() g.mu.Unlock()