Grzegorz Liter created AVRO-4334:
------------------------------------

             Summary: Utf8.hashCode() changed silently in 1.12.1 
(AVRO-4060/AVRO-4061) — undocumented breaking change for any hash-based consumer
                 Key: AVRO-4334
                 URL: https://issues.apache.org/jira/browse/AVRO-4334
             Project: Apache Avro
          Issue Type: Bug
    Affects Versions: 1.12.1, 1.12.2
            Reporter: Grzegorz Liter


Utf8.hashCode() (and therefore GenericData.hashCode() / every Avro-generated 
SpecificRecord's hashCode() for any string field) changed behavior between 
1.12.0 and 1.12.1, introduced by:

- AVRO-4061 "Use Default Value of 1 For UTF8 Hash" — commit 
31221f0f9acbb43a947bd04d239fa4317bbf0119
- AVRO-4060 "Use JDK to Hash Byte Array in UTF8" — commit 
057053f86074ec854f132b491ef0429a0eb1f977

Before (1.12.0 and every earlier release, confirmed identical back through at 
least 1.11.3):
{quote}{{public int hashCode() {}}
{{    int h = this.hash;      // defaults to 0}}
{{    if (h == 0) {}}
{{        for (int i = 0; i < length; i++) {}}
{{            h = h * 31 + bytes[i];   // accumulator starts at 0}}
{{        }}}
{{        this.hash = h;}}
{{    }}}
{{    return h;}}
{{}}}{quote}
After (1.12.1+):

{{public Utf8() {}}
{{    this.bytes = EMPTY;}}
{{    this.hash = 1;           // seeded to 1}}
{{}}}
{{...}}
{{public int hashCode() {}}
{{    int h = this.hash;}}
{{    if (h == 0) {}}
{{        if (length > 7 && bytes.length == length) {}}
{{            h = Arrays.hashCode(bytes);   // JDK hasher, also starts at 1}}
{{        } else {}}
{{            h = 1;            // accumulator now starts at 1, not 0}}
{{            for (int i = 0; i < length; i++) {}}
{{                h = h * 31 + bytes[i];}}
{{            }}}
{{        }}}
{{        this.hash = h;}}
{{    }}}
{{    return h;}}
{{}}}

This produces a completely different hashCode() for every non-empty string 
compared to 1.12.0 and earlier. Both tickets were filed and reviewed purely as 
performance/caching fixes (AVRO-4061's stated motivation was that empty strings 
hashed to 0 and were therefore never cached; AVRO-4060 added a JDK-vectorized 
fast path). Neither ticket, their linked PRs, nor the 1.12.1 release notes 
mention that this changes hashCode() output for existing data, or call out any 
compatibility impact — AVRO-4060/AVRO-4061 aren't even named in the release 
notes; they're folded into the generic "Java: minor bugfixes" line.

Impact:

Avro-generated SpecificRecord classes delegate hashCode() to 
GenericData.hashCode() at runtime rather than baking it into generated code, so 
any consumer relying on hashCode stability across an upgrade is silently 
affected — no compile error, no exception, no warning.

We hit this in production: an Apache Flink job uses an Avro type as a .keyBy() 
partitioning key. Flink's KeyGroupRangeAssignment.assignToKeyGroup() calls 
key.hashCode() to decide both routing and which key-group a piece of 
already-written state lives under. Crossing 1.12.0→1.12.1 with no other change, 
every previously-written entry's recomputed key-group silently disagreed with 
where it was stored — either a hard IllegalArgumentException: Key group X is 
not in KeyGroupRange\{...} (State Processor API, which revalidates hashCode 
against stored location) or, worse, silent state loss/duplication in normal 
streaming (which never cross-validates against history). This corrupted the 
majority of one operator's keyed state before being root-caused.

Ask:

at minimum, document in the release notes/upgrade guide that hashCode() for 
Avro-generated types is not a stable contract across versions and changed in 
1.12.1, so downstream consumers doing hash-based routing/partitioning are aware 
to pin an explicit hashCode() override (as we now do) rather than discovering 
this via silent data corruption. Longer-term, consider whether 
Utf8/GenericData.hashCode()'s stability should itself be documented as 
guaranteed-or-not, so this doesn't recur on a future "trivial" performance 
tweak.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to