com.netflix.ndbench.core.DataBackfill Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2016 Netflix, 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 com.netflix.ndbench.core;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.netflix.ndbench.api.plugin.NdBenchAbstractClient;
import com.netflix.ndbench.core.config.IConfiguration;
/**
* @author vchella
*/
@Singleton
public class DataBackfill {
private static final Logger logger = LoggerFactory.getLogger(DataBackfill.class);
private final IConfiguration config;
private final AtomicBoolean stop = new AtomicBoolean(false);
private final AtomicReference threadPool = new AtomicReference<>(null);
private final AtomicInteger missCount = new AtomicInteger(0);
final AtomicInteger count = new AtomicInteger(0);
private final Random random = new Random();
private final AtomicReference> futureRef = new AtomicReference<>(null);
@Inject
public DataBackfill(IConfiguration config) {
this.config = config;
}
public void backfill(final NdBenchAbstractClient> client) throws Exception {
backfill(client, new NormalBackfill());
}
public void conditionalBackfill(final NdBenchAbstractClient> client) throws Exception {
backfill(client, new ConditionalBackfill());
}
public void verifyBackfill(final NdBenchAbstractClient> client) throws Exception {
backfill(client, new VerifyBackfill());
}
private void backfill(final NdBenchAbstractClient> client, final BackfillOperation backfillOperation) throws Exception {
long start = System.currentTimeMillis();
backfillAsync(client, backfillOperation);
logger.info("Backfiller waiting to finish");
futureRef.get();
logger.info("Backfiller latch done! in " + (System.currentTimeMillis() - start) + " ms");
}
public void backfillAsync(final NdBenchAbstractClient> client) {
backfillAsync(client, new NormalBackfill());
}
private void backfillAsync(final NdBenchAbstractClient> client, final BackfillOperation backfillOperation) {
stop.set(false);
//Default #Cores*4 so that we can keep the CPUs busy even while waiting on I/O
final int numThreads = Runtime.getRuntime().availableProcessors() * 4;
initThreadPool(numThreads);
List> keyRanges = getKeyRangesPerThread(numThreads,
config.getBackfillKeySlots(),
config.getNumKeys());
final CountDownLatch latch = new CountDownLatch(numThreads);
for (int i = 0; i < numThreads; i++) {
final int startKey = keyRanges.get(i).getLeft();
final int endKey = keyRanges.get(i).getRight();
threadPool.get().submit(() -> {
int k = startKey;
while (k < endKey && !stop.get()) {
try {
String key = "T" + k;
k++;
count.incrementAndGet();
String result = backfillOperation.process(client, key);
logger.info("Backfill Key:" + key + " | Result: " + result);
} catch (Exception e) {
logger.error("Exception in processing backfill write. Key: T{}", k, e);
}
}
latch.countDown();
logger.info("Stopping datafill writer");
return null;
});
}
Future future = threadPool.get().submit(() -> {
final AtomicBoolean stopCounting = new AtomicBoolean(false);
while (!Thread.currentThread().isInterrupted() && !stopCounting.get()) {
logger.info("Backfill so far: " + count.get() + ", miss count: " + missCount.get());
try {
boolean done = latch.await(5000, TimeUnit.MILLISECONDS);
if (done) {
stopCounting.set(true);
}
} catch (InterruptedException e) {
// return from here.
stopCounting.set(true);
}
}
logger.info("Stopping datafill status poller");
return null;
});
futureRef.set(future);
}
public boolean getIsBackfillRunning() {
Future future = futureRef.get();
if (future != null) {
if (future.isDone() || future.isCancelled()) {
return false; // Completed running or cancelled, so currently not running
}
return true; //Still running
}
return false; //Never started
}
private interface BackfillOperation {
String process(final NdBenchAbstractClient> client, final String key) throws Exception;
}
private class NormalBackfill implements BackfillOperation {
@Override
public String process(NdBenchAbstractClient> client, String key) throws Exception {
Object result = client.writeSingle(key);
return result == null ? "" : result.toString();
}
}
private class ConditionalBackfill implements BackfillOperation {
@Override
public String process(NdBenchAbstractClient> client, String key) throws Exception {
String result = client.readSingle(key);
if (result == null) {
missCount.incrementAndGet();
Object writeResult = client.writeSingle(key);
return writeResult == null ? "" : writeResult.toString();
}
return "done";
}
}
private class VerifyBackfill implements BackfillOperation {
@Override
public String process(NdBenchAbstractClient> client, String key) throws Exception {
Object result = client.writeSingle(key);
String value = client.readSingle(key);
if (value == null) {
missCount.incrementAndGet();
return "backfill miss: " + result;
} else {
return result == null ? "" : result.toString();
}
}
}
private void initThreadPool(int numThreads) {
if (threadPool.get() != null) {
throw new RuntimeException("Backfill already started");
}
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("ndbench-backfill-pool-%d")
.setDaemon(false).build();
ExecutorService newPool = Executors.newFixedThreadPool(numThreads + 1, threadFactory);
boolean success = threadPool.compareAndSet(null, newPool);
if (!success) {
newPool.shutdownNow();
throw new RuntimeException("Backfill already started");
}
}
public void stopBackfill() {
stop.set(true);
Future future = futureRef.get();
if (future != null) {
future.cancel(true);
}
shutdown();
}
public void shutdown() {
if (threadPool.get() != null) {
threadPool.get().shutdownNow();
threadPool.set(null);
}
}
/**
* This method returns the key range to be back filled in [1 - IConfiguration.getNumKeys()] keyspace.
* Algorithm to determine keyrange:
* NumKeys/BackfillKeySlots --> This gives the key range slots to be processed.
* Each worker randomly picks the slot from above. With low #BackfillKeySlots there is high probability
* to cover keyspace without any misses.
* @return
*/
List> getKeyRangesPerThread(int numThreads, int keySlots, int numKeys)
{
List> keyRangesPerThread = new LinkedList<>();
int slotSize = numKeys / keySlots;
int randomSlot = random.nextInt(keySlots);
int startKey = randomSlot * slotSize;
int endKey = startKey + slotSize;
int numKeysToProcess = endKey - startKey;
int numKeysPerThread = numKeysToProcess / numThreads;
logger.info("Num keys (KEYSPACE): {}, Num threads: {}, Num slots: {}", numKeys, numThreads, keySlots);
logger.info("MyNode: Num keys to be processed: {}, Num keys per thread: {}, My key slot: {}",
numKeysToProcess, numKeysPerThread, randomSlot);
for (int i = 0; i < numThreads; i++)
{
int startKeyPerThread = startKey + (i * numKeysPerThread);
int endKeyPerThread = startKeyPerThread + numKeysPerThread;
keyRangesPerThread.add(Pair.of(startKeyPerThread, endKeyPerThread));
}
return keyRangesPerThread;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy