Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pubsub ordering keys #5624

Merged
merged 72 commits into from
Jul 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
41adb13
Merge from master. (#4468)
kamalaboulhosn Feb 8, 2019
7793aa3
Merge branch 'pubsub-ordering-keys' of github.com:GoogleCloudPlatform…
sduskis Feb 8, 2019
41430b2
Add SequentialExecutorService that assists Pub/Sub to publish message…
kimkyung-goog Feb 8, 2019
81ea157
Add ordered publishing support to Cloud Pub/Sub (#4474)
kamalaboulhosn Feb 15, 2019
fab0188
Pubsub ordering keys subscriber (#4515)
kamalaboulhosn Feb 20, 2019
858d4e9
Change the order of publishing batches when a large message is reques…
kimkyung-goog Mar 4, 2019
c2c8b5c
Merge from master; Resolve conflicts (#4943)
kimkyung-goog Apr 15, 2019
ad54d69
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 15, 2019
e026c5c
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 15, 2019
cc44d4b
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 16, 2019
e492b04
Update Publisher.java
sduskis Apr 16, 2019
3f9a663
Fixing format
sduskis Apr 16, 2019
e88432a
Merge branch 'pubsub-ordering-keys' of github.com:GoogleCloudPlatform…
sduskis Apr 16, 2019
7732506
Refactoring of the Pub/Sub Ordering keys client (#4962)
sduskis Apr 17, 2019
b68add0
[WIP] Refactoring SequentialExecutorService (#4969)
sduskis Apr 17, 2019
54420a4
Running the formatter.
sduskis Apr 18, 2019
7f96c1a
Merge remote-tracking branch 'upstream/master' into merge_master
sduskis Apr 18, 2019
6be9e3d
Merged with the formatting changes. (#4978)
sduskis Apr 18, 2019
7487e88
Fixng a bad merge.
sduskis Apr 18, 2019
708d785
Refactoring SequentialExecutorService.java (#4979)
sduskis Apr 18, 2019
d556ce4
Cleaning up generics in SequentialExecutorService (#4982)
sduskis Apr 18, 2019
4a26abe
Adding comments to CallbackExecutor.submit (#4981)
sduskis Apr 18, 2019
18aca86
Exposing AutoExecutor and CallbackExecutor directly (#4983)
sduskis Apr 18, 2019
b06fd9e
More refactoring to SequentialExecutor (#4984)
sduskis Apr 19, 2019
40e83a1
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 22, 2019
43f2ff3
Merge branch 'pubsub-ordering-keys' of github.com:googleapis/google-c…
sduskis Apr 22, 2019
d661492
SequentialExecutorService.callNextTaskAsync only uses key (#4992)
sduskis Apr 22, 2019
dd8a5ce
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 22, 2019
97ba17c
SequentialExecutorService now uses generics for Runnables.
sduskis Apr 22, 2019
02e06ae
Using a Queue instead of Deque.
sduskis Apr 22, 2019
c90b83c
Renaming a variable in Publisher
sduskis Apr 22, 2019
956537c
More refactoring to SequentialExecutorService.
sduskis Apr 23, 2019
75acad9
Running the formatter
sduskis Apr 24, 2019
5480455
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 25, 2019
d98f981
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 25, 2019
3bdbf77
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 25, 2019
41b9975
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 28, 2019
42697e2
Reformatting the publisher
sduskis Apr 28, 2019
fd07e9e
Pub/Sub: publishAll defers sending batches.
sduskis Apr 29, 2019
74daec8
Reverting last change to publishAllOutstanding
sduskis Apr 30, 2019
dd8db2e
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 30, 2019
9259fe6
Merge branch 'master' into pubsub-ordering-keys
sduskis Apr 30, 2019
8573e5a
Cleaning up messageBatch entries
sduskis May 1, 2019
ec2ecc6
Alarms now wait to publish until previous bundle completes
sduskis May 1, 2019
0b00228
Using Preconditions in Publisher.
sduskis May 1, 2019
142196c
Merge branch 'master' into pubsub-ordering-keys
sduskis May 1, 2019
92a7bf4
The Publisher's callback should happen second
sduskis May 1, 2019
32f4e23
Merge branch 'master' into pubsub-ordering-keys
sduskis May 1, 2019
f6fcbed
Fixing a flaky test
sduskis May 1, 2019
c918164
Adding resume publish. (#5046)
sduskis May 2, 2019
6073e7e
Fixing formatting issues
sduskis May 2, 2019
5a81613
Adding comments
sduskis May 3, 2019
e9e754c
Fixing formatting
sduskis May 13, 2019
33ebb40
Ordering keys setter is now package private
sduskis May 15, 2019
cec8e94
Cleanup before merge to main line
sduskis May 15, 2019
2400120
Merge branch 'master' into pubsub-ordering-keys
sduskis May 23, 2019
52c7c93
Merge branch 'master' into pubsub-ordering-keys
sduskis May 29, 2019
e463a05
Fixing lint
sduskis May 29, 2019
c01aa11
Merge branch 'master' into pubsub-ordering-keys
sduskis May 31, 2019
3087968
publish() doesn't use an executor.
sduskis May 31, 2019
da6dd3c
Moving the publish RPC call into the lock
sduskis May 31, 2019
ede8436
Updating GAX in clients to 1.45.0 for docs.
sduskis Jun 5, 2019
ae78886
Merge remote-tracking branch 'upstream/master'
sduskis Jun 11, 2019
99d1396
Merge remote-tracking branch 'upstream/master'
sduskis Jun 11, 2019
cf6d528
Merge remote-tracking branch 'upstream/master'
sduskis Jun 11, 2019
3ea3dc9
Merge branch 'master' into pubsub-ordering-keys
sduskis Jun 11, 2019
42399d2
Merge remote-tracking branch 'upstream/master' into pubsub-ordering-keys
sduskis Jun 24, 2019
16da3aa
Fix publisher throughput when there are no ordering keys. (#5607)
kamalaboulhosn Jul 1, 2019
549a962
Make appropriate things private, remove unnecessary call to publishAl…
kamalaboulhosn Jul 2, 2019
3ea314b
Fix publisher formatting
kamalaboulhosn Jul 3, 2019
3b1c817
Experimental removal of test to see if it is the one causing timeouts.
kamalaboulhosn Jul 8, 2019
d26d344
Fix comment
kamalaboulhosn Jul 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class MessageDispatcher {
@InternalApi static final Duration PENDING_ACKS_SEND_DELAY = Duration.ofMillis(100);

private final Executor executor;
private final SequentialExecutorService.AutoExecutor sequentialExecutor;
private final ScheduledExecutorService systemExecutor;
private final ApiClock clock;

Expand Down Expand Up @@ -206,6 +207,7 @@ void sendAckOperations(
jobLock = new ReentrantLock();
messagesWaiter = new MessageWaiter();
this.clock = clock;
this.sequentialExecutor = new SequentialExecutorService.AutoExecutor(executor);
}

void start() {
Expand Down Expand Up @@ -358,7 +360,7 @@ public void nack() {
}
};
ApiFutures.addCallback(response, ackHandler, MoreExecutors.directExecutor());
executor.execute(
Runnable deliverMessageTask =
new Runnable() {
@Override
public void run() {
Expand All @@ -379,7 +381,12 @@ public void run() {
response.setException(e);
}
}
});
};
if (message.getOrderingKey().isEmpty()) {
executor.execute(deliverMessageTask);
} else {
sequentialExecutor.submit(message.getOrderingKey(), deliverMessageTask);
}
}

/** Compute the ideal deadline, set subsequent modacks to this deadline, and return it. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@
import com.google.pubsub.v1.TopicNames;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -85,15 +88,20 @@ public class Publisher {
private final String topicName;

private final BatchingSettings batchingSettings;
private final boolean enableMessageOrdering;

private final Lock messagesBatchLock;
private MessagesBatch messagesBatch;

private final Map<String, MessagesBatch> messagesBatches;

private final AtomicBoolean activeAlarm;

private final PublisherStub publisherStub;

private final ScheduledExecutorService executor;

private final SequentialExecutorService.CallbackExecutor sequentialExecutor;

private final AtomicBoolean shutdown;
private final BackgroundResource backgroundResources;
private final MessageWaiter messagesWaiter;
Expand All @@ -114,22 +122,33 @@ private Publisher(Builder builder) throws IOException {
topicName = builder.topicName;

this.batchingSettings = builder.batchingSettings;
this.enableMessageOrdering = builder.enableMessageOrdering;
this.messageTransform = builder.messageTransform;

messagesBatch = new MessagesBatch(batchingSettings);
messagesBatches = new HashMap<>();
messagesBatchLock = new ReentrantLock();
activeAlarm = new AtomicBoolean(false);
executor = builder.executorProvider.getExecutor();
sequentialExecutor = new SequentialExecutorService.CallbackExecutor(executor);
List<BackgroundResource> backgroundResourceList = new ArrayList<>();
if (builder.executorProvider.shouldAutoClose()) {
backgroundResourceList.add(new ExecutorAsBackgroundResource(executor));
}

// Publisher used to take maxAttempt == 0 to mean infinity, but to GAX it means don't retry.
// We post-process this here to keep backward-compatibility.
RetrySettings retrySettings = builder.retrySettings;
if (retrySettings.getMaxAttempts() == 0) {
retrySettings = retrySettings.toBuilder().setMaxAttempts(Integer.MAX_VALUE).build();
// Also, if "message ordering" is enabled, the publisher should retry sending the failed
// message infinitely rather than sending the next one.
RetrySettings.Builder retrySettingsBuilder = builder.retrySettings.toBuilder();
if (retrySettingsBuilder.getMaxAttempts() == 0) {
retrySettingsBuilder.setMaxAttempts(Integer.MAX_VALUE);
}
if (enableMessageOrdering) {
// TODO: is there a way to have the default retry settings for requests without an ordering
// key?
retrySettingsBuilder
.setMaxAttempts(Integer.MAX_VALUE)
.setTotalTimeout(Duration.ofNanos(Long.MAX_VALUE));
}

PublisherStubSettings.Builder stubSettings =
Expand All @@ -147,7 +166,7 @@ private Publisher(Builder builder) throws IOException {
StatusCode.Code.RESOURCE_EXHAUSTED,
StatusCode.Code.UNKNOWN,
StatusCode.Code.UNAVAILABLE)
.setRetrySettings(retrySettings)
.setRetrySettings(retrySettingsBuilder.build())
.setBatchingSettings(BatchingSettings.newBuilder().setIsEnabled(false).build());
this.publisherStub = GrpcPublisherStub.create(stubSettings.build());
backgroundResourceList.add(publisherStub);
Expand Down Expand Up @@ -194,38 +213,71 @@ public String getTopicNameString() {
public ApiFuture<String> publish(PubsubMessage message) {
Preconditions.checkState(!shutdown.get(), "Cannot publish on a shut-down publisher.");

final String orderingKey = message.getOrderingKey();
Preconditions.checkState(
orderingKey.isEmpty() || enableMessageOrdering,
"Cannot publish a message with an ordering key when message ordering is not enabled.");

final OutstandingPublish outstandingPublish =
new OutstandingPublish(messageTransform.apply(message));
List<OutstandingBatch> batchesToSend;
messagesBatchLock.lock();
try {
MessagesBatch messagesBatch = messagesBatches.get(orderingKey);
if (messagesBatch == null) {
messagesBatch = new MessagesBatch(batchingSettings, orderingKey);
messagesBatches.put(orderingKey, messagesBatch);
}

batchesToSend = messagesBatch.add(outstandingPublish);
if (!batchesToSend.isEmpty() && messagesBatch.isEmpty()) {
messagesBatches.remove(orderingKey);
}
// Setup the next duration based delivery alarm if there are messages batched.
setupAlarm();
// For messages with an ordering key, we need to publish with messagesBatchLock held in order
// to ensure another publish doesn't slip in and send a batch before these batches we already
// want to send.
if (!batchesToSend.isEmpty() && !orderingKey.isEmpty()) {
for (final OutstandingBatch batch : batchesToSend) {
logger.log(Level.FINER, "Scheduling a batch for immediate sending.");
publishOutstandingBatch(batch);
}
}
} finally {
messagesBatchLock.unlock();
}

messagesWaiter.incrementPendingMessages(1);

if (!batchesToSend.isEmpty()) {
// For messages without ordering keys, it is okay to send batches without holding
// messagesBatchLock.
if (!batchesToSend.isEmpty() && orderingKey.isEmpty()) {
kamalaboulhosn marked this conversation as resolved.
Show resolved Hide resolved
for (final OutstandingBatch batch : batchesToSend) {
logger.log(Level.FINER, "Scheduling a batch for immediate sending.");
executor.execute(
new Runnable() {
@Override
public void run() {
publishOutstandingBatch(batch);
}
});
publishOutstandingBatch(batch);
}
}

return outstandingPublish.publishResult;
}

/**
* There may be non-recoverable problems with a request for an ordering key. In that case, all
* subsequent requests will fail until this method is called. If the key is not currently paused,
* calling this method will be a no-op.
*
* @param key The key for which to resume publishing.
*/
// TODO: make this public when Ordering keys is live
@BetaApi
void resumePublish(String key) {
Preconditions.checkState(!shutdown.get(), "Cannot publish on a shut-down publisher.");
sequentialExecutor.resumePublish(key);
}

private void setupAlarm() {
if (!messagesBatch.isEmpty()) {
if (!messagesBatches.isEmpty()) {
if (!activeAlarm.getAndSet(true)) {
long delayThresholdMs = getBatchingSettings().getDelayThreshold().toMillis();
logger.log(Level.FINER, "Setting up alarm for the next {0} ms.", delayThresholdMs);
Expand All @@ -236,7 +288,7 @@ private void setupAlarm() {
public void run() {
logger.log(Level.FINER, "Sending messages based on schedule.");
activeAlarm.getAndSet(false);
publishAllOutstanding();
publishAllWithoutInflight();
}
},
delayThresholdMs,
Expand All @@ -256,17 +308,61 @@ public void run() {
* futures returned from {@code publish}.
*/
public void publishAllOutstanding() {
OutstandingBatch unorderedOutstandingBatch = null;
messagesBatchLock.lock();
try {
for (MessagesBatch batch : messagesBatches.values()) {
if (!batch.isEmpty()) {
if (!batch.orderingKey.isEmpty()) {
// For messages with an ordering key, we need to publish with messagesBatchLock held in
// order to ensure another publish doesn't slip in and send a batch before these batches
// we already want to send.
publishOutstandingBatch(batch.popOutstandingBatch());
} else {
unorderedOutstandingBatch = batch.popOutstandingBatch();
}
}
}
messagesBatches.clear();
} finally {
messagesBatchLock.unlock();
}
if (unorderedOutstandingBatch != null) {
publishOutstandingBatch(unorderedOutstandingBatch);
}
}

/**
* Publish any outstanding batches if non-empty and there are no other batches in flight. This
* method sends buffered messages, but does not wait for the send operations to complete. To wait
* for messages to send, call {@code get} on the futures returned from {@code publish}.
*/
private void publishAllWithoutInflight() {
OutstandingBatch unorderedOutstandingBatch = null;
messagesBatchLock.lock();
OutstandingBatch batchToSend;
try {
if (messagesBatch.isEmpty()) {
return;
Iterator<Map.Entry<String, MessagesBatch>> it = messagesBatches.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, MessagesBatch> entry = it.next();
MessagesBatch batch = entry.getValue();
String key = entry.getKey();
if (batch.isEmpty()) {
it.remove();
} else if (key.isEmpty()) {
// We will publish the batch with no ordering key outside messagesBatchLock.
unorderedOutstandingBatch = batch.popOutstandingBatch();
it.remove();
} else if (!sequentialExecutor.hasTasksInflight(key)) {
publishOutstandingBatch(batch.popOutstandingBatch());
it.remove();
}
}
batchToSend = messagesBatch.popOutstandingBatch();
} finally {
messagesBatchLock.unlock();
}
publishOutstandingBatch(batchToSend);
if (unorderedOutstandingBatch != null) {
publishOutstandingBatch(unorderedOutstandingBatch);
}
}

private ApiFuture<PublishResponse> publishCall(OutstandingBatch outstandingBatch) {
Expand All @@ -280,12 +376,12 @@ private ApiFuture<PublishResponse> publishCall(OutstandingBatch outstandingBatch
}

private void publishOutstandingBatch(final OutstandingBatch outstandingBatch) {
ApiFutureCallback<PublishResponse> futureCallback =
final ApiFutureCallback<PublishResponse> futureCallback =
new ApiFutureCallback<PublishResponse>() {
@Override
public void onSuccess(PublishResponse result) {
try {
if (result.getMessageIdsCount() != outstandingBatch.size()) {
if (result == null || result.getMessageIdsCount() != outstandingBatch.size()) {
outstandingBatch.onFailure(
new IllegalStateException(
String.format(
Expand All @@ -311,20 +407,37 @@ public void onFailure(Throwable t) {
}
};

ApiFutures.addCallback(publishCall(outstandingBatch), futureCallback, directExecutor());
ApiFuture<PublishResponse> future;
if (outstandingBatch.orderingKey == null || outstandingBatch.orderingKey.isEmpty()) {
future = publishCall(outstandingBatch);
} else {
// If ordering key is specified, publish the batch using the sequential executor.
future =
sequentialExecutor.submit(
outstandingBatch.orderingKey,
new Callable<ApiFuture<PublishResponse>>() {
public ApiFuture<PublishResponse> call() {
return publishCall(outstandingBatch);
}
});
}
ApiFutures.addCallback(future, futureCallback, directExecutor());
}

private static final class OutstandingBatch {
final List<OutstandingPublish> outstandingPublishes;
final long creationTime;
int attempt;
int batchSizeBytes;
final String orderingKey;

OutstandingBatch(List<OutstandingPublish> outstandingPublishes, int batchSizeBytes) {
OutstandingBatch(
List<OutstandingPublish> outstandingPublishes, int batchSizeBytes, String orderingKey) {
this.outstandingPublishes = outstandingPublishes;
attempt = 1;
creationTime = System.currentTimeMillis();
this.batchSizeBytes = batchSizeBytes;
this.orderingKey = orderingKey;
}

int size() {
Expand Down Expand Up @@ -468,7 +581,7 @@ public static final class Builder {
.setRpcTimeoutMultiplier(2)
.setMaxRpcTimeout(DEFAULT_RPC_TIMEOUT)
.build();

static final boolean DEFAULT_ENABLE_MESSAGE_ORDERING = false;
private static final int THREADS_PER_CPU = 5;
static final ExecutorProvider DEFAULT_EXECUTOR_PROVIDER =
InstantiatingExecutorProvider.newBuilder()
Expand All @@ -482,6 +595,8 @@ public static final class Builder {

RetrySettings retrySettings = DEFAULT_RETRY_SETTINGS;

private boolean enableMessageOrdering = DEFAULT_ENABLE_MESSAGE_ORDERING;

private TransportChannelProvider channelProvider =
TopicAdminSettings.defaultGrpcTransportProviderBuilder().setChannelsPerCpu(1).build();

Expand Down Expand Up @@ -576,6 +691,14 @@ public Builder setRetrySettings(RetrySettings retrySettings) {
return this;
}

/** Sets the message ordering option. */
// TODO: make this public when Ordering keys is live
@BetaApi
Builder setEnableMessageOrdering(boolean enableMessageOrdering) {
this.enableMessageOrdering = enableMessageOrdering;
return this;
}

/** Gives the ability to set a custom executor to be used by the library. */
public Builder setExecutorProvider(ExecutorProvider executorProvider) {
this.executorProvider = Preconditions.checkNotNull(executorProvider);
Expand All @@ -601,15 +724,17 @@ public Publisher build() throws IOException {
private static class MessagesBatch {
private List<OutstandingPublish> messages;
private int batchedBytes;
private String orderingKey;
private final BatchingSettings batchingSettings;

public MessagesBatch(BatchingSettings batchingSettings) {
private MessagesBatch(BatchingSettings batchingSettings, String orderingKey) {
this.batchingSettings = batchingSettings;
this.orderingKey = orderingKey;
reset();
}

private OutstandingBatch popOutstandingBatch() {
OutstandingBatch batch = new OutstandingBatch(messages, batchedBytes);
OutstandingBatch batch = new OutstandingBatch(messages, batchedBytes, orderingKey);
reset();
return batch;
}
Expand Down
Loading