The router was three lines.
Take the customer key.
Hash it.
Pick a cache node.
Math.abs of the hash code,
modulo the number of nodes,
and you have an index.
Every key has a home.
It ran for two years
on millions of keys a day,
across ten nodes.
Then one request
started failing every time,
for one key,
with an index out of bounds.
The index was minus eight.
Nobody believed it.
There is an abs right there.
The answer cannot be negative.
It can.
Here is the mechanism.
A Java int runs from
minus 2,147,483,648
to plus 2,147,483,647.
The negative side
is one bigger than the positive side.
So the smallest int
has no positive twin.
Ask Math.abs for it,
and the true answer does not fit,
so the arithmetic wraps
and hands you back
the same negative number.
The documentation says so,
in a sentence nobody reads.
Then the remainder operator
keeps the sign of the left side.
A negative hash
gives a negative index.
About one string in four billion
hashes to exactly that value.
That sounds like never.
At millions of keys a day,
it is a date on the calendar.
And it is not random.
The same key fails
every single time,
so it looks like bad data
and gets parked
as one customer's problem.
For the record,
the word polygenelubricants
hashes to it.
So does a steady supply
of generated IDs.
The fix is one call.
Math.floorMod
of the hash and the node count.
It always lands
between zero and the count minus one,
for any input,
including the awkward one.
Or clear the sign bit
before you divide.
If you want abs
to refuse the impossible case,
Math.absExact
throws instead of lying.
Then write the test
with the value itself.
Integer.MIN_VALUE as the hash.
Expect a valid index.
The trap is not only in Java.
C calls the result undefined.
Rust wraps it in a release build.
The function was called abs,
so everyone trusted the name.
A name tells you what the author meant.
The edge of the type
tells you what it does.
– Serguey Asael Shinder
Top comments (0)