Change MetricFamily dictionary key from 32 to 64 bits hash to reduce collisions #342
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Context
The
MetricFamily
uses a 32-bit hashes of the labels as keys to disambiguate series. This can be an issue as collisions are very common on such a narrow space.According to the birthday paradox, and considering a good hash function (which
HashCode.Combine
is), there is a 50% chance of collisions for 77163 32-bit hashes. In our case, that translate to as many chances to have two series colliding, resulting in metrics data to be mixed up, which can be very nasty to debug.To prove this point, here is a test that shows that collisions do happen:
This test does fail about 50% of the time.
Solution
A solution would be to compare labels when checking for equality. This however would require some changes due to the support of pre-.NET6 because of the missing
ITuple
interface, requiring more usage of compiled expression, which would complexify the code.Instead, I propose to increase the hash width to 64-bit. This can be easily done by replacing the Int32 by a struct containing two Int32: one that remains used as a key, as we do today, but a second one to then check the equality, effectively reducing the odds of collision by a factor 77163, if I'm correct. That brings us to having to generate 5954128569 series to have a 50% chance of collisions, which to be goes from "quite likely to have collisions" to "very unlikely".
Of course, the two generated hashes need to be different, that's why I introduced a seed, which was quite an easy addition given the current code.
This PR presents this solution.
Performance
I ran the
CounterUsage
benchmark on my Macbook pro M1:It seems this changes does have a small performance cost, which I think is acceptable considering the issue this addresses. If this is a concern, an improvement could be to generate a single 64-bit hash instead, but that would be require access to such function, which I don't think we have pre .NET 6 without a third party library.