datadog agent hashset 优化

104 阅读2分钟

datadog agent 为每个指标生成唯一 hash 用于聚合。

  • 使用 metric_name 计算 hash
  • 对指标进行排序
  • 对指标进行去重
  • 将每个 tag 都用于计算 hash
	context = hash(metric_name)
	tags = sort(tags)
	tags = deduplicate(tags)
	for tag in tags:
		context = hash(context, tag)

将 context 从 128 位换成 64 位

go 的 map 对于特殊的 key 会有专门的优化。

因此将 context 从 128 位换成 64 位可以有效的提升效率。

使用 murmur3 算法计算 hash

一秒内生成的唯一 hash 最多。

去除排序和重复

通过异或的 hash 算法来去除排序:

	context = hash(metric_name)
	tags = deduplicate(tags)
	for tag in tags:
		context = context ^ hash(tag)

通过 hash set 来去重:

  • 避免 map 对内存的开销
context = hash(metric_name)
for tag in tags:
	htag = hash(tag)
	if hashset[htag] == False
		context = context ^ htag
		hashset[htag] = True
	hashset.reset()

当 tags 数量小于或等于 4 的时候:

我们维护一个 seen 的数组。

通过换位将重复的 tag 移动到末尾然后进行 truncate。

   tags := tb.data
   hashes := tb.hash
   ntags := tb.Len()
OUTER:
   for i := 0; i < ntags; {
      h := hashes[i]
      for j := 0; j < i; j++ {
         if g.seen[j] == h && tags[j] == tags[i] {
            tags[i] = tags[ntags-1]
            hashes[i] = hashes[ntags-1]
            ntags--
            continue OUTER // we do not want to xor multiple times the same tag
         }
      }
      hash ^= h
      g.seen[i] = h
      i++
   }
   tb.Truncate(ntags)

当数据量比较大的时候,使用 hashset 算法:

通过 hash & 数组的大小,得到一个下标。

  • 如果下标为空,说明没有见过,加入
  • 如果下标有值并且相同,跳过
  • 如果下标有值并且不相同,加入到

通过 bulk copy 来重置数组。

tags := tb.data
hashes := tb.hash

// reset the `seen` hashset.
// it copies `g.empty` instead of using make because it's faster

// for smaller tag sets, initialize only a portion of the array. when len(tags) is
// close to a power of two, size one up to keep hashset load low.
size := 1 << bits.Len(uint(len(tags)+len(tags)/8))
if size > hashSetSize {
   size = hashSetSize
}
mask := uint64(size - 1)
copy(g.seenIdx[:size], g.empty[:size])

ntags := len(tags)
for i := 0; i < ntags; {
   h := hashes[i]
   j := h & mask // address this hash into the hashset
   for {
      if g.seenIdx[j] == blank {
         // not seen, we will add it to the hash
         g.seen[j] = h
         g.seenIdx[j] = int16(i)
         hash ^= h // add this tag into the hash
         i++
         break
      } else if g.seen[j] == h && tags[g.seenIdx[j]] == tags[i] {
         // already seen, we do not want to xor multiple times the same tag
         tags[i] = tags[ntags-1]
         hashes[i] = hashes[ntags-1]
         ntags--
         break
      } else {
         // move 'right' in the hashset because there is already a value,
         // in this bucket, which is not the one we're dealing with right now,
         // we may have already seen this tag
         j = (j + 1) & mask
      }
   }
}