package org.gbif.occurrence.processor.zookeeper;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.beust.jcommander.internal.Lists;
import com.beust.jcommander.internal.Maps;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.atomic.AtomicValue;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong;
import org.apache.curator.retry.RetryUntilElapsed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is a simple wrapper around a DistributedAtomicLong that will flush periodically to ZooKeeper. It's meant as a
* patch to solve the massive contention problems that DALs have when many threads try to increment at the same time.
* Note that counts could be lost if the client application dies.
*/
public final class BatchingDalWrapper {
private static final Logger LOG = LoggerFactory.getLogger(BatchingDalWrapper.class);
private final CuratorFramework client;
private final List<String> paths = Lists.newArrayList();
public BatchingDalWrapper(CuratorFramework client, long flushFrequencyMsecs) {
this.client = client;
new Thread(new Flusher(flushFrequencyMsecs)).start();
}
public void increment(String path) {
synchronized (paths) {
paths.add(path);
}
}
private class Flusher implements Runnable {
private final long flushFrequencyMsecs;
private Flusher(long flushFrequencyMsecs) {
this.flushFrequencyMsecs = flushFrequencyMsecs;
}
@Override
public void run() {
while (true) {
List<String> copy;
synchronized (paths) {
copy = Lists.newArrayList(paths);
paths.clear();
}
// merge additions
Map<String, AtomicLong> mutations = Maps.newHashMap();
for (String path : copy) {
if (mutations.containsKey(path)) {
mutations.get(path).incrementAndGet();
} else {
mutations.put(path, new AtomicLong(1));
}
}
for (Map.Entry<String, AtomicLong> entry : mutations.entrySet()) {
try {
DistributedAtomicLong dal = new DistributedAtomicLong(client, entry.getKey(),
new RetryUntilElapsed((int) TimeUnit.MINUTES.toMillis(5), (int) TimeUnit.MILLISECONDS.toMillis(25)));
AtomicValue<Long> result = dal.add(entry.getValue().get());
if (!result.succeeded()) {
LOG.warn("Counter updates are failing and we've exhausted retry - counts will be wrong");
}
} catch (Exception e) {
LOG.warn("Failed to update DALs during flush - counts will be wrong", e);
}
}
try {
Thread.sleep(flushFrequencyMsecs); // not a true time of course
} catch (InterruptedException e1) {
break; // really?
}
}
}
}
}