/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.mongodb.repl.oplogreplier.batch;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.eightkdata.mongowp.server.api.tools.Empty;
import com.google.common.base.Supplier;
import com.torodb.core.concurrent.ConcurrentToolsFactory;
import com.torodb.core.concurrent.StreamExecutor;
import com.torodb.core.exceptions.user.UserException;
import com.torodb.core.metrics.ToroMetricRegistry;
import com.torodb.core.retrier.Retrier;
import com.torodb.core.transaction.RollbackException;
import com.torodb.mongodb.core.MongodConnection;
import com.torodb.mongodb.core.MongodServer;
import com.torodb.mongodb.repl.OplogManager.OplogManagerPersistException;
import com.torodb.mongodb.repl.oplogreplier.ApplierContext;
import com.torodb.mongodb.repl.oplogreplier.OplogOperationApplier;
import com.torodb.mongodb.repl.oplogreplier.analyzed.AnalyzedOp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
public class ConcurrentOplogBatchExecutor extends SimpleAnalyzedOplogBatchExecutor {
private final StreamExecutor streamExecutor;
private final ConcurrentOplogBatchExecutorMetrics concurrentMetrics;
private final SubBatchHeuristic subBatchHeuristic;
@Inject
public ConcurrentOplogBatchExecutor(OplogOperationApplier oplogOperationApplier,
MongodServer server, Retrier retrier, ConcurrentToolsFactory concurrentToolsFactory,
NamespaceJobExecutor namespaceJobExecutor,
ConcurrentOplogBatchExecutorMetrics concurrentMetrics, SubBatchHeuristic subBatchHeuristic) {
super(concurrentMetrics, oplogOperationApplier, server, retrier, namespaceJobExecutor);
this.streamExecutor = concurrentToolsFactory.createStreamExecutor(
"concurrent-oplog-batch-executor", true);
this.concurrentMetrics = concurrentMetrics;
this.subBatchHeuristic = subBatchHeuristic;
}
@Override
protected void doStart() {
super.doStart();
streamExecutor.startAsync();
streamExecutor.awaitRunning();
}
@Override
protected void doStop() {
streamExecutor.stopAsync();
streamExecutor.awaitTerminated();
super.doStop();
}
@Override
public void execute(CudAnalyzedOplogBatch cudBatch, ApplierContext context) throws UserException {
List<NamespaceJob> namespaceJobList = cudBatch.streamNamespaceJobs().flatMap(this::split)
.collect(Collectors.toList());
concurrentMetrics.getSubBatchSizeMeter().mark(namespaceJobList.size());
concurrentMetrics.getSubBatchSizeHistogram().update(namespaceJobList.size());
Stream<Callable<Empty>> callables = namespaceJobList.stream()
.map((Function<NamespaceJob, Callable<Empty>>) (NamespaceJob namespaceJob) -> () -> {
execute(namespaceJob, context);
return Empty.getInstance();
});
try {
streamExecutor.execute(callables)
.join();
} catch (CompletionException ex) {
Throwable cause = ex.getCause();
if (cause instanceof UserException) {
throw (UserException) cause;
} else if (cause instanceof RollbackException) {
throw (RollbackException) cause;
}
throw ex;
}
}
private void execute(NamespaceJob job, ApplierContext applierContext)
throws OplogManagerPersistException, UserException, NamespaceJobExecutionException {
try (MongodConnection connection = getServer().openConnection()) {
execute(job, applierContext, connection);
}
}
private Stream<NamespaceJob> split(NamespaceJob namespaceJob) {
Collection<AnalyzedOp> jobs = namespaceJob.getJobs();
int subBatchSize = subBatchHeuristic.getSubBatchSize(concurrentMetrics);
assert subBatchSize > 0 : "Sub batch size must be positive";
Supplier<List<AnalyzedOp>> currentListFactory = () -> new ArrayList<>(subBatchSize);
List<NamespaceJob> result = new ArrayList<>(1 + jobs.size() / subBatchSize);
List<AnalyzedOp> currentList = null;
for (AnalyzedOp job : jobs) {
if (currentList == null) {
currentList = currentListFactory.get();
}
currentList.add(job);
if (currentList.size() >= subBatchSize) {
result.add(new NamespaceJob(namespaceJob.getDatabase(), namespaceJob.getCollection(),
currentList));
currentList = currentListFactory.get();
}
assert currentList.size() <= subBatchSize : "Created a subatch whose size is "
+ currentList.size() + " but heuristic says max subatch size is " + subBatchSize;
}
if (currentList != null) {
result.add(new NamespaceJob(namespaceJob.getDatabase(), namespaceJob.getCollection(),
currentList));
}
return result.stream();
}
public static class ConcurrentOplogBatchExecutorMetrics
extends AnalyzedOplogBatchExecutorMetrics {
private final Meter subBatchSizeMeter;
private final Histogram subBatchSizeHistogram;
@Inject
public ConcurrentOplogBatchExecutorMetrics(ToroMetricRegistry metricRegistry) {
super(metricRegistry);
this.subBatchSizeMeter = metricRegistry.meter(NAME_FACTORY.createMetricName(
"subBatchSizeMeter"));
this.subBatchSizeHistogram = metricRegistry.histogram(NAME_FACTORY.createMetricName(
"subBatchSizeHistogram"));
}
public Meter getSubBatchSizeMeter() {
return subBatchSizeMeter;
}
public Histogram getSubBatchSizeHistogram() {
return subBatchSizeHistogram;
}
}
public static interface SubBatchHeuristic {
/**
* Given some metrics, this heuristic returns number of {@link AnalyzedOp ops} that each sub
* batch should have.
*
* @param metrics
* @return a positive integer
*/
public int getSubBatchSize(ConcurrentOplogBatchExecutorMetrics metrics);
}
}