allow to flush bulk processor based on time, and better listener api
This commit is contained in:
parent
ab49a8c2fc
commit
f317399b46
|
@ -19,48 +19,84 @@
|
||||||
|
|
||||||
package org.elasticsearch.action.bulk;
|
package org.elasticsearch.action.bulk;
|
||||||
|
|
||||||
|
import org.elasticsearch.ElasticSearchIllegalStateException;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.action.ActionRequest;
|
import org.elasticsearch.action.ActionRequest;
|
||||||
import org.elasticsearch.action.delete.DeleteRequest;
|
import org.elasticsearch.action.delete.DeleteRequest;
|
||||||
import org.elasticsearch.action.index.IndexRequest;
|
import org.elasticsearch.action.index.IndexRequest;
|
||||||
import org.elasticsearch.client.Client;
|
import org.elasticsearch.client.Client;
|
||||||
|
import org.elasticsearch.client.internal.InternalClient;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.unit.ByteSizeUnit;
|
import org.elasticsearch.common.unit.ByteSizeUnit;
|
||||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||||
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
import org.elasticsearch.common.util.concurrent.EsExecutors;
|
||||||
|
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A bulk processor is a thread safe bulk processing class, allowing to easily set when to "flush" a new bulk request
|
* A bulk processor is a thread safe bulk processing class, allowing to easily set when to "flush" a new bulk request
|
||||||
* (either based on number of actions, or based on the size), and to easily control the number of concurrent bulk
|
* (either based on number of actions, based on the size, or time), and to easily control the number of concurrent bulk
|
||||||
* requests allowed to be executed in parallel.
|
* requests allowed to be executed in parallel.
|
||||||
* <p/>
|
* <p/>
|
||||||
* In order to create a new bulk processor, use the {@link Builder}.
|
* In order to create a new bulk processor, use the {@link Builder}.
|
||||||
*/
|
*/
|
||||||
public class BulkProcessor {
|
public class BulkProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for the execution.
|
||||||
|
*/
|
||||||
|
public static interface Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback before the bulk is executed.
|
||||||
|
*/
|
||||||
|
void beforeBulk(long executionId, BulkRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback after a successful execution of bulk request.
|
||||||
|
*/
|
||||||
|
void afterBulk(long executionId, BulkRequest request, BulkResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback after a failed execution of bulk request.
|
||||||
|
*/
|
||||||
|
void afterBulk(long executionId, BulkRequest request, Throwable failure);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder used to create a build an instance of a bulk processor.
|
* A builder used to create a build an instance of a bulk processor.
|
||||||
*/
|
*/
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private final ActionListener<BulkResponse> listener;
|
private final Listener listener;
|
||||||
|
|
||||||
|
private String name;
|
||||||
private int concurrentRequests = 1;
|
private int concurrentRequests = 1;
|
||||||
private int bulkActions = 1000;
|
private int bulkActions = 1000;
|
||||||
private ByteSizeValue bulkSize = new ByteSizeValue(5, ByteSizeUnit.MB);
|
private ByteSizeValue bulkSize = new ByteSizeValue(5, ByteSizeUnit.MB);
|
||||||
|
private TimeValue flushInterval = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a builder of bulk processor with the client to use and the listener that will be used
|
* Creates a builder of bulk processor with the client to use and the listener that will be used
|
||||||
* to be notified on the completion of bulk requests.
|
* to be notified on the completion of bulk requests.
|
||||||
*/
|
*/
|
||||||
public Builder(Client client, ActionListener<BulkResponse> listener) {
|
public Builder(Client client, Listener listener) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an optional name to identify this bulk processor.
|
||||||
|
*/
|
||||||
|
public Builder setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the number of concurrent requests allowed to be executed. A value of 0 means that only a single
|
* Sets the number of concurrent requests allowed to be executed. A value of 0 means that only a single
|
||||||
* request will be allowed to be executed. A value of 1 means 1 concurrent request is allowed to be executed
|
* request will be allowed to be executed. A value of 1 means 1 concurrent request is allowed to be executed
|
||||||
|
@ -89,38 +125,87 @@ public class BulkProcessor {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a flush interval flushing *any* bulk actions pending if the interval passes. Defaults to not set.
|
||||||
|
* <p/>
|
||||||
|
* Note, both {@link #setBulkActions(int)} and {@link #setBulkSize(org.elasticsearch.common.unit.ByteSizeValue)}
|
||||||
|
* can be set to <tt>-1</tt> with the flush interval set allowing for complete async processing of bulk actions.
|
||||||
|
*/
|
||||||
|
public Builder setFlushInterval(TimeValue flushInterval) {
|
||||||
|
this.flushInterval = flushInterval;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a new bulk processor.
|
* Builds a new bulk processor.
|
||||||
*/
|
*/
|
||||||
public BulkProcessor build() {
|
public BulkProcessor build() {
|
||||||
return new BulkProcessor(client, listener, concurrentRequests, bulkActions, bulkSize);
|
return new BulkProcessor(client, listener, name, concurrentRequests, bulkActions, bulkSize, flushInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(Client client, ActionListener<BulkResponse> listener) {
|
public static Builder builder(Client client, Listener listener) {
|
||||||
return new Builder(client, listener);
|
return new Builder(client, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private final ActionListener<BulkResponse> listener;
|
private final Listener listener;
|
||||||
|
|
||||||
private int concurrentRequests;
|
private final String name;
|
||||||
|
|
||||||
|
private final int concurrentRequests;
|
||||||
private final int bulkActions;
|
private final int bulkActions;
|
||||||
private final int bulkSize;
|
private final int bulkSize;
|
||||||
|
private final TimeValue flushInterval;
|
||||||
|
|
||||||
private final Semaphore semaphore;
|
private final Semaphore semaphore;
|
||||||
|
private final ScheduledThreadPoolExecutor scheduler;
|
||||||
|
private final ScheduledFuture scheduledFuture;
|
||||||
|
|
||||||
|
private final AtomicLong executionIdGen = new AtomicLong();
|
||||||
|
|
||||||
private BulkRequest bulkRequest;
|
private BulkRequest bulkRequest;
|
||||||
|
|
||||||
BulkProcessor(Client client, ActionListener<BulkResponse> listener, int concurrentRequests, int bulkActions, ByteSizeValue bulkSize) {
|
private volatile boolean closed = false;
|
||||||
|
|
||||||
|
BulkProcessor(Client client, Listener listener, @Nullable String name, int concurrentRequests, int bulkActions, ByteSizeValue bulkSize, @Nullable TimeValue flushInterval) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
|
this.name = name;
|
||||||
this.concurrentRequests = concurrentRequests;
|
this.concurrentRequests = concurrentRequests;
|
||||||
this.bulkActions = bulkActions;
|
this.bulkActions = bulkActions;
|
||||||
this.bulkSize = bulkSize.bytesAsInt();
|
this.bulkSize = bulkSize.bytesAsInt();
|
||||||
|
|
||||||
this.semaphore = new Semaphore(concurrentRequests);
|
this.semaphore = new Semaphore(concurrentRequests);
|
||||||
this.bulkRequest = new BulkRequest();
|
this.bulkRequest = new BulkRequest();
|
||||||
|
|
||||||
|
this.flushInterval = flushInterval;
|
||||||
|
if (flushInterval != null) {
|
||||||
|
this.scheduler = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1, EsExecutors.daemonThreadFactory(((InternalClient) client).settings(), (name != null ? "[" + name + "]" : "") + "bulk_processor"));
|
||||||
|
this.scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||||
|
this.scheduler.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
|
||||||
|
this.scheduledFuture = this.scheduler.scheduleWithFixedDelay(new Flush(), flushInterval.millis(), flushInterval.millis(), TimeUnit.MILLISECONDS);
|
||||||
|
} else {
|
||||||
|
this.scheduler = null;
|
||||||
|
this.scheduledFuture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the processor. If flushing by time is enabled, then its shutdown. Any remaining bulk actions are flushed.
|
||||||
|
*/
|
||||||
|
public synchronized void close() {
|
||||||
|
if (closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closed = true;
|
||||||
|
if (this.scheduledFuture != null) {
|
||||||
|
this.scheduledFuture.cancel(false);
|
||||||
|
this.scheduler.shutdown();
|
||||||
|
}
|
||||||
|
if (bulkRequest.numberOfActions() > 0) {
|
||||||
|
execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,28 +240,44 @@ public class BulkProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeIfNeeded() {
|
private void executeIfNeeded() {
|
||||||
|
if (closed) {
|
||||||
|
throw new ElasticSearchIllegalStateException("bulk process already closed");
|
||||||
|
}
|
||||||
|
this.closed = true;
|
||||||
if (!isOverTheLimit()) {
|
if (!isOverTheLimit()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// (currently) needs to be executed under a lock
|
||||||
|
private void execute() {
|
||||||
|
final BulkRequest bulkRequest = this.bulkRequest;
|
||||||
|
final long executionId = executionIdGen.incrementAndGet();
|
||||||
|
|
||||||
|
this.bulkRequest = new BulkRequest();
|
||||||
|
|
||||||
if (concurrentRequests == 0) {
|
if (concurrentRequests == 0) {
|
||||||
// execute in a blocking fashion...
|
// execute in a blocking fashion...
|
||||||
try {
|
try {
|
||||||
listener.onResponse(client.bulk(bulkRequest).actionGet());
|
listener.beforeBulk(executionId, bulkRequest);
|
||||||
|
listener.afterBulk(executionId, bulkRequest, client.bulk(bulkRequest).actionGet());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
listener.onFailure(e);
|
listener.afterBulk(executionId, bulkRequest, e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
semaphore.acquire();
|
semaphore.acquire();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
listener.onFailure(e);
|
listener.afterBulk(executionId, bulkRequest, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
listener.beforeBulk(executionId, bulkRequest);
|
||||||
client.bulk(bulkRequest, new ActionListener<BulkResponse>() {
|
client.bulk(bulkRequest, new ActionListener<BulkResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(BulkResponse response) {
|
public void onResponse(BulkResponse response) {
|
||||||
try {
|
try {
|
||||||
listener.onResponse(response);
|
listener.afterBulk(executionId, bulkRequest, response);
|
||||||
} finally {
|
} finally {
|
||||||
semaphore.release();
|
semaphore.release();
|
||||||
}
|
}
|
||||||
|
@ -185,14 +286,13 @@ public class BulkProcessor {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Throwable e) {
|
public void onFailure(Throwable e) {
|
||||||
try {
|
try {
|
||||||
listener.onFailure(e);
|
listener.afterBulk(executionId, bulkRequest, e);
|
||||||
} finally {
|
} finally {
|
||||||
semaphore.release();
|
semaphore.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
bulkRequest = new BulkRequest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOverTheLimit() {
|
private boolean isOverTheLimit() {
|
||||||
|
@ -204,4 +304,20 @@ public class BulkProcessor {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Flush implements Runnable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (BulkProcessor.this) {
|
||||||
|
if (closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bulkRequest.numberOfActions() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package org.elasticsearch.client.internal;
|
package org.elasticsearch.client.internal;
|
||||||
|
|
||||||
import org.elasticsearch.client.Client;
|
import org.elasticsearch.client.Client;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,5 +28,7 @@ import org.elasticsearch.threadpool.ThreadPool;
|
||||||
*/
|
*/
|
||||||
public interface InternalClient extends Client {
|
public interface InternalClient extends Client {
|
||||||
|
|
||||||
|
Settings settings();
|
||||||
|
|
||||||
ThreadPool threadPool();
|
ThreadPool threadPool();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class NodeClient extends AbstractClient implements InternalClient {
|
public class NodeClient extends AbstractClient implements InternalClient {
|
||||||
|
|
||||||
|
private final Settings settings;
|
||||||
private final ThreadPool threadPool;
|
private final ThreadPool threadPool;
|
||||||
|
|
||||||
private final NodeAdminClient admin;
|
private final NodeAdminClient admin;
|
||||||
|
@ -45,6 +46,7 @@ public class NodeClient extends AbstractClient implements InternalClient {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public NodeClient(Settings settings, ThreadPool threadPool, NodeAdminClient admin, Map<GenericAction, TransportAction> actions) {
|
public NodeClient(Settings settings, ThreadPool threadPool, NodeAdminClient admin, Map<GenericAction, TransportAction> actions) {
|
||||||
|
this.settings = settings;
|
||||||
this.threadPool = threadPool;
|
this.threadPool = threadPool;
|
||||||
this.admin = admin;
|
this.admin = admin;
|
||||||
MapBuilder<Action, TransportAction> actionsBuilder = new MapBuilder<Action, TransportAction>();
|
MapBuilder<Action, TransportAction> actionsBuilder = new MapBuilder<Action, TransportAction>();
|
||||||
|
@ -56,6 +58,11 @@ public class NodeClient extends AbstractClient implements InternalClient {
|
||||||
this.actions = actionsBuilder.immutableMap();
|
this.actions = actionsBuilder.immutableMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings settings() {
|
||||||
|
return this.settings;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThreadPool threadPool() {
|
public ThreadPool threadPool() {
|
||||||
return this.threadPool;
|
return this.threadPool;
|
||||||
|
|
|
@ -271,6 +271,11 @@ public class TransportClient extends AbstractClient {
|
||||||
ThreadLocals.clearReferencesThreadLocals();
|
ThreadLocals.clearReferencesThreadLocals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings settings() {
|
||||||
|
return this.settings;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThreadPool threadPool() {
|
public ThreadPool threadPool() {
|
||||||
return internalClient.threadPool();
|
return internalClient.threadPool();
|
||||||
|
|
|
@ -40,6 +40,7 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class InternalTransportClient extends AbstractClient implements InternalClient {
|
public class InternalTransportClient extends AbstractClient implements InternalClient {
|
||||||
|
|
||||||
|
private final Settings settings;
|
||||||
private final ThreadPool threadPool;
|
private final ThreadPool threadPool;
|
||||||
|
|
||||||
private final TransportClientNodesService nodesService;
|
private final TransportClientNodesService nodesService;
|
||||||
|
@ -52,6 +53,7 @@ public class InternalTransportClient extends AbstractClient implements InternalC
|
||||||
public InternalTransportClient(Settings settings, ThreadPool threadPool, TransportService transportService,
|
public InternalTransportClient(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||||
TransportClientNodesService nodesService, InternalTransportAdminClient adminClient,
|
TransportClientNodesService nodesService, InternalTransportAdminClient adminClient,
|
||||||
Map<String, GenericAction> actions) {
|
Map<String, GenericAction> actions) {
|
||||||
|
this.settings = settings;
|
||||||
this.threadPool = threadPool;
|
this.threadPool = threadPool;
|
||||||
this.nodesService = nodesService;
|
this.nodesService = nodesService;
|
||||||
this.adminClient = adminClient;
|
this.adminClient = adminClient;
|
||||||
|
@ -70,6 +72,11 @@ public class InternalTransportClient extends AbstractClient implements InternalC
|
||||||
// nothing to do here
|
// nothing to do here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings settings() {
|
||||||
|
return this.settings;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThreadPool threadPool() {
|
public ThreadPool threadPool() {
|
||||||
return this.threadPool;
|
return this.threadPool;
|
||||||
|
|
Loading…
Reference in New Issue