From 67aab6f660a6965eb50fa92b0cb2ae996ae01907 Mon Sep 17 00:00:00 2001 From: Maksim Timonin Date: Fri, 30 Jan 2026 18:20:11 +0300 Subject: [PATCH 1/4] IGNITE-27696 Fix IndexQuery queries backup partition when setPartition used --- .../processors/cache/query/CacheQuery.java | 14 +++++++++++--- .../cache/query/GridCacheQueryManager.java | 6 +++++- .../cache/query/IndexQueryPartitionTest.java | 3 ++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java index 6f394c2efce5d..634556f270022 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java @@ -859,14 +859,22 @@ private static Collection nodes(final GridCacheContext cctx, if (part != null && part >= cctx.affinity().partitions()) throw new IgniteCheckedException("Invalid partition number: " + part); - final Set owners = - part == null ? Collections.emptySet() : new HashSet<>(cctx.topology().owners(part, topVer)); + final Set partHolders; + + if (part != null) { + if (cctx.config().getCacheMode() == CacheMode.PARTITIONED) + partHolders = Collections.singleton(cctx.affinity().primaryByPartition(part, topVer)); + else + partHolders = new HashSet<>(cctx.topology().owners(part, topVer)); + } + else + partHolders = Collections.emptySet(); return F.view(affNodes, new P1() { @Override public boolean apply(ClusterNode n) { return cctx.discovery().cacheAffinityNode(n, cctx.name()) && (prj == null || prj.node(n.id()) != null) && - (part == null || owners.contains(n)); + (part == null || partHolders.contains(n)); } }); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java index 8fe2c925bb10b..a23361aa0bdd4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java @@ -46,6 +46,7 @@ import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteSystemProperties; import org.apache.ignite.cache.CacheEntry; +import org.apache.ignite.cache.CacheMode; import org.apache.ignite.cache.QueryIndexType; import org.apache.ignite.cache.query.IndexQuery; import org.apache.ignite.cache.query.QueryMetrics; @@ -1123,7 +1124,10 @@ public GridCloseableIterator indexQueryLocal(final CacheQuery qry) throws Ignite if (part != null) { final GridDhtLocalPartition locPart = cctx.dht().topology().localPartition(part); - if (locPart == null || locPart.state() != OWNING) { + boolean nonLocPart = locPart == null || locPart.state() != OWNING || + (cctx.config().getCacheMode() == CacheMode.PARTITIONED && !locPart.primary(AffinityTopologyVersion.NONE)); + + if (nonLocPart) { throw new CacheInvalidStateException("Failed to execute index query because required partition " + "has not been found on local node [cacheName=" + cctx.name() + ", part=" + part + "]"); } diff --git a/modules/indexing/src/test/java/org/apache/ignite/cache/query/IndexQueryPartitionTest.java b/modules/indexing/src/test/java/org/apache/ignite/cache/query/IndexQueryPartitionTest.java index 73ea7f305320b..a0704047a43d7 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/cache/query/IndexQueryPartitionTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/cache/query/IndexQueryPartitionTest.java @@ -78,6 +78,7 @@ public static List params() { .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL) .setCacheMode(cacheMode) .setIndexedTypes(Integer.class, Person.class) + .setBackups(1) .setAffinity(new RendezvousAffinityFunction().setPartitions(100)); cfg.setCacheConfiguration(ccfg); @@ -136,7 +137,7 @@ public void testSinglePartition() { } } - assertEquals(sendReq, TestRecordingCommunicationSpi.spi(grid()).recordedMessages(true).size()); + assertEquals("part=" + part, sendReq, TestRecordingCommunicationSpi.spi(grid()).recordedMessages(true).size()); } } From ce8ab506a75a496ff8b9781ab94d12e817f3b442 Mon Sep 17 00:00:00 2001 From: Maksim Timonin Date: Wed, 11 Feb 2026 13:32:51 +0300 Subject: [PATCH 2/4] Fix --- .../processors/cache/query/CacheQuery.java | 23 ++++++++----------- .../GridCacheDistributedQueryFuture.java | 21 +++++++++++------ .../cache/query/GridCacheQueryManager.java | 6 +---- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java index 634556f270022..ee64c6b552a91 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java @@ -840,9 +840,12 @@ private Collection nodes() throws IgniteCheckedException { } /** + * Collects query data nodes matching specified {@code prj} and {@code part}. + * * @param cctx Cache context. * @param prj Projection (optional). - * @return Collection of data nodes in provided projection (if any). + * @param part Partition (optional). + * @return Collection of data nodes matching specified {@code prj} and {@code part}. * @throws IgniteCheckedException If partition number is invalid. */ private static Collection nodes(final GridCacheContext cctx, @@ -856,25 +859,17 @@ private static Collection nodes(final GridCacheContext cctx, if (prj == null && part == null) return affNodes; - if (part != null && part >= cctx.affinity().partitions()) - throw new IgniteCheckedException("Invalid partition number: " + part); - - final Set partHolders; - if (part != null) { - if (cctx.config().getCacheMode() == CacheMode.PARTITIONED) - partHolders = Collections.singleton(cctx.affinity().primaryByPartition(part, topVer)); - else - partHolders = new HashSet<>(cctx.topology().owners(part, topVer)); + if (part >= cctx.affinity().partitions()) + throw new IgniteCheckedException("Invalid partition number: " + part); + + affNodes = cctx.topology().nodes(part, topVer); } - else - partHolders = Collections.emptySet(); return F.view(affNodes, new P1() { @Override public boolean apply(ClusterNode n) { return cctx.discovery().cacheAffinityNode(n, cctx.name()) && - (prj == null || prj.node(n.id()) != null) && - (part == null || partHolders.contains(n)); + (prj == null || prj.node(n.id()) != null); } }); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java index 2b12a3b784c7a..53ac18b170d0d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java @@ -25,6 +25,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.ignite.IgniteCheckedException; @@ -38,6 +39,7 @@ import org.apache.ignite.internal.processors.cache.query.reducer.TextQueryReducer; import org.apache.ignite.internal.processors.cache.query.reducer.UnsortedCacheQueryReducer; import org.apache.ignite.internal.util.lang.GridPlainCallable; +import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import static org.apache.ignite.internal.processors.cache.query.GridCacheQueryType.INDEX; @@ -92,7 +94,7 @@ protected GridCacheDistributedQueryFuture( qryMgr = (GridCacheDistributedQueryManager)ctx.queries(); if (qry.query().partition() != null) - nodes = Collections.singletonList(node(nodes)); + nodes = Collections.singletonList(cctx.isReplicated() ? localOrRemoteNode(nodes) : F.first(nodes)); streams = new ConcurrentHashMap<>(nodes.size()); @@ -118,19 +120,24 @@ protected GridCacheDistributedQueryFuture( } /** - * @return Nodes for query execution. + * @return A local node if available, otherwise a random node from the given collection. */ - private ClusterNode node(Collection nodes) { - ClusterNode rmtNode = null; + private static ClusterNode localOrRemoteNode(Collection nodes) { + int remoteNodeIdx = ThreadLocalRandom.current().nextInt(nodes.size()); + + ClusterNode remoteNode = null; for (ClusterNode node : nodes) { if (node.isLocal()) return node; - rmtNode = node; + if (remoteNodeIdx-- == 0) + remoteNode = node; } - return rmtNode; + assert remoteNode != null; + + return remoteNode; } /** {@inheritDoc} */ @@ -282,7 +289,7 @@ private void cancelPages(UUID nodeId) { GridCacheQueryRequest req = GridCacheQueryRequest.cancelRequest(cctx, reqId, fields()); if (nodeId.equals(cctx.localNodeId())) { - // Process cancel query directly (without sending) for local node, + // Process cancel query directly (without sending) for local node. cctx.closures().callLocalSafe(new GridPlainCallable() { @Override public Object call() { qryMgr.processQueryRequest(cctx.localNodeId(), req); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java index a23361aa0bdd4..8fe2c925bb10b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java @@ -46,7 +46,6 @@ import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteSystemProperties; import org.apache.ignite.cache.CacheEntry; -import org.apache.ignite.cache.CacheMode; import org.apache.ignite.cache.QueryIndexType; import org.apache.ignite.cache.query.IndexQuery; import org.apache.ignite.cache.query.QueryMetrics; @@ -1124,10 +1123,7 @@ public GridCloseableIterator indexQueryLocal(final CacheQuery qry) throws Ignite if (part != null) { final GridDhtLocalPartition locPart = cctx.dht().topology().localPartition(part); - boolean nonLocPart = locPart == null || locPart.state() != OWNING || - (cctx.config().getCacheMode() == CacheMode.PARTITIONED && !locPart.primary(AffinityTopologyVersion.NONE)); - - if (nonLocPart) { + if (locPart == null || locPart.state() != OWNING) { throw new CacheInvalidStateException("Failed to execute index query because required partition " + "has not been found on local node [cacheName=" + cctx.name() + ", part=" + part + "]"); } From 58e1f253037fd64ec746cecf9da88cf5dcf01f3d Mon Sep 17 00:00:00 2001 From: Maksim Timonin Date: Wed, 11 Feb 2026 15:10:29 +0300 Subject: [PATCH 3/4] Fix test --- .../cache/query/GridCacheDistributedQueryFuture.java | 10 +++++----- .../processors/cache/query/GridCacheQueryManager.java | 6 +++--- .../ignite/cache/query/IndexQueryPartitionTest.java | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java index 53ac18b170d0d..515cc7323b510 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java @@ -122,22 +122,22 @@ protected GridCacheDistributedQueryFuture( /** * @return A local node if available, otherwise a random node from the given collection. */ - private static ClusterNode localOrRemoteNode(Collection nodes) { + private ClusterNode localOrRemoteNode(Collection nodes) { int remoteNodeIdx = ThreadLocalRandom.current().nextInt(nodes.size()); - ClusterNode remoteNode = null; + ClusterNode rmtNode = null; for (ClusterNode node : nodes) { if (node.isLocal()) return node; if (remoteNodeIdx-- == 0) - remoteNode = node; + rmtNode = node; } - assert remoteNode != null; + assert rmtNode != null; - return remoteNode; + return rmtNode; } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java index 8fe2c925bb10b..2db3ed5263ea4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java @@ -1121,11 +1121,11 @@ public GridCloseableIterator indexQueryLocal(final CacheQuery qry) throws Ignite int[] parts = null; if (part != null) { - final GridDhtLocalPartition locPart = cctx.dht().topology().localPartition(part); + AffinityTopologyVersion topVer = cctx.affinity().affinityTopologyVersion(); - if (locPart == null || locPart.state() != OWNING) { + if (cctx.isPartitioned() && !cctx.affinity().primaryByPartition(cctx.localNode(), part, topVer)) { throw new CacheInvalidStateException("Failed to execute index query because required partition " + - "has not been found on local node [cacheName=" + cctx.name() + ", part=" + part + "]"); + "is not primary on local node [cacheName=" + cctx.name() + ", part=" + part + ", topVer=" + topVer + ']'); } parts = new int[] {part}; diff --git a/modules/indexing/src/test/java/org/apache/ignite/cache/query/IndexQueryPartitionTest.java b/modules/indexing/src/test/java/org/apache/ignite/cache/query/IndexQueryPartitionTest.java index a0704047a43d7..8fe205ef7d9ea 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/cache/query/IndexQueryPartitionTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/cache/query/IndexQueryPartitionTest.java @@ -182,7 +182,7 @@ public void testLocalWithPartition() { GridTestUtils.assertThrows(null, () -> grid().cache("CACHE").query(qry).getAll(), client ? IgniteException.class : CacheInvalidStateException.class, client ? "Failed to execute local index query on a client node." : - "Failed to execute index query because required partition has not been found on local node"); + "Failed to execute index query because required partition is not primary on local node"); } else assertTrue(!grid().cache("CACHE").query(qry).getAll().isEmpty()); From d6d4a8cdf41f9a09a33dfadbdcb6dd93485c0b81 Mon Sep 17 00:00:00 2001 From: Maksim Timonin Date: Thu, 12 Feb 2026 18:58:23 +0300 Subject: [PATCH 4/4] Fix comment --- .../internal/processors/cache/query/CacheQuery.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java index ee64c6b552a91..9c6d6782b1cb1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java @@ -62,7 +62,6 @@ import org.apache.ignite.internal.util.lang.GridIterator; import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.P1; import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.A; @@ -866,12 +865,7 @@ private static Collection nodes(final GridCacheContext cctx, affNodes = cctx.topology().nodes(part, topVer); } - return F.view(affNodes, new P1() { - @Override public boolean apply(ClusterNode n) { - return cctx.discovery().cacheAffinityNode(n, cctx.name()) && - (prj == null || prj.node(n.id()) != null); - } - }); + return prj == null ? affNodes : F.view(affNodes, n -> prj.node(n.id()) != null); } /** */