All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.map.impl.query.QueryEngineImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hazelcast.map.impl.query;

import com.hazelcast.cluster.Address;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.internal.cluster.ClusterService;
import com.hazelcast.internal.partition.IPartitionService;
import com.hazelcast.internal.util.IterationType;
import com.hazelcast.internal.util.collection.PartitionIdSet;
import com.hazelcast.logging.ILogger;
import com.hazelcast.map.QueryResultSizeExceededException;
import com.hazelcast.map.impl.MapService;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.query.Target.TargetMode;
import com.hazelcast.query.PagingPredicate;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.Predicates;
import com.hazelcast.query.QueryException;
import com.hazelcast.query.impl.predicates.PagingPredicateImpl;
import com.hazelcast.spi.impl.NodeEngine;
import com.hazelcast.spi.impl.operationservice.Operation;
import com.hazelcast.spi.impl.operationservice.OperationService;
import com.hazelcast.spi.properties.HazelcastProperty;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.PrimitiveIterator;
import java.util.concurrent.Future;
import java.util.function.IntConsumer;
import java.util.stream.Collectors;

import static com.hazelcast.cluster.memberselector.MemberSelectors.DATA_MEMBER_SELECTOR;
import static com.hazelcast.internal.util.ExceptionUtil.rethrow;
import static com.hazelcast.internal.util.SetUtil.allPartitionIds;
import static java.util.Collections.singletonList;

/**
 * Invokes and orchestrates the query logic returning the final result.
 * 

* Knows nothing about query logic. * Should never invoke any query operations directly. *

* Should be used from top-level proxy-layer only (e.g. MapProxy, etc.). *

* Top level-query actors: *

    *
  • QueryEngine orchestrates the queries and merging the result *
  • QueryRunner -> runs the query logic in the calling thread (so like evaluates the predicates and asks the index) *
*/ public class QueryEngineImpl implements QueryEngine { /** * Used only for tests to disable the fallback to partition operations. We * had issues when the fallback fixed the results in the normal case and * the much worsened performance went unnoticed. (E.g. * https://github.com/hazelcast/hazelcast/issues/18240). */ public static final HazelcastProperty DISABLE_MIGRATION_FALLBACK = new HazelcastProperty(QueryEngineImpl.class.getName() + ".disableMigrationFallback", false); private final MapServiceContext mapServiceContext; private final NodeEngine nodeEngine; private final ILogger logger; private final QueryResultSizeLimiter queryResultSizeLimiter; private final IPartitionService partitionService; private final OperationService operationService; private final ClusterService clusterService; private final ResultProcessorRegistry resultProcessorRegistry; private final boolean disableMigrationFallback; public QueryEngineImpl(MapServiceContext mapServiceContext) { this.mapServiceContext = mapServiceContext; this.nodeEngine = mapServiceContext.getNodeEngine(); this.partitionService = nodeEngine.getPartitionService(); this.logger = nodeEngine.getLogger(getClass()); this.queryResultSizeLimiter = new QueryResultSizeLimiter(mapServiceContext, logger); this.operationService = nodeEngine.getOperationService(); this.clusterService = nodeEngine.getClusterService(); this.resultProcessorRegistry = mapServiceContext.getResultProcessorRegistry(); this.disableMigrationFallback = nodeEngine.getProperties().getBoolean(DISABLE_MIGRATION_FALLBACK); } @SuppressWarnings("unchecked") @Override public Result execute(Query query, Target target) { Query adjustedQuery = adjustQuery(query); switch (target.mode()) { case ALL_NODES: adjustedQuery = Query.of(adjustedQuery).partitionIdSet(getAllPartitionIds()).build(); return runOnGivenPartitions(adjustedQuery, adjustedQuery.getPartitionIdSet(), TargetMode.ALL_NODES); case LOCAL_NODE: adjustedQuery = Query.of(adjustedQuery).partitionIdSet(getLocalPartitionIds()).build(); return runOnGivenPartitions(adjustedQuery, adjustedQuery.getPartitionIdSet(), TargetMode.LOCAL_NODE); case PARTITION_OWNER: int solePartition = target.partitions().solePartition(); adjustedQuery = Query.of(adjustedQuery).partitionIdSet(target.partitions()).build(); if (solePartition >= 0) { return runOnGivenPartition(adjustedQuery, solePartition); } else { return runOnGivenPartitions(adjustedQuery, adjustedQuery.getPartitionIdSet(), TargetMode.ALL_NODES); } default: throw new IllegalArgumentException("Illegal target " + target); } } private Query adjustQuery(Query query) { IterationType retrievalIterationType = getRetrievalIterationType(query.getPredicate(), query.getIterationType()); Query.QueryBuilder builder = Query.of(query).iterationType(retrievalIterationType); if (query.getPredicate() instanceof PagingPredicateImpl) { // PagingPredicateImpl must be cloned before adjusting else it corrupts the iteration type for the caller PagingPredicateImpl clonedPredicate = new PagingPredicateImpl((PagingPredicateImpl) query.getPredicate()); clonedPredicate.setIterationType(query.getIterationType()); builder.predicate(clonedPredicate); } else if (query.getPredicate() == Predicates.alwaysTrue()) { queryResultSizeLimiter.precheckMaxResultLimitOnLocalPartitions(query.getMapName()); } return builder.build(); } // query thread first, fallback to partition thread private Result runOnGivenPartitions(Query query, PartitionIdSet partitions, TargetMode targetMode) { Result result = doRunOnQueryThreads(query, partitions, targetMode); if (!disableMigrationFallback) { if (isResultFromAnyPartitionMissing(partitions)) { doRunOnPartitionThreads(query, partitions, result); } } assertAllPartitionsQueried(partitions); return result; } // partition thread ONLY (for now) private Result runOnGivenPartition(Query query, int partitionId) { try { return dispatchPartitionScanQueryOnOwnerMemberOnPartitionThread( query, partitionId).get(); } catch (Throwable t) { throw rethrow(t); } } private Result doRunOnQueryThreads(Query query, PartitionIdSet partitionIds, TargetMode targetMode) { Result result = populateResult(query); List> futures = dispatchOnQueryThreads(query, targetMode); addResultsOfPredicate(futures, result, partitionIds, disableMigrationFallback); return result; } private List> dispatchOnQueryThreads(Query query, TargetMode targetMode) { try { return dispatchFullQueryOnQueryThread(query, targetMode); } catch (Throwable t) { if (!(t instanceof HazelcastException)) { // these are programmatic errors that needs to be visible throw rethrow(t); } else if (t.getCause() instanceof QueryResultSizeExceededException) { throw rethrow(t); } else { if (disableMigrationFallback) { throw rethrow(t); } else if (logger.isFineEnabled()) { // log failure to invoke query on member at fine level // the missing partition IDs will be queried anyway, so it's not a terminal failure logger.fine("Query invocation failed on member ", t); } } } return Collections.emptyList(); } private Result populateResult(Query query) { return resultProcessorRegistry.get(query.getResultType()).populateResult(query, queryResultSizeLimiter.getNodeResultLimit(query.getPartitionIdSet().size())); } private void doRunOnPartitionThreads(Query query, PartitionIdSet partitionIds, Result result) { try { List> futures = dispatchPartitionScanQueryOnOwnerMemberOnPartitionThread( query, partitionIds); addResultsOfPredicate(futures, result, partitionIds, true); } catch (Throwable t) { throw rethrow(t); } } @SuppressWarnings("unchecked") // modifies partitionIds list! Optimization not to allocate an extra collection with collected partitionIds private void addResultsOfPredicate(List> futures, Result result, PartitionIdSet unfinishedPartitionIds, boolean rethrowAll) { for (Future future : futures) { Result queryResult = null; try { queryResult = future.get(); } catch (Throwable t) { if (t.getCause() instanceof QueryResultSizeExceededException || rethrowAll) { throw rethrow(t); } logger.fine("Could not get query results", t); } if (queryResult == null) { continue; } PartitionIdSet queriedPartitionIds = queryResult.getPartitionIds(); if (queriedPartitionIds != null) { if (!unfinishedPartitionIds.containsAll(queriedPartitionIds)) { // do not take into account results that contain partition IDs already removed from partitionIds // collection as this means that we will count results from a single partition twice // see also https://github.com/hazelcast/hazelcast/issues/6471 continue; } unfinishedPartitionIds.removeAll(queriedPartitionIds); result.combine(queryResult); } } } private void assertAllPartitionsQueried(PartitionIdSet mutablePartitionIds) { if (isResultFromAnyPartitionMissing(mutablePartitionIds)) { throw new QueryException("Query aborted. Could not execute query for all partitions. Missed " + mutablePartitionIds.size() + " partitions"); } } private IterationType getRetrievalIterationType(Predicate predicate, IterationType iterationType) { IterationType retrievalIterationType = iterationType; if (predicate instanceof PagingPredicate pagingPredicate) { if (pagingPredicate.getComparator() != null) { // custom comparators may act on keys and values at the same time retrievalIterationType = IterationType.ENTRY; } else { // in case of value, we also need to get the keys for sorting retrievalIterationType = iterationType == IterationType.VALUE ? IterationType.ENTRY : iterationType; } } return retrievalIterationType; } private PartitionIdSet getLocalPartitionIds() { int partitionCount = partitionService.getPartitionCount(); List memberPartitions = partitionService.getMemberPartitions(nodeEngine.getThisAddress()); return new PartitionIdSet(partitionCount, memberPartitions); } // package-private for tests PartitionIdSet getAllPartitionIds() { int partitionCount = partitionService.getPartitionCount(); return allPartitionIds(partitionCount); } private boolean isResultFromAnyPartitionMissing(PartitionIdSet unfinishedPartitionIds) { return !unfinishedPartitionIds.isEmpty(); } protected QueryResultSizeLimiter getQueryResultSizeLimiter() { return queryResultSizeLimiter; } protected List> dispatchFullQueryOnQueryThread(Query query, TargetMode targetMode) { switch (targetMode) { case ALL_NODES: return dispatchFullQueryOnAllMembersOnQueryThread(query); case LOCAL_NODE: return dispatchFullQueryOnLocalMemberOnQueryThread(query); default: throw new IllegalArgumentException("Illegal target " + query); } } private List> dispatchFullQueryOnLocalMemberOnQueryThread(Query query) { Operation operation = mapServiceContext.getMapOperationProvider(query.getMapName()).createQueryOperation(query); Future result = operationService.invokeOnTarget( MapService.SERVICE_NAME, operation, nodeEngine.getThisAddress()); return singletonList(result); } private List> dispatchFullQueryOnAllMembersOnQueryThread(Query query) { Collection
members; if (query.getPartitionIdSet().size() == partitionService.getPartitionCount()) { members = clusterService.getMembers(DATA_MEMBER_SELECTOR).stream() .map(m -> m.getAddress()) .collect(Collectors.toList()); } else { members = new HashSet<>(); for (PrimitiveIterator.OfInt iterator = query.getPartitionIdSet().intIterator(); iterator.hasNext(); ) { members.add(partitionService.getPartitionOwnerOrWait(iterator.next())); } } List> futures = new ArrayList<>(members.size()); for (Address address : members) { Operation operation = createQueryOperation(query); Future future = operationService.invokeOnTarget( MapService.SERVICE_NAME, operation, address); futures.add(future); } return futures; } private Operation createQueryOperation(Query query) { return mapServiceContext.getMapOperationProvider(query.getMapName()).createQueryOperation(query); } protected List> dispatchPartitionScanQueryOnOwnerMemberOnPartitionThread( Query query, PartitionIdSet partitionIds) { if (shouldSkipPartitionsQuery(partitionIds)) { return Collections.emptyList(); } List> futures = new ArrayList<>(partitionIds.size()); partitionIds.intIterator().forEachRemaining((IntConsumer) partitionId -> futures.add(dispatchPartitionScanQueryOnOwnerMemberOnPartitionThread(query, partitionId))); return futures; } protected Future dispatchPartitionScanQueryOnOwnerMemberOnPartitionThread(Query query, int partitionId) { Operation op = createQueryPartitionOperation(query); op.setPartitionId(partitionId); try { return operationService.invokeOnPartition(MapService.SERVICE_NAME, op, partitionId); } catch (Throwable t) { throw rethrow(t); } } private Operation createQueryPartitionOperation(Query query) { return mapServiceContext.getMapOperationProvider(query.getMapName()).createQueryPartitionOperation(query); } private static boolean shouldSkipPartitionsQuery(PartitionIdSet partitionIds) { return partitionIds == null || partitionIds.isEmpty(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy