
com.hazelcast.map.impl.eviction.ExpirationManager Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2016, 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.eviction;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.PartitionContainer;
import com.hazelcast.map.impl.recordstore.RecordStore;
import com.hazelcast.map.impl.operation.ClearExpiredOperation;
import com.hazelcast.partition.InternalPartition;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.OperationService;
import com.hazelcast.util.Clock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
// TODO refactor
/**
* Manages expiration operations.
*
* @since 3.3
*/
public class ExpirationManager {
private static final long INITIAL_DELAY = 5;
private static final long PERIOD = 5;
private static final TimeUnit UNIT = TimeUnit.SECONDS;
private final NodeEngine nodeEngine;
private final MapServiceContext mapServiceContext;
public ExpirationManager(MapServiceContext mapServiceContext, NodeEngine nodeEngine) {
this.nodeEngine = nodeEngine;
this.mapServiceContext = mapServiceContext;
}
public void start() {
nodeEngine.getExecutionService()
.scheduleAtFixedRate(new ClearExpiredRecordsTask(), INITIAL_DELAY, PERIOD, UNIT);
}
/**
* Periodically clears expired entries.(ttl & idle)
* This task provides per partition expiration operation logic. (not per map, not per record store).
* Fires cleanup operations at most partition operation thread count or some factor of it in one round.
*/
private class ClearExpiredRecordsTask implements Runnable {
private static final int EXPIRATION_PERCENTAGE = 10;
private static final long MIN_MILLIS_DIFF_BETWEEN_TWO_RUNS = 1000;
private final Comparator partitionContainerComparator = new Comparator() {
@Override
public int compare(PartitionContainer o1, PartitionContainer o2) {
final long s1 = o1.getLastCleanupTimeCopy();
final long s2 = o2.getLastCleanupTimeCopy();
return (s1 < s2) ? -1 : ((s1 == s2) ? 0 : 1);
}
};
@Override
public void run() {
final long now = Clock.currentTimeMillis();
final NodeEngine nodeEngine = ExpirationManager.this.nodeEngine;
final int partitionCount = nodeEngine.getPartitionService().getPartitionCount();
List partitionContainers = Collections.emptyList();
boolean createLazy = true;
int currentlyRunningCleanupOperationsCount = 0;
for (int partitionId = 0; partitionId < partitionCount; partitionId++) {
InternalPartition partition = nodeEngine.getPartitionService().getPartition(partitionId, false);
if (partition.isOwnerOrBackup(nodeEngine.getThisAddress())) {
final PartitionContainer partitionContainer = mapServiceContext.getPartitionContainer(partitionId);
if (isContainerEmpty(partitionContainer)) {
continue;
}
if (hasRunningCleanup(partitionContainer)) {
currentlyRunningCleanupOperationsCount++;
continue;
}
if (currentlyRunningCleanupOperationsCount > getMaxCleanupOperationCountInOneRound()
|| notInProcessableTimeWindow(partitionContainer, now)
|| notHaveAnyExpirableRecord(partitionContainer)) {
continue;
}
if (createLazy) {
partitionContainers = new ArrayList();
createLazy = false;
}
partitionContainers.add(partitionContainer);
}
}
if (partitionContainers.isEmpty()) {
return;
}
sortPartitionContainers(partitionContainers);
sendCleanupOperations(partitionContainers);
}
private void sortPartitionContainers(List partitionContainers) {
updateLastCleanupTimesBeforeSorting(partitionContainers);
Collections.sort(partitionContainers, partitionContainerComparator);
}
private void sendCleanupOperations(List partitionContainers) {
final int maxCleanupOperationCountInOneRound = getMaxCleanupOperationCountInOneRound();
final int start = 0;
int end = maxCleanupOperationCountInOneRound;
if (end > partitionContainers.size()) {
end = partitionContainers.size();
}
final List partitionIds = partitionContainers.subList(start, end);
for (PartitionContainer container : partitionIds) {
// mark partition container as has on going expiration operation.
container.setHasRunningCleanup(true);
OperationService operationService = ExpirationManager.this.nodeEngine.getOperationService();
operationService.executeOperation(createExpirationOperation(EXPIRATION_PERCENTAGE,
container.getPartitionId()));
}
}
private boolean expirable(RecordStore recordStore) {
return recordStore.isExpirable();
}
private boolean hasRunningCleanup(PartitionContainer partitionContainer) {
return partitionContainer.hasRunningCleanup();
}
private boolean notInProcessableTimeWindow(PartitionContainer partitionContainer, long now) {
return now - partitionContainer.getLastCleanupTime() < MIN_MILLIS_DIFF_BETWEEN_TWO_RUNS;
}
private int getMaxCleanupOperationCountInOneRound() {
final int times = 3;
return times * ExpirationManager.this.nodeEngine.getOperationService().getPartitionOperationThreadCount();
}
private boolean isContainerEmpty(PartitionContainer container) {
long size = 0L;
final ConcurrentMap maps = container.getMaps();
for (RecordStore store : maps.values()) {
size += store.size();
if (size > 0L) {
return false;
}
}
return true;
}
/**
* Here we check if that partition has any expirable record or not,
* if no expirable record exists in that partition no need to fire an expiration operation.
*
* @param partitionContainer corresponding partition container.
* @return true
if no expirable record in that partition false
otherwise.
*/
private boolean notHaveAnyExpirableRecord(PartitionContainer partitionContainer) {
boolean notExist = true;
final ConcurrentMap maps = partitionContainer.getMaps();
for (RecordStore store : maps.values()) {
if (expirable(store)) {
notExist = false;
break;
}
}
return notExist;
}
}
private Operation createExpirationOperation(int expirationPercentage, int partitionId) {
final ClearExpiredOperation clearExpiredOperation = new ClearExpiredOperation(expirationPercentage);
clearExpiredOperation
.setNodeEngine(nodeEngine)
.setCallerUuid(nodeEngine.getLocalMember().getUuid())
.setPartitionId(partitionId)
.setValidateTarget(false)
.setService(mapServiceContext.getService());
return clearExpiredOperation;
}
/**
* Sets last clean-up time before sorting.
*
* @param partitionContainers partition containers.
*/
private void updateLastCleanupTimesBeforeSorting(List partitionContainers) {
for (PartitionContainer partitionContainer : partitionContainers) {
partitionContainer.setLastCleanupTimeCopy(partitionContainer.getLastCleanupTime());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy