This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 8303cc4f02 IGNITE-19935 Allow combining individual colocation hashes 
(#2302)
8303cc4f02 is described below

commit 8303cc4f022451e932f65a2c6172112895cc1ce3
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Tue Jul 11 16:07:54 2023 +0300

    IGNITE-19935 Allow combining individual colocation hashes (#2302)
    
    Refactor hashing to change combined hash formula from `hash(col2, seed: 
hash(col1))` to `hashCombine(hash(col1), hash(col2))`.
    
    * This allows us to compute individual hash codes in any order, then 
combine them in correct order (according to colocation key)
    * Also this means we can combine hashing and serialization and avoid 
duplicate value extraction (especially for strings, which require array 
allocation to get the bytes)
    * Collisions: 125 per 1M entries (`Row(0..99, 0..99, 0..99)`), up from 118 
reported in [IGNITE-14769 
comments](https://issues.apache.org/jira/browse/IGNITE-14769)
    * Distribution:
    
![image](https://github.com/apache/ignite-3/assets/10922892/03059300-0faa-4d4d-bcaf-2ad92aa8de2b)
---
 .../ignite/client/PartitionAwarenessTest.java      | 359 +++++++++++----------
 .../ignite/internal/util/HashCalculator.java       | 206 ++++++++++--
 .../org/apache/ignite/internal/util/HashUtils.java |  52 ++-
 .../cpp/tests/client-test/compute_test.cpp         |   2 +-
 .../Apache.Ignite.Tests/Compute/ComputeTests.cs    |  18 +-
 .../Apache.Ignite.Tests/PartitionAwarenessTests.cs |  18 +-
 .../Proto/BinaryTuple/BinaryTupleBuilder.cs        |  37 +--
 .../Apache.Ignite/Internal/Proto/HashUtils.cs      |  58 ++--
 .../runner/app/client/ItThinClientComputeTest.java |   2 +-
 .../table/ColocationHashCalculationTest.java       |  67 +++-
 10 files changed, 541 insertions(+), 278 deletions(-)

diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/PartitionAwarenessTest.java
 
b/modules/client/src/test/java/org/apache/ignite/client/PartitionAwarenessTest.java
index 490f0b12c5..99da4e97ac 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/PartitionAwarenessTest.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/PartitionAwarenessTest.java
@@ -54,6 +54,13 @@ import org.junit.jupiter.params.provider.ValueSource;
  * Tests partition awareness.
  */
 public class PartitionAwarenessTest extends AbstractClientTest {
+    private static final String nodeKey0 = "server-2";
+    private static final String nodeKey1 = "server-2";
+
+    private static final String nodeKey2 = "server-1";
+
+    private static final String nodeKey3 = "server-2";
+
     private static TestServer testServer2;
 
     private static Ignite server2;
@@ -112,40 +119,40 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
     public void testGetTupleRoutesRequestToPrimaryNode() {
         RecordView<Tuple> recordView = defaultTable().recordView();
 
-        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0L)));
-        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 1L)));
-        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 2L)));
-        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 3L)));
+        assertOpOnNode(nodeKey0, "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0L)));
+        assertOpOnNode(nodeKey1, "get", x -> recordView.get(null, 
Tuple.create().set("ID", 1L)));
+        assertOpOnNode(nodeKey2, "get", x -> recordView.get(null, 
Tuple.create().set("ID", 2L)));
+        assertOpOnNode(nodeKey3, "get", x -> recordView.get(null, 
Tuple.create().set("ID", 3L)));
     }
 
     @Test
     public void testGetRecordRoutesRequestToPrimaryNode() {
         RecordView<PersonPojo> pojoView = 
defaultTable().recordView(Mapper.of(PersonPojo.class));
 
-        assertOpOnNode("server-1", "get", x -> pojoView.get(null, new 
PersonPojo(0L)));
-        assertOpOnNode("server-2", "get", x -> pojoView.get(null, new 
PersonPojo(1L)));
-        assertOpOnNode("server-1", "get", x -> pojoView.get(null, new 
PersonPojo(2L)));
-        assertOpOnNode("server-2", "get", x -> pojoView.get(null, new 
PersonPojo(3L)));
+        assertOpOnNode(nodeKey0, "get", x -> pojoView.get(null, new 
PersonPojo(0L)));
+        assertOpOnNode(nodeKey1, "get", x -> pojoView.get(null, new 
PersonPojo(1L)));
+        assertOpOnNode(nodeKey2, "get", x -> pojoView.get(null, new 
PersonPojo(2L)));
+        assertOpOnNode(nodeKey3, "get", x -> pojoView.get(null, new 
PersonPojo(3L)));
     }
 
     @Test
     public void testGetKeyValueRoutesRequestToPrimaryNode() {
         KeyValueView<Long, String> kvView = 
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
 
-        assertOpOnNode("server-1", "get", x -> kvView.get(null, 0L));
-        assertOpOnNode("server-2", "get", x -> kvView.get(null, 1L));
-        assertOpOnNode("server-1", "get", x -> kvView.get(null, 2L));
-        assertOpOnNode("server-2", "get", x -> kvView.get(null, 3L));
+        assertOpOnNode(nodeKey0, "get", x -> kvView.get(null, 0L));
+        assertOpOnNode(nodeKey1, "get", x -> kvView.get(null, 1L));
+        assertOpOnNode(nodeKey2, "get", x -> kvView.get(null, 2L));
+        assertOpOnNode(nodeKey3, "get", x -> kvView.get(null, 3L));
     }
 
     @Test
     public void testGetKeyValueBinaryRoutesRequestToPrimaryNode() {
         KeyValueView<Tuple, Tuple> kvView = defaultTable().keyValueView();
 
-        assertOpOnNode("server-1", "get", x -> kvView.get(null, 
Tuple.create().set("ID", 0L)));
-        assertOpOnNode("server-2", "get", x -> kvView.get(null, 
Tuple.create().set("ID", 1L)));
-        assertOpOnNode("server-1", "get", x -> kvView.get(null, 
Tuple.create().set("ID", 2L)));
-        assertOpOnNode("server-2", "get", x -> kvView.get(null, 
Tuple.create().set("ID", 3L)));
+        assertOpOnNode(nodeKey0, "get", x -> kvView.get(null, 
Tuple.create().set("ID", 0L)));
+        assertOpOnNode(nodeKey1, "get", x -> kvView.get(null, 
Tuple.create().set("ID", 1L)));
+        assertOpOnNode(nodeKey2, "get", x -> kvView.get(null, 
Tuple.create().set("ID", 2L)));
+        assertOpOnNode(nodeKey3, "get", x -> kvView.get(null, 
Tuple.create().set("ID", 3L)));
     }
 
     @Test
@@ -164,8 +171,8 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
         // Check default assignment.
         RecordView<Tuple> recordView = defaultTable().recordView();
 
-        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0L)));
-        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 1L)));
+        assertOpOnNode(nodeKey1, "get", x -> recordView.get(null, 
Tuple.create().set("ID", 1L)));
+        assertOpOnNode(nodeKey2, "get", x -> recordView.get(null, 
Tuple.create().set("ID", 2L)));
 
         // Update partition assignment.
         var assignments = new ArrayList<String>();
@@ -186,8 +193,8 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
         }
 
         // Check new assignment.
-        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0L)));
-        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 1L)));
+        assertOpOnNode(nodeKey2, "get", x -> recordView.get(null, 
Tuple.create().set("ID", 1L)));
+        assertOpOnNode(nodeKey1, "get", x -> recordView.get(null, 
Tuple.create().set("ID", 2L)));
     }
 
     @Test
@@ -198,20 +205,20 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
         assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0).set("COLO-1", "0")));
         assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 2).set("COLO-1", "0")));
         assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 3).set("COLO-1", "0")));
-        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 3).set("COLO-1", "2")));
+        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 3).set("COLO-1", "4")));
 
         // COLO-2 is set.
-        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0).set("COLO-1", "0").set("COLO-2", 1)));
-        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0).set("COLO-1", "0").set("COLO-2", 2)));
+        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0).set("COLO-1", "0").set("COLO-2", 1)));
+        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID", 0).set("COLO-1", "0").set("COLO-2", 8)));
     }
 
     @Test
     public void testCompositeKey() {
         RecordView<Tuple> recordView = 
table(FakeIgniteTables.TABLE_COMPOSITE_KEY).recordView();
 
-        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID1", 0).set("ID2", "0")));
-        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID1", 1).set("ID2", "0")));
-        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID1", 0).set("ID2", "1")));
+        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID1", 0).set("ID2", "0")));
+        assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID1", 1).set("ID2", "0")));
+        assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID1", 0).set("ID2", "1")));
         assertOpOnNode("server-1", "get", x -> recordView.get(null, 
Tuple.create().set("ID1", 1).set("ID2", "1")));
         assertOpOnNode("server-2", "get", x -> recordView.get(null, 
Tuple.create().set("ID1", 1).set("ID2", "2")));
     }
@@ -224,215 +231,215 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
         var t1 = new PersonPojo(0L);
         var t2 = new PersonPojo(1L);
 
-        assertOpOnNode("server-1", "insert", x -> pojoView.insert(null, t1));
-        assertOpOnNode("server-2", "insert", x -> pojoView.insert(null, t2));
+        assertOpOnNode(nodeKey0, "insert", x -> pojoView.insert(null, t1));
+        assertOpOnNode(nodeKey1, "insert", x -> pojoView.insert(null, t2));
 
-        assertOpOnNode("server-1", "insertAll", x -> pojoView.insertAll(null, 
List.of(t1)));
-        assertOpOnNode("server-2", "insertAll", x -> pojoView.insertAll(null, 
List.of(t2)));
+        assertOpOnNode(nodeKey0, "insertAll", x -> pojoView.insertAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey1, "insertAll", x -> pojoView.insertAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "upsert", x -> pojoView.upsert(null, t1));
-        assertOpOnNode("server-2", "upsert", x -> pojoView.upsert(null, t2));
+        assertOpOnNode(nodeKey0, "upsert", x -> pojoView.upsert(null, t1));
+        assertOpOnNode(nodeKey1, "upsert", x -> pojoView.upsert(null, t2));
 
-        assertOpOnNode("server-1", "upsertAll", x -> pojoView.upsertAll(null, 
List.of(t1)));
-        assertOpOnNode("server-2", "upsertAll", x -> pojoView.upsertAll(null, 
List.of(t2)));
+        assertOpOnNode(nodeKey0, "upsertAll", x -> pojoView.upsertAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> pojoView.upsertAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "get", x -> pojoView.get(null, t1));
-        assertOpOnNode("server-2", "get", x -> pojoView.get(null, t2));
+        assertOpOnNode(nodeKey0, "get", x -> pojoView.get(null, t1));
+        assertOpOnNode(nodeKey1, "get", x -> pojoView.get(null, t2));
 
-        assertOpOnNode("server-1", "getAll", x -> pojoView.getAll(null, 
List.of(t1)));
-        assertOpOnNode("server-2", "getAll", x -> pojoView.getAll(null, 
List.of(t2)));
+        assertOpOnNode(nodeKey0, "getAll", x -> pojoView.getAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey1, "getAll", x -> pojoView.getAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "getAndUpsert", x -> 
pojoView.getAndUpsert(null, t1));
-        assertOpOnNode("server-2", "getAndUpsert", x -> 
pojoView.getAndUpsert(null, t2));
+        assertOpOnNode(nodeKey0, "getAndUpsert", x -> 
pojoView.getAndUpsert(null, t1));
+        assertOpOnNode(nodeKey1, "getAndUpsert", x -> 
pojoView.getAndUpsert(null, t2));
 
-        assertOpOnNode("server-1", "getAndReplace", x -> 
pojoView.getAndReplace(null, t1));
-        assertOpOnNode("server-2", "getAndReplace", x -> 
pojoView.getAndReplace(null, t2));
+        assertOpOnNode(nodeKey0, "getAndReplace", x -> 
pojoView.getAndReplace(null, t1));
+        assertOpOnNode(nodeKey1, "getAndReplace", x -> 
pojoView.getAndReplace(null, t2));
 
-        assertOpOnNode("server-1", "getAndDelete", x -> 
pojoView.getAndDelete(null, t1));
-        assertOpOnNode("server-2", "getAndDelete", x -> 
pojoView.getAndDelete(null, t2));
+        assertOpOnNode(nodeKey0, "getAndDelete", x -> 
pojoView.getAndDelete(null, t1));
+        assertOpOnNode(nodeKey1, "getAndDelete", x -> 
pojoView.getAndDelete(null, t2));
 
-        assertOpOnNode("server-1", "replace", x -> pojoView.replace(null, t1));
-        assertOpOnNode("server-2", "replace", x -> pojoView.replace(null, t2));
+        assertOpOnNode(nodeKey0, "replace", x -> pojoView.replace(null, t1));
+        assertOpOnNode(nodeKey1, "replace", x -> pojoView.replace(null, t2));
 
-        assertOpOnNode("server-1", "replace", x -> pojoView.replace(null, t1, 
t1));
-        assertOpOnNode("server-2", "replace", x -> pojoView.replace(null, t2, 
t2));
+        assertOpOnNode(nodeKey0, "replace", x -> pojoView.replace(null, t1, 
t1));
+        assertOpOnNode(nodeKey1, "replace", x -> pojoView.replace(null, t2, 
t2));
 
-        assertOpOnNode("server-1", "delete", x -> pojoView.delete(null, t1));
-        assertOpOnNode("server-2", "delete", x -> pojoView.delete(null, t2));
+        assertOpOnNode(nodeKey0, "delete", x -> pojoView.delete(null, t1));
+        assertOpOnNode(nodeKey1, "delete", x -> pojoView.delete(null, t2));
 
-        assertOpOnNode("server-1", "deleteExact", x -> 
pojoView.deleteExact(null, t1));
-        assertOpOnNode("server-2", "deleteExact", x -> 
pojoView.deleteExact(null, t2));
+        assertOpOnNode(nodeKey0, "deleteExact", x -> 
pojoView.deleteExact(null, t1));
+        assertOpOnNode(nodeKey1, "deleteExact", x -> 
pojoView.deleteExact(null, t2));
 
-        assertOpOnNode("server-1", "deleteAll", x -> pojoView.deleteAll(null, 
List.of(t1)));
-        assertOpOnNode("server-2", "deleteAll", x -> pojoView.deleteAll(null, 
List.of(t2)));
+        assertOpOnNode(nodeKey0, "deleteAll", x -> pojoView.deleteAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey1, "deleteAll", x -> pojoView.deleteAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "deleteAllExact", x -> 
pojoView.deleteAllExact(null, List.of(t1)));
-        assertOpOnNode("server-2", "deleteAllExact", x -> 
pojoView.deleteAllExact(null, List.of(t2)));
+        assertOpOnNode(nodeKey0, "deleteAllExact", x -> 
pojoView.deleteAllExact(null, List.of(t1)));
+        assertOpOnNode(nodeKey1, "deleteAllExact", x -> 
pojoView.deleteAllExact(null, List.of(t2)));
     }
 
     @Test
     public void testAllRecordBinaryViewOperations() {
         RecordView<Tuple> recordView = defaultTable().recordView();
 
-        Tuple t1 = Tuple.create().set("ID", 0L);
-        Tuple t2 = Tuple.create().set("ID", 1L);
+        Tuple t1 = Tuple.create().set("ID", 1L);
+        Tuple t2 = Tuple.create().set("ID", 2L);
 
-        assertOpOnNode("server-1", "insert", x -> recordView.insert(null, t1));
-        assertOpOnNode("server-2", "insert", x -> recordView.insert(null, t2));
+        assertOpOnNode(nodeKey1, "insert", x -> recordView.insert(null, t1));
+        assertOpOnNode(nodeKey2, "insert", x -> recordView.insert(null, t2));
 
-        assertOpOnNode("server-1", "insertAll", x -> 
recordView.insertAll(null, List.of(t1)));
-        assertOpOnNode("server-2", "insertAll", x -> 
recordView.insertAll(null, List.of(t2)));
+        assertOpOnNode(nodeKey1, "insertAll", x -> recordView.insertAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey2, "insertAll", x -> recordView.insertAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "upsert", x -> recordView.upsert(null, t1));
-        assertOpOnNode("server-2", "upsert", x -> recordView.upsert(null, t2));
+        assertOpOnNode(nodeKey1, "upsert", x -> recordView.upsert(null, t1));
+        assertOpOnNode(nodeKey2, "upsert", x -> recordView.upsert(null, t2));
 
-        assertOpOnNode("server-1", "upsertAll", x -> 
recordView.upsertAll(null, List.of(t1)));
-        assertOpOnNode("server-2", "upsertAll", x -> 
recordView.upsertAll(null, List.of(t2)));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> recordView.upsertAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> recordView.upsertAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "get", x -> recordView.get(null, t1));
-        assertOpOnNode("server-2", "get", x -> recordView.get(null, t2));
+        assertOpOnNode(nodeKey1, "get", x -> recordView.get(null, t1));
+        assertOpOnNode(nodeKey2, "get", x -> recordView.get(null, t2));
 
-        assertOpOnNode("server-1", "getAll", x -> recordView.getAll(null, 
List.of(t1)));
-        assertOpOnNode("server-2", "getAll", x -> recordView.getAll(null, 
List.of(t2)));
+        assertOpOnNode(nodeKey1, "getAll", x -> recordView.getAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey2, "getAll", x -> recordView.getAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "getAndUpsert", x -> 
recordView.getAndUpsert(null, t1));
-        assertOpOnNode("server-2", "getAndUpsert", x -> 
recordView.getAndUpsert(null, t2));
+        assertOpOnNode(nodeKey1, "getAndUpsert", x -> 
recordView.getAndUpsert(null, t1));
+        assertOpOnNode(nodeKey2, "getAndUpsert", x -> 
recordView.getAndUpsert(null, t2));
 
-        assertOpOnNode("server-1", "getAndReplace", x -> 
recordView.getAndReplace(null, t1));
-        assertOpOnNode("server-2", "getAndReplace", x -> 
recordView.getAndReplace(null, t2));
+        assertOpOnNode(nodeKey1, "getAndReplace", x -> 
recordView.getAndReplace(null, t1));
+        assertOpOnNode(nodeKey2, "getAndReplace", x -> 
recordView.getAndReplace(null, t2));
 
-        assertOpOnNode("server-1", "getAndDelete", x -> 
recordView.getAndDelete(null, t1));
-        assertOpOnNode("server-2", "getAndDelete", x -> 
recordView.getAndDelete(null, t2));
+        assertOpOnNode(nodeKey1, "getAndDelete", x -> 
recordView.getAndDelete(null, t1));
+        assertOpOnNode(nodeKey2, "getAndDelete", x -> 
recordView.getAndDelete(null, t2));
 
-        assertOpOnNode("server-1", "replace", x -> recordView.replace(null, 
t1));
-        assertOpOnNode("server-2", "replace", x -> recordView.replace(null, 
t2));
+        assertOpOnNode(nodeKey1, "replace", x -> recordView.replace(null, t1));
+        assertOpOnNode(nodeKey2, "replace", x -> recordView.replace(null, t2));
 
-        assertOpOnNode("server-1", "replace", x -> recordView.replace(null, 
t1, t1));
-        assertOpOnNode("server-2", "replace", x -> recordView.replace(null, 
t2, t2));
+        assertOpOnNode(nodeKey1, "replace", x -> recordView.replace(null, t1, 
t1));
+        assertOpOnNode(nodeKey2, "replace", x -> recordView.replace(null, t2, 
t2));
 
-        assertOpOnNode("server-1", "delete", x -> recordView.delete(null, t1));
-        assertOpOnNode("server-2", "delete", x -> recordView.delete(null, t2));
+        assertOpOnNode(nodeKey1, "delete", x -> recordView.delete(null, t1));
+        assertOpOnNode(nodeKey2, "delete", x -> recordView.delete(null, t2));
 
-        assertOpOnNode("server-1", "deleteExact", x -> 
recordView.deleteExact(null, t1));
-        assertOpOnNode("server-2", "deleteExact", x -> 
recordView.deleteExact(null, t2));
+        assertOpOnNode(nodeKey1, "deleteExact", x -> 
recordView.deleteExact(null, t1));
+        assertOpOnNode(nodeKey2, "deleteExact", x -> 
recordView.deleteExact(null, t2));
 
-        assertOpOnNode("server-1", "deleteAll", x -> 
recordView.deleteAll(null, List.of(t1)));
-        assertOpOnNode("server-2", "deleteAll", x -> 
recordView.deleteAll(null, List.of(t2)));
+        assertOpOnNode(nodeKey1, "deleteAll", x -> recordView.deleteAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey2, "deleteAll", x -> recordView.deleteAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "deleteAllExact", x -> 
recordView.deleteAllExact(null, List.of(t1)));
-        assertOpOnNode("server-2", "deleteAllExact", x -> 
recordView.deleteAllExact(null, List.of(t2)));
+        assertOpOnNode(nodeKey1, "deleteAllExact", x -> 
recordView.deleteAllExact(null, List.of(t1)));
+        assertOpOnNode(nodeKey2, "deleteAllExact", x -> 
recordView.deleteAllExact(null, List.of(t2)));
     }
 
     @Test
     public void testAllKeyValueViewOperations() {
         KeyValueView<Long, String> kvView = 
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
 
-        var k1 = 0L;
-        var k2 = 1L;
+        var k1 = 1L;
+        var k2 = 2L;
         var v = "v";
 
-        assertOpOnNode("server-1", "insert", x -> kvView.putIfAbsent(null, k1, 
v));
-        assertOpOnNode("server-2", "insert", x -> kvView.putIfAbsent(null, k2, 
v));
+        assertOpOnNode(nodeKey1, "insert", x -> kvView.putIfAbsent(null, k1, 
v));
+        assertOpOnNode(nodeKey2, "insert", x -> kvView.putIfAbsent(null, k2, 
v));
 
-        assertOpOnNode("server-1", "upsert", x -> kvView.put(null, k1, v));
-        assertOpOnNode("server-2", "upsert", x -> kvView.put(null, k2, v));
+        assertOpOnNode(nodeKey1, "upsert", x -> kvView.put(null, k1, v));
+        assertOpOnNode(nodeKey2, "upsert", x -> kvView.put(null, k2, v));
 
-        assertOpOnNode("server-1", "upsertAll", x -> kvView.putAll(null, 
Map.of(k1, v)));
-        assertOpOnNode("server-2", "upsertAll", x -> kvView.putAll(null, 
Map.of(k2, v)));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> kvView.putAll(null, 
Map.of(k1, v)));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> kvView.putAll(null, 
Map.of(k2, v)));
 
-        assertOpOnNode("server-1", "get", x -> kvView.get(null, k1));
-        assertOpOnNode("server-2", "get", x -> kvView.get(null, k2));
+        assertOpOnNode(nodeKey1, "get", x -> kvView.get(null, k1));
+        assertOpOnNode(nodeKey2, "get", x -> kvView.get(null, k2));
 
-        assertOpOnNode("server-1", "get", x -> kvView.contains(null, k1));
-        assertOpOnNode("server-2", "get", x -> kvView.contains(null, k2));
+        assertOpOnNode(nodeKey1, "get", x -> kvView.contains(null, k1));
+        assertOpOnNode(nodeKey2, "get", x -> kvView.contains(null, k2));
 
-        assertOpOnNode("server-1", "getAll", x -> kvView.getAll(null, 
List.of(k1)));
-        assertOpOnNode("server-2", "getAll", x -> kvView.getAll(null, 
List.of(k2)));
+        assertOpOnNode(nodeKey1, "getAll", x -> kvView.getAll(null, 
List.of(k1)));
+        assertOpOnNode(nodeKey2, "getAll", x -> kvView.getAll(null, 
List.of(k2)));
 
-        assertOpOnNode("server-1", "getAndUpsert", x -> kvView.getAndPut(null, 
k1, v));
-        assertOpOnNode("server-2", "getAndUpsert", x -> kvView.getAndPut(null, 
k2, v));
+        assertOpOnNode(nodeKey1, "getAndUpsert", x -> kvView.getAndPut(null, 
k1, v));
+        assertOpOnNode(nodeKey2, "getAndUpsert", x -> kvView.getAndPut(null, 
k2, v));
 
-        assertOpOnNode("server-1", "getAndReplace", x -> 
kvView.getAndReplace(null, k1, v));
-        assertOpOnNode("server-2", "getAndReplace", x -> 
kvView.getAndReplace(null, k2, v));
+        assertOpOnNode(nodeKey1, "getAndReplace", x -> 
kvView.getAndReplace(null, k1, v));
+        assertOpOnNode(nodeKey2, "getAndReplace", x -> 
kvView.getAndReplace(null, k2, v));
 
-        assertOpOnNode("server-1", "getAndDelete", x -> 
kvView.getAndRemove(null, k1));
-        assertOpOnNode("server-2", "getAndDelete", x -> 
kvView.getAndRemove(null, k2));
+        assertOpOnNode(nodeKey1, "getAndDelete", x -> 
kvView.getAndRemove(null, k1));
+        assertOpOnNode(nodeKey2, "getAndDelete", x -> 
kvView.getAndRemove(null, k2));
 
-        assertOpOnNode("server-1", "replace", x -> kvView.replace(null, k1, 
v));
-        assertOpOnNode("server-2", "replace", x -> kvView.replace(null, k2, 
v));
+        assertOpOnNode(nodeKey1, "replace", x -> kvView.replace(null, k1, v));
+        assertOpOnNode(nodeKey2, "replace", x -> kvView.replace(null, k2, v));
 
-        assertOpOnNode("server-1", "replace", x -> kvView.replace(null, k1, v, 
v));
-        assertOpOnNode("server-2", "replace", x -> kvView.replace(null, k2, v, 
v));
+        assertOpOnNode(nodeKey1, "replace", x -> kvView.replace(null, k1, v, 
v));
+        assertOpOnNode(nodeKey2, "replace", x -> kvView.replace(null, k2, v, 
v));
 
-        assertOpOnNode("server-1", "delete", x -> kvView.remove(null, k1));
-        assertOpOnNode("server-2", "delete", x -> kvView.remove(null, k2));
+        assertOpOnNode(nodeKey1, "delete", x -> kvView.remove(null, k1));
+        assertOpOnNode(nodeKey2, "delete", x -> kvView.remove(null, k2));
 
-        assertOpOnNode("server-1", "deleteExact", x -> kvView.remove(null, k1, 
v));
-        assertOpOnNode("server-2", "deleteExact", x -> kvView.remove(null, k2, 
v));
+        assertOpOnNode(nodeKey1, "deleteExact", x -> kvView.remove(null, k1, 
v));
+        assertOpOnNode(nodeKey2, "deleteExact", x -> kvView.remove(null, k2, 
v));
 
-        assertOpOnNode("server-1", "deleteAll", x -> kvView.removeAll(null, 
List.of(k1)));
-        assertOpOnNode("server-2", "deleteAll", x -> kvView.removeAll(null, 
List.of(k2)));
+        assertOpOnNode(nodeKey1, "deleteAll", x -> kvView.removeAll(null, 
List.of(k1)));
+        assertOpOnNode(nodeKey2, "deleteAll", x -> kvView.removeAll(null, 
List.of(k2)));
     }
 
     @Test
     public void testAllKeyValueBinaryViewOperations() {
         KeyValueView<Tuple, Tuple> kvView = defaultTable().keyValueView();
 
-        Tuple t1 = Tuple.create().set("ID", 0L);
-        Tuple t2 = Tuple.create().set("ID", 1L);
+        Tuple t1 = Tuple.create().set("ID", 1L);
+        Tuple t2 = Tuple.create().set("ID", 2L);
 
-        assertOpOnNode("server-1", "insert", x -> kvView.putIfAbsent(null, t1, 
t1));
-        assertOpOnNode("server-2", "insert", x -> kvView.putIfAbsent(null, t2, 
t2));
+        assertOpOnNode(nodeKey1, "insert", x -> kvView.putIfAbsent(null, t1, 
t1));
+        assertOpOnNode(nodeKey2, "insert", x -> kvView.putIfAbsent(null, t2, 
t2));
 
-        assertOpOnNode("server-1", "upsert", x -> kvView.put(null, t1, t1));
-        assertOpOnNode("server-2", "upsert", x -> kvView.put(null, t2, t2));
+        assertOpOnNode(nodeKey1, "upsert", x -> kvView.put(null, t1, t1));
+        assertOpOnNode(nodeKey2, "upsert", x -> kvView.put(null, t2, t2));
 
-        assertOpOnNode("server-1", "upsertAll", x -> kvView.putAll(null, 
Map.of(t1, t1)));
-        assertOpOnNode("server-2", "upsertAll", x -> kvView.putAll(null, 
Map.of(t2, t2)));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> kvView.putAll(null, 
Map.of(t1, t1)));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> kvView.putAll(null, 
Map.of(t2, t2)));
 
-        assertOpOnNode("server-1", "get", x -> kvView.get(null, t1));
-        assertOpOnNode("server-2", "get", x -> kvView.get(null, t2));
+        assertOpOnNode(nodeKey1, "get", x -> kvView.get(null, t1));
+        assertOpOnNode(nodeKey2, "get", x -> kvView.get(null, t2));
 
-        assertOpOnNode("server-1", "get", x -> kvView.contains(null, t1));
-        assertOpOnNode("server-2", "get", x -> kvView.contains(null, t2));
+        assertOpOnNode(nodeKey1, "get", x -> kvView.contains(null, t1));
+        assertOpOnNode(nodeKey2, "get", x -> kvView.contains(null, t2));
 
-        assertOpOnNode("server-1", "getAll", x -> kvView.getAll(null, 
List.of(t1)));
-        assertOpOnNode("server-2", "getAll", x -> kvView.getAll(null, 
List.of(t2)));
+        assertOpOnNode(nodeKey1, "getAll", x -> kvView.getAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey2, "getAll", x -> kvView.getAll(null, 
List.of(t2)));
 
-        assertOpOnNode("server-1", "getAndUpsert", x -> kvView.getAndPut(null, 
t1, t1));
-        assertOpOnNode("server-2", "getAndUpsert", x -> kvView.getAndPut(null, 
t2, t2));
+        assertOpOnNode(nodeKey1, "getAndUpsert", x -> kvView.getAndPut(null, 
t1, t1));
+        assertOpOnNode(nodeKey2, "getAndUpsert", x -> kvView.getAndPut(null, 
t2, t2));
 
-        assertOpOnNode("server-1", "getAndReplace", x -> 
kvView.getAndReplace(null, t1, t1));
-        assertOpOnNode("server-2", "getAndReplace", x -> 
kvView.getAndReplace(null, t2, t2));
+        assertOpOnNode(nodeKey1, "getAndReplace", x -> 
kvView.getAndReplace(null, t1, t1));
+        assertOpOnNode(nodeKey2, "getAndReplace", x -> 
kvView.getAndReplace(null, t2, t2));
 
-        assertOpOnNode("server-1", "getAndDelete", x -> 
kvView.getAndRemove(null, t1));
-        assertOpOnNode("server-2", "getAndDelete", x -> 
kvView.getAndRemove(null, t2));
+        assertOpOnNode(nodeKey1, "getAndDelete", x -> 
kvView.getAndRemove(null, t1));
+        assertOpOnNode(nodeKey2, "getAndDelete", x -> 
kvView.getAndRemove(null, t2));
 
-        assertOpOnNode("server-1", "replace", x -> kvView.replace(null, t1, 
t1));
-        assertOpOnNode("server-2", "replace", x -> kvView.replace(null, t2, 
t2));
+        assertOpOnNode(nodeKey1, "replace", x -> kvView.replace(null, t1, t1));
+        assertOpOnNode(nodeKey2, "replace", x -> kvView.replace(null, t2, t2));
 
-        assertOpOnNode("server-1", "replace", x -> kvView.replace(null, t1, 
t1, t1));
-        assertOpOnNode("server-2", "replace", x -> kvView.replace(null, t2, 
t2, t2));
+        assertOpOnNode(nodeKey1, "replace", x -> kvView.replace(null, t1, t1, 
t1));
+        assertOpOnNode(nodeKey2, "replace", x -> kvView.replace(null, t2, t2, 
t2));
 
-        assertOpOnNode("server-1", "delete", x -> kvView.remove(null, t1));
-        assertOpOnNode("server-2", "delete", x -> kvView.remove(null, t2));
+        assertOpOnNode(nodeKey1, "delete", x -> kvView.remove(null, t1));
+        assertOpOnNode(nodeKey2, "delete", x -> kvView.remove(null, t2));
 
-        assertOpOnNode("server-1", "deleteExact", x -> kvView.remove(null, t1, 
t1));
-        assertOpOnNode("server-2", "deleteExact", x -> kvView.remove(null, t2, 
t2));
+        assertOpOnNode(nodeKey1, "deleteExact", x -> kvView.remove(null, t1, 
t1));
+        assertOpOnNode(nodeKey2, "deleteExact", x -> kvView.remove(null, t2, 
t2));
 
-        assertOpOnNode("server-1", "deleteAll", x -> kvView.removeAll(null, 
List.of(t1)));
-        assertOpOnNode("server-2", "deleteAll", x -> kvView.removeAll(null, 
List.of(t2)));
+        assertOpOnNode(nodeKey1, "deleteAll", x -> kvView.removeAll(null, 
List.of(t1)));
+        assertOpOnNode(nodeKey2, "deleteAll", x -> kvView.removeAll(null, 
List.of(t2)));
     }
 
     @Test
     public void testExecuteColocatedTupleKeyRoutesRequestToPrimaryNode() {
         Table table = defaultTable();
 
-        Tuple t1 = Tuple.create().set("ID", 0L);
-        Tuple t2 = Tuple.create().set("ID", 1L);
+        Tuple t1 = Tuple.create().set("ID", 1L);
+        Tuple t2 = Tuple.create().set("ID", 2L);
 
-        assertEquals("server-1", compute().executeColocated(table.name(), t1, 
List.of(), "job").join());
-        assertEquals("server-2", compute().executeColocated(table.name(), t2, 
List.of(), "job").join());
+        assertEquals(nodeKey1, compute().executeColocated(table.name(), t1, 
List.of(), "job").join());
+        assertEquals(nodeKey2, compute().executeColocated(table.name(), t2, 
List.of(), "job").join());
     }
 
     @Test
@@ -440,8 +447,8 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
         var mapper = Mapper.of(Long.class);
         Table table = defaultTable();
 
-        assertEquals("server-1", compute().executeColocated(table.name(), 0L, 
mapper, List.of(), "job").join());
-        assertEquals("server-2", compute().executeColocated(table.name(), 1L, 
mapper, List.of(), "job").join());
+        assertEquals(nodeKey1, compute().executeColocated(table.name(), 1L, 
mapper, List.of(), "job").join());
+        assertEquals(nodeKey2, compute().executeColocated(table.name(), 2L, 
mapper, List.of(), "job").join());
     }
 
     @Test
@@ -456,10 +463,10 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
             fut.join();
         };
 
-        assertOpOnNode("server-1", "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 0L)));
-        assertOpOnNode("server-2", "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 1L)));
-        assertOpOnNode("server-1", "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 2L)));
-        assertOpOnNode("server-2", "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 3L)));
+        assertOpOnNode(nodeKey0, "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 0L)));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 1L)));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 2L)));
+        assertOpOnNode(nodeKey3, "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 3L)));
     }
 
     @Test
@@ -474,10 +481,10 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
             fut.join();
         };
 
-        assertOpOnNode("server-1", "upsertAll", x -> stream.accept(new 
PersonPojo(0L)));
-        assertOpOnNode("server-2", "upsertAll", x -> stream.accept(new 
PersonPojo(1L)));
-        assertOpOnNode("server-1", "upsertAll", x -> stream.accept(new 
PersonPojo(2L)));
-        assertOpOnNode("server-2", "upsertAll", x -> stream.accept(new 
PersonPojo(3L)));
+        assertOpOnNode(nodeKey0, "upsertAll", x -> stream.accept(new 
PersonPojo(0L)));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> stream.accept(new 
PersonPojo(1L)));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> stream.accept(new 
PersonPojo(2L)));
+        assertOpOnNode(nodeKey3, "upsertAll", x -> stream.accept(new 
PersonPojo(3L)));
     }
 
     @Test
@@ -492,10 +499,10 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
             fut.join();
         };
 
-        assertOpOnNode("server-1", "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 0L)));
-        assertOpOnNode("server-2", "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 1L)));
-        assertOpOnNode("server-1", "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 2L)));
-        assertOpOnNode("server-2", "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 3L)));
+        assertOpOnNode(nodeKey0, "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 0L)));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 1L)));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 2L)));
+        assertOpOnNode(nodeKey3, "upsertAll", x -> 
stream.accept(Tuple.create().set("ID", 3L)));
     }
 
     @Test
@@ -510,10 +517,10 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
             fut.join();
         };
 
-        assertOpOnNode("server-1", "upsertAll", x -> stream.accept(0L));
-        assertOpOnNode("server-2", "upsertAll", x -> stream.accept(1L));
-        assertOpOnNode("server-1", "upsertAll", x -> stream.accept(2L));
-        assertOpOnNode("server-2", "upsertAll", x -> stream.accept(3L));
+        assertOpOnNode(nodeKey0, "upsertAll", x -> stream.accept(0L));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> stream.accept(1L));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> stream.accept(2L));
+        assertOpOnNode(nodeKey3, "upsertAll", x -> stream.accept(3L));
     }
 
     @Test
@@ -537,8 +544,8 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
             }
         };
 
-        assertOpOnNode("server-1", "upsertAll", x -> submit.accept(0L));
-        assertOpOnNode("server-2", "upsertAll", x -> submit.accept(1L));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> submit.accept(1L));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> submit.accept(2L));
 
         // Update partition assignment.
         var assignments = new ArrayList<String>();
@@ -550,16 +557,16 @@ public class PartitionAwarenessTest extends 
AbstractClientTest {
 
         // Send some batches so that the client receives updated assignment.
         lastOpServerName = null;
-        submit.accept(0L);
+        submit.accept(1L);
         assertTrue(IgniteTestUtils.waitForCondition(() -> lastOpServerName != 
null, 1000));
 
         lastOpServerName = null;
-        submit.accept(1L);
+        submit.accept(2L);
         assertTrue(IgniteTestUtils.waitForCondition(() -> lastOpServerName != 
null, 1000));
 
         // Check updated assignment.
-        assertOpOnNode("server-2", "upsertAll", x -> submit.accept(0L));
-        assertOpOnNode("server-1", "upsertAll", x -> submit.accept(1L));
+        assertOpOnNode(nodeKey2, "upsertAll", x -> submit.accept(1L));
+        assertOpOnNode(nodeKey1, "upsertAll", x -> submit.accept(2L));
 
         publisher.close();
         fut.join();
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/util/HashCalculator.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/util/HashCalculator.java
index 871ecb55a5..8657cd4f00 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/util/HashCalculator.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/util/HashCalculator.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.util;
 
+import static org.apache.ignite.internal.util.HashUtils.combine;
+
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.RoundingMode;
@@ -98,7 +100,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendByte(byte v) {
-        hash = HashUtils.hash32(v, hash);
+        hash = combine(hash, hashByte(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashByte(byte v) {
+        return HashUtils.hash32(v);
     }
 
     /**
@@ -107,7 +118,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendShort(short v) {
-        hash = HashUtils.hash32(v, hash);
+        hash = combine(hash, hashShort(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashShort(short v) {
+        return HashUtils.hash32(v);
     }
 
     /**
@@ -116,7 +136,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendInt(int v) {
-        hash = HashUtils.hash32(v, hash);
+        hash = combine(hash, hashInt(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashInt(int v) {
+        return HashUtils.hash32(v);
     }
 
     /**
@@ -125,7 +154,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendLong(long v) {
-        hash = HashUtils.hash32(v, hash);
+        hash = combine(hash, hashLong(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashLong(long v) {
+        return HashUtils.hash32(v);
     }
 
     /**
@@ -134,7 +172,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendFloat(float v) {
-        appendInt(Float.floatToRawIntBits(v));
+        hash = combine(hash, hashFloat(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashFloat(float v) {
+        return HashUtils.hash32(Float.floatToRawIntBits(v));
     }
 
     /**
@@ -143,7 +190,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendDouble(double v) {
-        appendLong(Double.doubleToRawLongBits(v));
+        hash = combine(hash, hashDouble(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashDouble(double v) {
+        return HashUtils.hash32(Double.doubleToRawLongBits(v));
     }
 
     /**
@@ -152,7 +208,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendDecimal(BigDecimal v, int columnScale) {
-        appendBytes(v.setScale(columnScale, 
RoundingMode.HALF_UP).unscaledValue().toByteArray());
+        hash = combine(hash, hashDecimal(v, columnScale));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashDecimal(BigDecimal v, int columnScale) {
+        return hashBytes(v.setScale(columnScale, 
RoundingMode.HALF_UP).unscaledValue().toByteArray());
     }
 
     /**
@@ -161,7 +226,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendNumber(BigInteger v) {
-        appendBytes(v.toByteArray());
+        hash = combine(hash, hashNumber(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashNumber(BigInteger v) {
+        return hashBytes(v.toByteArray());
     }
 
     /**
@@ -170,8 +244,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendUuid(UUID v) {
-        appendLong(v.getMostSignificantBits());
-        appendLong(v.getLeastSignificantBits());
+        hash = combine(hash, hashUuid(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashUuid(UUID v) {
+        return HashUtils.hash32(v.getLeastSignificantBits(), 
HashUtils.hash32(v.getMostSignificantBits()));
     }
 
     /**
@@ -180,7 +262,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendString(String v) {
-        appendBytes(v.getBytes(StandardCharsets.UTF_8));
+        hash = combine(hash, hashString(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashString(String v) {
+        return hashBytes(v.getBytes(StandardCharsets.UTF_8));
     }
 
     /**
@@ -189,7 +280,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendBytes(byte[] v) {
-        hash = HashUtils.hash32(v, 0, v.length, hash);
+        hash = combine(hash, hashBytes(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashBytes(byte[] v) {
+        return HashUtils.hash32(v);
     }
 
     /**
@@ -198,7 +298,16 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendBitmask(BitSet v) {
-        appendBytes(v.toByteArray());
+        hash = combine(hash, hashBitmask(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashBitmask(BitSet v) {
+        return hashBytes(v.toByteArray());
     }
 
     /**
@@ -207,9 +316,18 @@ public class HashCalculator {
      * @param v Value to update hash.
      */
     public void appendDate(LocalDate v) {
-        appendLong(v.getYear());
-        appendLong(v.getMonthValue());
-        appendLong(v.getDayOfMonth());
+        hash = combine(hash, hashDate(v));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashDate(LocalDate v) {
+        return HashUtils.hash32(v.getDayOfMonth(),
+                HashUtils.hash32(v.getMonthValue(),
+                        HashUtils.hash32(v.getYear())));
     }
 
     /**
@@ -219,10 +337,19 @@ public class HashCalculator {
      * @param precision Precision.
      */
     public void appendTime(LocalTime v, int precision) {
-        appendLong(v.getHour());
-        appendLong(v.getMinute());
-        appendLong(v.getSecond());
-        appendLong(TemporalTypeUtils.normalizeNanos(v.getNano(), precision));
+        hash = combine(hash, hashTime(v, precision));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashTime(LocalTime v, int precision) {
+        int hourHash = HashUtils.hash32(v.getHour());
+        int minuteHash = HashUtils.hash32(v.getMinute(), hourHash);
+        int secondHash = HashUtils.hash32(v.getSecond(), minuteHash);
+        return HashUtils.hash32(TemporalTypeUtils.normalizeNanos(v.getNano(), 
precision), secondHash);
     }
 
     /**
@@ -232,8 +359,16 @@ public class HashCalculator {
      * @param precision Precision.
      */
     public void appendDateTime(LocalDateTime v, int precision) {
-        appendDate(v.toLocalDate());
-        appendTime(v.toLocalTime(), precision);
+        hash = combine(hash, hashDateTime(v, precision));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashDateTime(LocalDateTime v, int precision) {
+        return combine(hashDate(v.toLocalDate()), hashTime(v.toLocalTime(), 
precision));
     }
 
     /**
@@ -243,8 +378,31 @@ public class HashCalculator {
      * @param precision Precision.
      */
     public void appendTimestamp(Instant v, int precision) {
-        appendLong(v.getEpochSecond());
-        appendLong(TemporalTypeUtils.normalizeNanos(v.getNano(), precision));
+        hash = combine(hash, hashTimestamp(v, precision));
+    }
+
+    /**
+     * Get value hash.
+     *
+     * @param v Value to hash.
+     */
+    public static int hashTimestamp(Instant v, int precision) {
+        return HashUtils.hash32(TemporalTypeUtils.normalizeNanos(v.getNano(), 
precision), hashLong(v.getEpochSecond()));
+    }
+
+    /**
+     * Get сombined hash code.
+     *
+     * @param hashes Individual hash codes.
+     */
+    public static int combinedHash(int[] hashes) {
+        int hash = 0;
+
+        for (int h : hashes) {
+            hash = combine(hash, h);
+        }
+
+        return hash;
     }
 
     /**
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/util/HashUtils.java 
b/modules/core/src/main/java/org/apache/ignite/internal/util/HashUtils.java
index fd53e87442..dc7924250b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/HashUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/HashUtils.java
@@ -64,42 +64,37 @@ public class HashUtils {
     }
 
     /**
-     * Generates 32-bit hash from the byte array with the given offset, length 
and seed.
+     * Generates 32-bit hash from the byte value.
      *
-     * @param data   The input byte array.
-     * @param offset The first element of array.
-     * @param length The length of array.
-     * @param seed   The initial seed value.
+     * @param data The input byte.
      * @return The 32-bit hash.
      */
-    public static int hash32(byte[] data, int offset, int length, int seed) {
-        long hash = hash64(data, offset, length, seed);
+    public static int hash32(byte data) {
+        long hash = hash64(data, 0);
 
         return (int) (hash ^ (hash >>> 32));
     }
 
     /**
-     * Generates 32-bit hash from the byte value.
+     * Generates 32-bit hash from the short value.
      *
-     * @param data The input byte.
+     * @param data The input short value.
      * @return The 32-bit hash.
      */
-    public static int hash32(byte data, int seed) {
-        long hash = hash64(data, seed);
+    public static int hash32(short data) {
+        long hash = hash64(data, 0);
 
         return (int) (hash ^ (hash >>> 32));
     }
 
     /**
-     * Generates 32-bit hash from the short value.
+     * Generates 32-bit hash from the integer value.
      *
-     * @param data The input short value.
+     * @param data The input integer value.
      * @return The 32-bit hash.
      */
-    public static int hash32(short data, int seed) {
-        long hash = hash64(data, seed);
-
-        return (int) (hash ^ (hash >>> 32));
+    public static int hash32(int data) {
+        return hash32(data, 0);
     }
 
     /**
@@ -114,6 +109,16 @@ public class HashUtils {
         return (int) (hash ^ (hash >>> 32));
     }
 
+    /**
+     * Generates 32-bit hash from the long value.
+     *
+     * @param data The input long value.
+     * @return The 32-bit hash.
+     */
+    public static int hash32(long data) {
+        return hash32(data, 0);
+    }
+
     /**
      * Generates 32-bit hash from the long value.
      *
@@ -126,6 +131,19 @@ public class HashUtils {
         return (int) (hash ^ (hash >>> 32));
     }
 
+    /**
+     * Combines two hash values, using second value as a seed for the hash of 
the first one.
+     *
+     * <p>The operation is not commutative - the order of the arguments 
matters.
+     *
+     * @param hash1 The first hash.
+     * @param hash2 The second hash.
+     * @return The combined 32-bit hash.
+     */
+    public static int combine(int hash1, int hash2) {
+        return hash32(hash1, hash2);
+    }
+
     /**
      * Generates 64-bit hash from the byte value.
      *
diff --git a/modules/platforms/cpp/tests/client-test/compute_test.cpp 
b/modules/platforms/cpp/tests/client-test/compute_test.cpp
index 69e6299889..b5c31a5021 100644
--- a/modules/platforms/cpp/tests/client-test/compute_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/compute_test.cpp
@@ -239,7 +239,7 @@ TEST_F(compute_test, all_arg_types) {
 }
 
 TEST_F(compute_test, execute_colocated) {
-    std::map<std::int32_t, std::string> nodes_for_values = {{1, "_4"}, {5, 
"_2"}, {9, "_3"}, {10, ""}, {11, "_2"}};
+    std::map<std::int32_t, std::string> nodes_for_values = {{1, "_2"}, {5, 
"_4"}, {9, ""}, {10, "_2"}, {11, "_4"}};
 
     for (const auto &var : nodes_for_values) {
         SCOPED_TRACE("key=" + std::to_string(var.first) + ", node=" + 
var.second);
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
index 7d63d4bea6..ea6acc0c0b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
@@ -233,16 +233,16 @@ namespace Apache.Ignite.Tests.Compute
         }
 
         [Test]
-        [TestCase(1, 4)]
-        [TestCase(2, 4)]
-        [TestCase(4, 2)]
-        [TestCase(5, 2)]
-        [TestCase(6, 1)]
-        [TestCase(7, 4)]
+        [TestCase(1, 2)]
+        [TestCase(2, 1)]
+        [TestCase(4, 3)]
+        [TestCase(5, 4)]
+        [TestCase(6, 3)]
+        [TestCase(7, 1)]
         [TestCase(8, 2)]
-        [TestCase(9, 3)]
-        [TestCase(10, 1)]
-        [TestCase(11, 2)]
+        [TestCase(9, 1)]
+        [TestCase(10, 2)]
+        [TestCase(11, 4)]
         public async Task TestExecuteColocated(long key, int nodeIdx)
         {
             var proxies = GetProxies();
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
index 5a793a086f..94779fe45a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
@@ -33,14 +33,14 @@ public class PartitionAwarenessTests
 {
     private static readonly object[] KeyNodeCases =
     {
-        new object[] { 3, 1 },
-        new object[] { 5, 1 },
-        new object[] { 8, 1 },
+        new object[] { 0, 1 },
         new object[] { 1, 2 },
+        new object[] { 3, 1 },
         new object[] { 4, 2 },
-        new object[] { 0, 2 },
+        new object[] { 5, 2 },
+        new object[] { 8, 2 },
         new object[] { int.MaxValue, 2 },
-        new object[] { int.MaxValue - 1, 1 },
+        new object[] { int.MaxValue - 1, 2 },
         new object[] { int.MinValue, 2 }
     };
 
@@ -78,7 +78,7 @@ public class PartitionAwarenessTests
         await AssertOpOnNode(async () => await recordView.UpsertAsync(null, 
1), ClientOp.TupleUpsert, _server2, _server1);
         await AssertOpOnNode(async () => await recordView.UpsertAsync(null, 
3), ClientOp.TupleUpsert, _server1, _server2);
         await AssertOpOnNode(async () => await recordView.UpsertAsync(null, 
4), ClientOp.TupleUpsert, _server2, _server1);
-        await AssertOpOnNode(async () => await recordView.UpsertAsync(null, 
5), ClientOp.TupleUpsert, _server1, _server2);
+        await AssertOpOnNode(async () => await recordView.UpsertAsync(null, 
7), ClientOp.TupleUpsert, _server1, _server2);
     }
 
     [Test]
@@ -265,8 +265,8 @@ public class PartitionAwarenessTests
         await Test("1", Guid.Empty, _server1);
         await Test("1", Guid.Parse("b0000000-0000-0000-0000-000000000000"), 
_server2);
 
-        await Test("a", Guid.Empty, _server2);
-        await Test("a", Guid.Parse("b0000000-0000-0000-0000-000000000000"), 
_server1);
+        await Test("c", Guid.Empty, _server2);
+        await Test("c", Guid.Parse("b0000000-0000-0000-0000-000000000000"), 
_server1);
 
         async Task Test(string idStr, Guid idGuid, FakeServer node) =>
             await AssertOpOnNode(() => view.UpsertAsync(null, new 
CompositeKey(idStr, idGuid)), ClientOp.TupleUpsert, node);
@@ -283,7 +283,7 @@ public class PartitionAwarenessTests
 
         // Both columns are part of key, but only string column is colocation 
key, so random Guid does not affect the hash.
         await Test("1", Guid.NewGuid(), _server2);
-        await Test("a", Guid.NewGuid(), _server1);
+        await Test("c", Guid.NewGuid(), _server1);
 
         async Task Test(string idStr, Guid idGuid, FakeServer node) =>
             await AssertOpOnNode(() => view.UpsertAsync(null, new 
CompositeKey(idStr, idGuid)), ClientOp.TupleUpsert, node);
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
index 62972edead..0b88821997 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
@@ -105,7 +105,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32((sbyte)0, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32((sbyte)0));
             }
 
             OnWrite();
@@ -119,7 +119,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value));
             }
 
             PutByte(value);
@@ -150,7 +150,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value));
             }
 
             if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
@@ -189,7 +189,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value));
             }
 
             if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
@@ -232,7 +232,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value));
             }
 
             if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
@@ -279,7 +279,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value));
             }
 
             PutFloat(value);
@@ -310,7 +310,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value));
             }
 
             // ReSharper disable once CompareOfFloatsByEqualityOperator
@@ -377,7 +377,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value));
             }
 
             PutBytes(value);
@@ -419,8 +419,9 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             {
                 var lo = BinaryPrimitives.ReadInt64LittleEndian(span[..8]);
                 var hi = BinaryPrimitives.ReadInt64LittleEndian(span[8..]);
+                var hash = HashUtils.Hash32(hi, HashUtils.Hash32(lo));
 
-                _hash = HashUtils.Hash32(hi, HashUtils.Hash32(lo, _hash));
+                _hash = HashUtils.Combine(_hash, hash);
             }
 
             OnWrite();
@@ -465,7 +466,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
 
                 if (ShouldHash())
                 {
-                    _hash = HashUtils.Hash32(resBytes, _hash);
+                    _hash = HashUtils.Combine(_hash, 
HashUtils.Hash32(resBytes));
                 }
 
                 PutBytes(resBytes);
@@ -531,7 +532,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
 
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(destination[..written], _hash);
+                _hash = HashUtils.Combine(_hash, 
HashUtils.Hash32(destination[..written]));
             }
 
             Debug.Assert(success, "success");
@@ -564,7 +565,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value));
             }
 
             PutDate(value);
@@ -596,7 +597,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, precision, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value, 
precision));
             }
 
             PutTime(value, precision);
@@ -629,7 +630,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, precision, _hash);
+                _hash = HashUtils.Combine(_hash, HashUtils.Hash32(value, 
precision));
             }
 
             PutDate(value.Date);
@@ -665,8 +666,8 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
 
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(seconds, _hash);
-                _hash = HashUtils.Hash32((long)nanos, _hash);
+                var hash = HashUtils.Hash32(nanos, HashUtils.Hash32(seconds));
+                _hash = HashUtils.Combine(_hash, hash);
             }
 
             OnWrite();
@@ -1050,7 +1051,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             {
                 if (ShouldHash())
                 {
-                    _hash = HashUtils.Hash32(Span<byte>.Empty, _hash);
+                    _hash = HashUtils.Combine(_hash, 
HashUtils.Hash32(Span<byte>.Empty));
                 }
 
                 _buffer.GetSpan(1)[0] = BinaryTupleCommon.VarlenEmptyByte;
@@ -1065,7 +1066,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
 
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(span[..actualBytes], _hash);
+                _hash = HashUtils.Combine(_hash, 
HashUtils.Hash32(span[..actualBytes]));
             }
 
             _buffer.Advance(actualBytes);
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/HashUtils.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/HashUtils.cs
index c386d535b2..ebb4d1c2e9 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/HashUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/HashUtils.cs
@@ -44,78 +44,85 @@ internal static class HashUtils
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
-    /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(sbyte data, int seed) => 
Hash32Internal((ulong)(data & 0xffL), (ulong)seed, 1);
+    public static int Hash32(sbyte data) => 
Hash32Internal(unchecked((byte)data), 0, 1);
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
-    /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(short data, int seed) => 
Hash32Internal((ulong)(data & 0xffffL), (ulong)seed, 2);
+    public static int Hash32(short data) => 
Hash32Internal(unchecked((ushort)data), 0, 2);
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
-    /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(int data, int seed) => 
Hash32Internal((ulong)(data & 0xffffffffL), (ulong)seed, 4);
+    public static int Hash32(int data) => Hash32(data, 0);
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
-    /// <param name="seed">Current hash.</param>
+    /// <param name="seed">Seed.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(long data, int seed) => 
Hash32Internal((ulong)data, (ulong)seed, 8);
+    public static int Hash32(int data, int seed) => 
Hash32Internal(unchecked((uint)data), (ulong)seed, 4);
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
-    /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(float data, int seed) => 
Hash32(BitConverter.SingleToInt32Bits(data), seed);
+    public static int Hash32(long data) => Hash32(data, 0);
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
-    /// <param name="seed">Current hash.</param>
+    /// <param name="seed">Seed.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(double data, int seed) => 
Hash32(BitConverter.DoubleToInt64Bits(data), seed);
+    public static int Hash32(long data, int seed) => 
Hash32Internal(unchecked((ulong)data), (ulong)seed, 8);
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
-    /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(Span<byte> data, int seed) => 
Hash32Internal(data, (ulong)seed & 0xffffffffL);
+    public static int Hash32(float data) => 
Hash32(BitConverter.SingleToInt32Bits(data));
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
-    /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(LocalDate data, int seed) => 
Hash32((long)data.Day, Hash32((long)data.Month, Hash32((long)data.Year, seed)));
+    public static int Hash32(double data) => 
Hash32(BitConverter.DoubleToInt64Bits(data));
+
+    /// <summary>
+    /// Generates 32-bit hash.
+    /// </summary>
+    /// <param name="data">Input data.</param>
+    /// <returns>Resulting hash.</returns>
+    public static int Hash32(Span<byte> data) => Hash32Internal(data, 0);
+
+    /// <summary>
+    /// Generates 32-bit hash.
+    /// </summary>
+    /// <param name="data">Input data.</param>
+    /// <returns>Resulting hash.</returns>
+    public static int Hash32(LocalDate data) => Hash32(data.Day, 
Hash32(data.Month, Hash32(data.Year)));
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
     /// <param name="precision">Precision.</param>
-    /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(LocalTime data, int precision, int seed)
+    public static int Hash32(LocalTime data, int precision)
     {
-        var nanos = 
(long)TemporalTypes.NormalizeNanos(data.NanosecondOfSecond, precision);
+        var nanos = TemporalTypes.NormalizeNanos(data.NanosecondOfSecond, 
precision);
 
-        return Hash32(nanos, Hash32((long)data.Second, 
Hash32((long)data.Minute, Hash32((long)data.Hour, seed))));
+        return Hash32(nanos, Hash32(data.Second, Hash32(data.Minute, 
Hash32(data.Hour))));
     }
 
     /// <summary>
@@ -123,9 +130,16 @@ internal static class HashUtils
     /// </summary>
     /// <param name="data">Input data.</param>
     /// <param name="precision">Precision.</param>
-    /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(LocalDateTime data, int precision, int seed) => 
Hash32(data.TimeOfDay, precision, Hash32(data.Date, seed));
+    public static int Hash32(LocalDateTime data, int precision) => 
Combine(Hash32(data.Date), Hash32(data.TimeOfDay, precision));
+
+    /// <summary>
+    /// Combines two hashes.
+    /// </summary>
+    /// <param name="hash1">Hash 1.</param>
+    /// <param name="hash2">Hash 2.</param>
+    /// <returns>Combined hash.</returns>
+    public static int Combine(int hash1, int hash2) => 
Hash32Internal(unchecked((uint)hash1), unchecked((ulong)hash2), 4);
 
     private static int Hash32Internal(ulong data, ulong seed, byte byteCount)
     {
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientComputeTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientComputeTest.java
index 6912c90e76..6c9bed0313 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientComputeTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientComputeTest.java
@@ -185,7 +185,7 @@ public class ItThinClientComputeTest extends 
ItAbstractThinClientTest {
     }
 
     @ParameterizedTest
-    @CsvSource({"1,3345", "2,3345", "3,3344", "4,3345"})
+    @CsvSource({"1,3344", "2,3345", "3,3345", "10,3344"})
     void testExecuteColocatedRunsComputeJobOnKeyNode(int key, int port) {
         var table = TABLE_NAME;
         var keyTuple = Tuple.create().set(COLUMN_KEY, key);
diff --git 
a/modules/table/src/test/java/org/apache/ignite/internal/table/ColocationHashCalculationTest.java
 
b/modules/table/src/test/java/org/apache/ignite/internal/table/ColocationHashCalculationTest.java
index f0aa965be3..935605659f 100644
--- 
a/modules/table/src/test/java/org/apache/ignite/internal/table/ColocationHashCalculationTest.java
+++ 
b/modules/table/src/test/java/org/apache/ignite/internal/table/ColocationHashCalculationTest.java
@@ -21,7 +21,10 @@ import static 
org.apache.ignite.internal.schema.NativeTypes.INT32;
 import static org.apache.ignite.internal.schema.NativeTypes.INT8;
 import static org.apache.ignite.internal.schema.NativeTypes.STRING;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Random;
 import java.util.stream.IntStream;
 import org.apache.ignite.internal.logger.Loggers;
@@ -86,7 +89,13 @@ public class ColocationHashCalculationTest {
         hashCalc.appendInt(2);
         hashCalc.appendString("key_" + 3);
 
+        int[] hashes = new int[3];
+        hashes[0] = HashCalculator.hashByte((byte) 1);
+        hashes[1] = HashCalculator.hashInt(2);
+        hashes[2] = HashCalculator.hashString("key_" + 3);
+
         assertEquals(hashCalc.hash(), colocationHash(r));
+        assertEquals(hashCalc.hash(), HashCalculator.combinedHash(hashes));
     }
 
     @Test
@@ -117,6 +126,62 @@ public class ColocationHashCalculationTest {
         }
     }
 
+    @Test
+    void collisions() {
+        var set = new HashSet<Integer>();
+        int collisions = 0;
+
+        for (var key1 = 0; key1 < 100; key1++) {
+            for (var key2 = 0; key2 < 100; key2++) {
+                for (var key3 = 0; key3 < 100; key3++) {
+                    HashCalculator hashCalc = new HashCalculator();
+                    hashCalc.appendInt(key1);
+                    hashCalc.appendInt(key2);
+                    hashCalc.appendInt(key3);
+
+                    int hash = hashCalc.hash();
+                    if (set.contains(hash)) {
+                        collisions++;
+                    } else {
+                        set.add(hash);
+                    }
+                }
+            }
+        }
+
+        assertEquals(125, collisions);
+    }
+
+    @Test
+    void distribution() {
+        int partitions = 100;
+        var map = new HashMap<Integer, Integer>();
+
+        for (var key1 = 0; key1 < 100; key1++) {
+            for (var key2 = 0; key2 < 100; key2++) {
+                for (var key3 = 0; key3 < 100; key3++) {
+                    HashCalculator hashCalc = new HashCalculator();
+                    hashCalc.appendInt(key1);
+                    hashCalc.appendInt(key2);
+                    hashCalc.appendInt(key3);
+
+                    int hash = hashCalc.hash();
+                    int partition = Math.abs(hash % partitions);
+
+                    map.put(partition, map.getOrDefault(partition, 0) + 1);
+                }
+            }
+        }
+
+        var maxSkew = 326;
+
+        for (var entry : map.entrySet()) {
+            // CSV to plot: System.out.println(entry.getKey() + ", " + 
entry.getValue());
+            assertTrue(entry.getValue() < 10_000 + maxSkew, "Partition " + 
entry.getKey() + " keys: " + entry.getValue());
+            assertTrue(entry.getValue() > 10_000 - maxSkew, "Partition " + 
entry.getKey() + " keys: " + entry.getValue());
+        }
+    }
+
     private static Row generateRandomRow(Random rnd, @NotNull SchemaDescriptor 
schema) throws TupleMarshallerException {
         TupleMarshaller marshaller = new TupleMarshallerImpl(new 
DummySchemaManagerImpl(schema));
 
@@ -131,7 +196,7 @@ public class ColocationHashCalculationTest {
         return marshaller.marshal(t);
     }
 
-    private int colocationHash(Row r) {
+    private static int colocationHash(Row r) {
         HashCalculator hashCalc = new HashCalculator();
         for (Column c : r.schema().colocationColumns()) {
             var scale = c.type() instanceof DecimalNativeType ? 
((DecimalNativeType) c.type()).scale() : 0;

Reply via email to