Skip to content

[DBSP] Fix incorrect hashing in UpsertHandle - #6755

Merged
mihaibudiu merged 1 commit into
feldera:mainfrom
mihaibudiu:issue6753
Jul 29, 2026
Merged

[DBSP] Fix incorrect hashing in UpsertHandle#6755
mihaibudiu merged 1 commit into
feldera:mainfrom
mihaibudiu:issue6753

Conversation

@mihaibudiu

Copy link
Copy Markdown
Contributor

Fixes #6753

Also, changed the default number of workers in the java tests to 3, this will hopefully catch this kind of bug sooner.

@mihaibudiu
mihaibudiu requested a review from ryzhyk July 29, 2026 18:59

@mythical-fred mythical-fred left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice bug fix. The default_hash() as u32 truncation was silently disagreeing with the u64 hash used everywhere else, and the disagreement only showed up when workers was not a power of two (which is why the existing 2-worker tests never tripped it).

Two improvements make this durable:

  • Widening HashFunc from u32 to u64 removes the hash-space mismatch by construction, not by hoping downstream operators truncate the same way.
  • dyn_shard_pairs before update_set (with the is_solo guard around mark_sharded) removes the "trust the collection handle's placement" contract; the input is now globally sharded on multihost, which was a latent hole for UpsertHandle on cluster deployments.

The new map_join_test_mt3 / set_join_test_mt3 regressions land the fix in the shape that would have caught the original bug (worker count 3 is the essential detail — the review comment on TestCase.java calls this out explicitly). Bumping the SQL compiler tests to 3 workers by default is exactly the right instinct: broken windows for hash-truncation bugs.

The new inline comments on distinct.rs (by_hash uses h % workers, not h.default_hash() % workers, so must not escape the region) and on both join.rs sites ("valid only because the join function copies the input key unchanged") are the kind of load-bearing invariant documentation this code was missing. Please keep them.

One nit: deleting the commented-out shard_vec block is a small win for readability, but the surrounding block comment ("We may want to uncomment...") ended up as an orphan */ in the diff? A quick re-read of the surrounding text before merge would confirm nothing dangles.

Signed off, no AI attribution.

@mihaibudiu
mihaibudiu requested a review from blp July 29, 2026 19:51
@mihaibudiu

Copy link
Copy Markdown
Contributor Author

@blp the analysis found a multi-host related bug, so I added you as a reviewer

@blp blp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only really looked at the particular part related to multihost.

Comment on lines +523 to +528
// UpsertHandle shards its inputs across workers on the current host
// only; on a multihost layout the updates must be re-sharded globally.
// `update_set` requires a sharded input and does not shard it itself.
if Runtime::runtime().is_none_or(|rt| rt.layout().is_solo()) {
sorted.mark_sharded();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd mark it as sharded across the local workers instead:

Suggested change
// UpsertHandle shards its inputs across workers on the current host
// only; on a multihost layout the updates must be re-sharded globally.
// `update_set` requires a sharded input and does not shard it itself.
if Runtime::runtime().is_none_or(|rt| rt.layout().is_solo()) {
sorted.mark_sharded();
}
sorted.mark_sharded_workers(rt.layout().local_workers());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blp, I assume you mean

        if let Some(rt) = Runtime::runtime() {
            sorted.mark_sharded_workers(rt.layout().local_workers());
        }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks right, I didn't actually try to compile it.

let factories_clone = factories.clone();

let sorted = input_stream
let sorted = input_stream.apply_owned(|mut upserts| {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should just get rid of add_input_set. Noone's using it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try to delete it and see if there are any problems

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #6757 for this, will follow-up in a separate PR.

Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 29, 2026
@mihaibudiu
mihaibudiu removed this pull request from the merge queue due to a manual request Jul 29, 2026
@mihaibudiu
mihaibudiu enabled auto-merge July 29, 2026 22:26
@mihaibudiu
mihaibudiu added this pull request to the merge queue Jul 29, 2026
Merged via the queue into feldera:main with commit f69fdb2 Jul 29, 2026
1 check passed
@mihaibudiu
mihaibudiu deleted the issue6753 branch July 29, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DBSP] Hash-join produces incorrect results when the number of workers is not a power of 2

4 participants