-
Notifications
You must be signed in to change notification settings - Fork 4.6k
[SolaceIO] refactor SolaceIO's writers finishBundle #39612
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,8 +29,9 @@ | |
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.Queue; | ||
| import java.util.Set; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.BlockingQueue; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import org.apache.beam.sdk.annotations.Internal; | ||
|
|
@@ -68,6 +69,7 @@ public abstract class UnboundedSolaceWriter | |
|
|
||
| // This is the batch limit supported by the send multiple JCSMP API method. | ||
| static final int SOLACE_BATCH_LIMIT = 50; | ||
| static final int ACKS_FLUSHING_INTERVAL_SECS = 10; | ||
| private final Distribution latencyPublish = | ||
| Metrics.distribution(SolaceIO.Write.class, "latency_publish_ms"); | ||
|
|
||
|
|
@@ -132,7 +134,14 @@ public SessionService solaceSessionServiceWithProducer() { | |
| currentBundleProducerIndex, sessionServiceFactory, writerTransformUuid); | ||
| } | ||
|
|
||
| public void publishResults(BeamContextWrapper context) { | ||
| public void publishResults(BeamContextWrapper context, @Nullable Set<String> messageIdsToAck) { | ||
| publishResults(context, null, messageIdsToAck); | ||
| } | ||
|
|
||
| public void publishResults( | ||
| BeamContextWrapper context, | ||
| @Nullable PublishResult firstResult, | ||
| @Nullable Set<String> messageIdsToAck) { | ||
| long sumPublish = 0; | ||
| long countPublish = 0; | ||
| long minPublish = Long.MAX_VALUE; | ||
|
|
@@ -143,9 +152,9 @@ public void publishResults(BeamContextWrapper context) { | |
| long minFailed = Long.MAX_VALUE; | ||
| long maxFailed = 0; | ||
|
|
||
| Queue<PublishResult> publishResultsQueue = | ||
| BlockingQueue<PublishResult> publishResultsQueue = | ||
| solaceSessionServiceWithProducer().getPublishedResultsQueue(); | ||
| Solace.PublishResult result = publishResultsQueue.poll(); | ||
| PublishResult result = firstResult != null ? firstResult : publishResultsQueue.poll(); | ||
|
|
||
| if (result != null) { | ||
| if (getCurrentBundleTimestamp() == null) { | ||
|
|
@@ -154,6 +163,9 @@ public void publishResults(BeamContextWrapper context) { | |
| } | ||
|
|
||
| while (result != null) { | ||
| if (messageIdsToAck != null) { | ||
| messageIdsToAck.remove(result.getMessageId()); | ||
| } | ||
| Long latency = result.getLatencyNanos(); | ||
|
|
||
| if (latency == null && shouldPublishLatencyMetrics()) { | ||
|
|
@@ -218,6 +230,37 @@ public void publishResults(BeamContextWrapper context) { | |
| } | ||
| } | ||
|
|
||
| public void waitForAcks(BeamContextWrapper context, Set<String> messageIdsToAck) { | ||
| BlockingQueue<PublishResult> queue = | ||
| solaceSessionServiceWithProducer().getPublishedResultsQueue(); | ||
| long timeoutMs = System.currentTimeMillis() + ACKS_FLUSHING_INTERVAL_SECS * 1000; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we use exponential backoff here and set a larger overall timeout (O (minutes)) to support potentially large bundles and/or API rate limit issues ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so the tiny cost here will be cpu of worker on checking the queue. there is no api rate limit issue as different thread is publishing to the queue. |
||
| while (!messageIdsToAck.isEmpty()) { | ||
| publishResults(context, messageIdsToAck); | ||
| if (messageIdsToAck.isEmpty()) { | ||
| break; | ||
| } | ||
| long remainingTimeMs = timeoutMs - System.currentTimeMillis(); | ||
| if (remainingTimeMs <= 0) { | ||
| break; | ||
| } | ||
| try { | ||
| PublishResult result = queue.poll(remainingTimeMs, TimeUnit.MILLISECONDS); | ||
| if (result != null) { | ||
| publishResults(context, result, messageIdsToAck); | ||
| } | ||
| } catch (InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| break; | ||
| } | ||
| } | ||
| if (!messageIdsToAck.isEmpty()) { | ||
| LOG.warn( | ||
| "SolaceIO.Write: Timed out waiting for ACKs of {} messages. Outstanding message IDs: {}", | ||
| messageIdsToAck.size(), | ||
| messageIdsToAck); | ||
| } | ||
| } | ||
|
|
||
| public BytesXMLMessage createSingleMessage( | ||
| Solace.Record record, boolean useCorrelationKeyLatency) { | ||
| JCSMPFactory jcsmpFactory = JCSMPFactory.onlyInstance(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,8 @@ | |
|
|
||
| import com.solacesystems.jcsmp.DeliveryMode; | ||
| import com.solacesystems.jcsmp.Destination; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
| import org.apache.beam.sdk.annotations.Internal; | ||
| import org.apache.beam.sdk.io.solace.SolaceIO; | ||
| import org.apache.beam.sdk.io.solace.broker.SessionServiceFactory; | ||
|
|
@@ -63,6 +65,8 @@ public final class UnboundedStreamingSolaceWriter extends UnboundedSolaceWriter | |
| private final Counter rejectedByBroker = | ||
| Metrics.counter(UnboundedStreamingSolaceWriter.class, "msgs_rejected_by_broker"); | ||
|
|
||
| private final Set<String> messageIdsToAck = new HashSet<>(); | ||
|
|
||
| // We use a state variable to force a shuffling and ensure the cardinality of the processing | ||
| @SuppressWarnings("UnusedVariable") | ||
| @StateId("current_key") | ||
|
|
@@ -84,6 +88,13 @@ public UnboundedStreamingSolaceWriter( | |
| publishLatencyMetrics); | ||
| } | ||
|
|
||
| @StartBundle | ||
| @Override | ||
| public void startBundle() { | ||
| super.startBundle(); | ||
| messageIdsToAck.clear(); | ||
| } | ||
|
|
||
| @ProcessElement | ||
| public void processElement( | ||
| @Element KV<Integer, Solace.Record> element, | ||
|
|
@@ -105,6 +116,10 @@ public void processElement( | |
| return; | ||
| } | ||
|
|
||
| if (getDeliveryMode() == DeliveryMode.PERSISTENT) { | ||
| messageIdsToAck.add(record.getMessageId()); | ||
| } | ||
|
|
||
| // The publish method will retry, let's send a failure message if all the retries fail | ||
| try { | ||
| solaceSessionServiceWithProducer() | ||
|
|
@@ -133,6 +148,10 @@ public void processElement( | |
|
|
||
| @FinishBundle | ||
| public void finishBundle(FinishBundleContext context) { | ||
| publishResults(BeamContextWrapper.of(context)); | ||
| if (getDeliveryMode() == DeliveryMode.PERSISTENT) { | ||
| waitForAcks(BeamContextWrapper.of(context), messageIdsToAck); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. |
||
| } else { | ||
| publishResults(BeamContextWrapper.of(context), null); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to invoke publishResults in this path as well ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waitForAcks is waiting for Acks and publishing results in it.