/*
* Copyright 2008-2013 LinkedIn, Inc
*
* 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 voldemort.server.storage;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.log4j.Logger;
import voldemort.annotations.jmx.JmxGetter;
import voldemort.server.StoreRepository;
import voldemort.store.StoreDefinition;
import voldemort.store.metadata.MetadataStore;
import voldemort.store.readonly.ReadOnlyStorageConfiguration;
import voldemort.utils.ByteArray;
import voldemort.utils.ClosableIterator;
import voldemort.utils.EventThrottler;
import voldemort.utils.Utils;
/**
* Base class for jobs that do some maintenance on the data stored in the server
*
*
*/
public abstract class DataMaintenanceJob implements Runnable {
public final static List<String> BLACKLISTED_STORAGE_TYPES = Arrays.asList(ReadOnlyStorageConfiguration.TYPE_NAME);
protected final static int STAT_RECORDS_INTERVAL = 10000;
protected final ScanPermitWrapper scanPermits;
protected final StoreRepository storeRepo;
protected final MetadataStore metadataStore;
protected ClosableIterator<ByteArray> iterator = null;
protected AtomicLong numKeysScannedThisRun;
protected AtomicLong numKeysUpdatedThisRun;
protected long totalKeysScanned = 0;
protected long totalKeysUpdated = 0;
protected AtomicBoolean isRunning;
protected final EventThrottler throttler;
public DataMaintenanceJob(StoreRepository storeRepo,
MetadataStore metadataStore,
ScanPermitWrapper scanPermits,
int maxRatePerSecond) {
this.storeRepo = storeRepo;
this.metadataStore = metadataStore;
this.scanPermits = Utils.notNull(scanPermits);
this.numKeysScannedThisRun = new AtomicLong(0);
this.numKeysUpdatedThisRun = new AtomicLong(0);
this.isRunning = new AtomicBoolean(false);
this.throttler = new EventThrottler(maxRatePerSecond);
}
public DataMaintenanceJob(StoreRepository storeRepo,
MetadataStore metadataStore,
ScanPermitWrapper scanPermits) {
this(storeRepo, metadataStore, scanPermits, Integer.MAX_VALUE);
}
@Override
public void run() {
// don't do maintenance when the server is already neither normal nor
// offline
if(!isServerNormal() && !isServerOffline()) {
getLogger().error("Cannot run "
+ getJobName()
+ " since Voldemort server is neither in normal state nor in offline state");
return;
}
isRunning.set(true);
Date startTime = new Date();
getLogger().info("Started " + getJobName() + " at " + startTime);
if(!acquireScanPermit()) {
isRunning.set(false);
return;
}
// actually operate the job
try {
operate();
} catch(Exception e) {
getLogger().error("Error running " + getJobName(), e);
} finally {
closeIterator(iterator);
this.scanPermits.release(this.getClass().getCanonicalName());
resetStats();
getLogger().info("Completed " + getJobName() + " started at " + startTime);
isRunning.set(false);
}
}
abstract public void operate() throws Exception;
abstract protected Logger getLogger();
abstract protected String getJobName();
private boolean isServerNormal() {
return metadataStore.getServerStateUnlocked()
.equals(MetadataStore.VoldemortState.NORMAL_SERVER);
}
private boolean isServerOffline() {
return metadataStore.getServerStateUnlocked()
.equals(MetadataStore.VoldemortState.OFFLINE_SERVER);
}
protected boolean isWritableStore(StoreDefinition storeDef) {
if(!storeDef.isView() && !BLACKLISTED_STORAGE_TYPES.contains(storeDef.getType())) {
return true;
} else {
return false;
}
}
private boolean acquireScanPermit() {
getLogger().info("Acquiring lock to perform " + getJobName());
if(this.scanPermits.tryAcquire(this.numKeysScannedThisRun,
this.numKeysUpdatedThisRun,
this.getClass().getCanonicalName())) {
getLogger().info("Acquired lock to perform " + getJobName());
return true;
} else {
getLogger().error("Aborting " + getJobName()
+ " since another instance is already running! ");
return false;
}
}
protected void closeIterator(ClosableIterator<ByteArray> iterator) {
try {
if(iterator != null) {
iterator.close();
iterator = null;
}
} catch(Exception e) {
getLogger().error("Error in closing iterator", e);
}
}
/**
* Determines whether this job is already runnning.. Note that this only
* protects against scheduling the same job twice by mistake from Admin tool
* (most practical use case)
*
* Note: There can still be a race when two threads find this value to be
* false and both attempt to execute the job
*
* @return
*/
public AtomicBoolean getIsRunning() {
return isRunning;
}
@JmxGetter(name = "numKeysScanned", description = "Returns number of keys scanned")
public synchronized long getKeysScanned() {
return totalKeysScanned + numKeysScannedThisRun.get();
}
protected synchronized void resetStats() {
totalKeysScanned += numKeysScannedThisRun.get();
numKeysScannedThisRun.set(0);
totalKeysUpdated += numKeysUpdatedThisRun.get();
numKeysUpdatedThisRun.set(0);
}
}