/* * Copyright (c) 2008-2017, 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.internal.cluster.ClusterService; import com.hazelcast.internal.serialization.InternalSerializationService; import com.hazelcast.logging.ILogger; import com.hazelcast.map.QueryResultSizeExceededException; import com.hazelcast.map.impl.MapServiceContext; import com.hazelcast.query.PagingPredicate; import com.hazelcast.query.Predicate; import com.hazelcast.query.TruePredicate; import com.hazelcast.spi.NodeEngine; import com.hazelcast.spi.OperationService; import com.hazelcast.spi.partition.IPartitionService; import com.hazelcast.util.IterationType; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import static com.hazelcast.util.ExceptionUtil.rethrow; /** * Invokes and orchestrates the query logic returning the final result. * <p> * Knows nothing about query logic or how to dispatch query operations. It relies on QueryDispatcher only. * Should never invoke any query operations directly. * <p> * Should be used from top-level proxy-layer only (e.g. MapProxy, etc.). * <p> * Top level-query actors: * - QueryEngine orchestrates the queries by dispatching query operations using QueryDispatcher and merging the result * - QueryDispatcher invokes query operations on the given members and partitions * - QueryRunner -> runs the query logic in the calling thread (so like evaluates the predicates and asks the index) * * TODO: Use bitset instead of collection for partitionIds */ public class MapQueryEngineImpl implements MapQueryEngine { protected final MapServiceContext mapServiceContext; protected final NodeEngine nodeEngine; protected final ILogger logger; protected final QueryResultSizeLimiter queryResultSizeLimiter; protected final InternalSerializationService serializationService; protected final IPartitionService partitionService; protected final OperationService operationService; protected final ClusterService clusterService; protected final QueryDispatcher queryDispatcher; protected final ResultProcessorRegistry resultProcessorRegistry; public MapQueryEngineImpl(MapServiceContext mapServiceContext) { this.mapServiceContext = mapServiceContext; this.nodeEngine = mapServiceContext.getNodeEngine(); this.serializationService = (InternalSerializationService) nodeEngine.getSerializationService(); this.partitionService = nodeEngine.getPartitionService(); this.logger = nodeEngine.getLogger(getClass()); this.queryResultSizeLimiter = new QueryResultSizeLimiter(mapServiceContext, logger); this.operationService = nodeEngine.getOperationService(); this.clusterService = nodeEngine.getClusterService(); this.queryDispatcher = new QueryDispatcher(mapServiceContext); this.resultProcessorRegistry = mapServiceContext.getResultProcessorRegistry(); } @Override public Result execute(Query query, Target target) { Query adjustedQuery = adjustQuery(query); if (target.isTargetAllNodes()) { return runQueryOnAllPartitions(adjustedQuery); } else if (target.isTargetLocalNode()) { return runQueryOnLocalPartitions(adjustedQuery); } else if (target.isTargetPartitionOwner()) { // we do not adjust the query here - it's a single partition operation only return runQueryOnGivenPartition(query, target); } throw new IllegalArgumentException("Illegal target " + query); } private Query adjustQuery(Query query) { IterationType retrievalIterationType = getRetrievalIterationType(query.getPredicate(), query.getIterationType()); Query adjustedQuery = Query.of(query).iterationType(retrievalIterationType).build(); if (adjustedQuery.getPredicate() instanceof PagingPredicate) { ((PagingPredicate) adjustedQuery.getPredicate()).setIterationType(query.getIterationType()); } else { if (adjustedQuery.getPredicate() == TruePredicate.INSTANCE) { queryResultSizeLimiter.precheckMaxResultLimitOnLocalPartitions(adjustedQuery.getMapName()); } } return adjustedQuery; } // query thread first, fallback to partition thread private Result runQueryOnLocalPartitions(Query query) { Collection<Integer> mutablePartitionIds = getLocalPartitionIds(); Result result = doRunQueryOnQueryThreads(query, mutablePartitionIds, Target.LOCAL_NODE); if (isResultFromAnyPartitionMissing(mutablePartitionIds)) { doRunQueryOnPartitionThreads(query, mutablePartitionIds, result); } return result; } // query thread first, fallback to partition thread private Result runQueryOnAllPartitions(Query query) { Collection<Integer> mutablePartitionIds = getAllPartitionIds(); Result result = doRunQueryOnQueryThreads(query, mutablePartitionIds, Target.ALL_NODES); if (isResultFromAnyPartitionMissing(mutablePartitionIds)) { doRunQueryOnPartitionThreads(query, mutablePartitionIds, result); } return result; } // partition thread ONLY (for now) private Result runQueryOnGivenPartition(Query query, Target target) { try { return queryDispatcher.dispatchPartitionScanQueryOnOwnerMemberOnPartitionThread( query, target.getPartitionId()).get(); } catch (Throwable t) { throw rethrow(t); } } private Result doRunQueryOnQueryThreads(Query query, Collection<Integer> partitionIds, Target target) { Result result = resultProcessorRegistry.get(query.getResultType()).populateResult(query, queryResultSizeLimiter.getNodeResultLimit(partitionIds.size())); dispatchQueryOnQueryThreads(query, target, partitionIds, result); return result; } private void dispatchQueryOnQueryThreads(Query query, Target target, Collection<Integer> partitionIds, Result result) { try { List<Future<Result>> futures = queryDispatcher.dispatchFullQueryOnQueryThread(query, target); addResultsOfPredicate(futures, result, partitionIds); } catch (Throwable t) { if (t.getCause() instanceof QueryResultSizeExceededException) { throw rethrow(t); } logger.fine("Could not get results", t); } } private void doRunQueryOnPartitionThreads(Query query, Collection<Integer> partitionIds, Result result) { try { List<Future<Result>> futures = queryDispatcher.dispatchPartitionScanQueryOnOwnerMemberOnPartitionThread( query, partitionIds); addResultsOfPredicate(futures, result, partitionIds); } 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<Future<Result>> futures, Result result, Collection<Integer> partitionIds) throws ExecutionException, InterruptedException { for (Future<Result> future : futures) { Result queryResult = future.get(); if (queryResult == null) { continue; } Collection<Integer> queriedPartitionIds = queryResult.getPartitionIds(); if (queriedPartitionIds != null) { if (!partitionIds.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; } partitionIds.removeAll(queriedPartitionIds); result.combine(queryResult); } } } private IterationType getRetrievalIterationType(Predicate predicate, IterationType iterationType) { IterationType retrievalIterationType = iterationType; if (predicate instanceof PagingPredicate) { // in case of value, we also need to get the keys for sorting. retrievalIterationType = (iterationType == IterationType.VALUE) ? IterationType.ENTRY : iterationType; } return retrievalIterationType; } private List<Integer> getLocalPartitionIds() { return partitionService.getMemberPartitions(nodeEngine.getThisAddress()); } private Set<Integer> getAllPartitionIds() { int partitionCount = partitionService.getPartitionCount(); return createSetWithPopulatedPartitionIds(partitionCount); } private boolean isResultFromAnyPartitionMissing(Collection<Integer> partitionIds) { return !partitionIds.isEmpty(); } private static Set<Integer> createSetWithPopulatedPartitionIds(int partitionCount) { Set<Integer> partitionIds = new HashSet<Integer>(partitionCount); for (int i = 0; i < partitionCount; i++) { partitionIds.add(i); } return partitionIds; } protected QueryResultSizeLimiter getQueryResultSizeLimiter() { return queryResultSizeLimiter; } }