Skip to content

Commit 0ec55b7

Browse files
committed
[Dataflow Streaming] Reuse ByteStringOutputStream buffers to reduce GC overhead
1 parent b1f5287 commit 0ec55b7

2 files changed

Lines changed: 50 additions & 13 deletions

File tree

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/WindmillSink.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
package org.apache.beam.runners.dataflow.worker;
1919

2020
import static org.apache.beam.runners.dataflow.util.Structs.getString;
21-
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState;
2221

2322
import com.google.auto.service.AutoService;
2423
import java.io.IOException;
@@ -54,6 +53,7 @@
5453
"nullness" // TODO(https://github.com/apache/beam/issues/20497)
5554
})
5655
class WindmillSink<T> extends Sink<WindowedValue<T>> {
56+
5757
private WindmillStreamWriter writer;
5858
private final Coder<T> valueCoder;
5959
private final Coder<Collection<? extends BoundedWindow>> windowsCoder;
@@ -71,15 +71,29 @@ class WindmillSink<T> extends Sink<WindowedValue<T>> {
7171
this.context = context;
7272
}
7373

74+
private static ByteString encodeMetadata(
75+
ByteStringOutputStream stream,
76+
Coder<Collection<? extends BoundedWindow>> windowsCoder,
77+
Collection<? extends BoundedWindow> windows,
78+
PaneInfo paneInfo)
79+
throws IOException {
80+
try {
81+
PaneInfoCoder.INSTANCE.encode(paneInfo, stream);
82+
windowsCoder.encode(windows, stream, Coder.Context.OUTER);
83+
return stream.toByteStringAndReset();
84+
} catch (Exception e) {
85+
stream.toByteStringAndReset();
86+
throw e;
87+
}
88+
}
89+
7490
public static ByteString encodeMetadata(
7591
Coder<Collection<? extends BoundedWindow>> windowsCoder,
7692
Collection<? extends BoundedWindow> windows,
7793
PaneInfo paneInfo)
7894
throws IOException {
7995
ByteStringOutputStream stream = new ByteStringOutputStream();
80-
PaneInfoCoder.INSTANCE.encode(paneInfo, stream);
81-
windowsCoder.encode(windows, stream, Coder.Context.OUTER);
82-
return stream.toByteString();
96+
return encodeMetadata(stream, windowsCoder, windows, paneInfo);
8397
}
8498

8599
public static PaneInfo decodeMetadataPane(ByteString metadata) throws IOException {
@@ -109,6 +123,7 @@ public Map<String, SinkFactory> factories() {
109123
}
110124

111125
public static class Factory implements SinkFactory {
126+
112127
@Override
113128
public WindmillSink<?> create(
114129
CloudObject spec,
@@ -133,6 +148,7 @@ public SinkWriter<WindowedValue<T>> writer() {
133148
}
134149

135150
class WindmillStreamWriter implements SinkWriter<WindowedValue<T>> {
151+
136152
private Map<ByteString, Windmill.KeyedMessageBundle.Builder> productionMap;
137153
private final String destinationName;
138154
private final ByteStringOutputStream stream; // Kept across encodes for buffer reuse.
@@ -144,10 +160,10 @@ private WindmillStreamWriter(String destinationName) {
144160
}
145161

146162
private <EncodeT> ByteString encode(Coder<EncodeT> coder, EncodeT object) throws IOException {
147-
checkState(
148-
stream.size() == 0,
149-
"Expected output stream to be empty but had %s",
150-
stream.toByteString());
163+
if (stream.size() != 0) {
164+
throw new IllegalStateException(
165+
"Expected output stream to be empty but had " + stream.toByteString());
166+
}
151167
try {
152168
coder.encode(object, stream, Coder.Context.OUTER);
153169
return stream.toByteStringAndReset();
@@ -162,7 +178,8 @@ private <EncodeT> ByteString encode(Coder<EncodeT> coder, EncodeT object) throws
162178
public long add(WindowedValue<T> data) throws IOException {
163179
ByteString key, value;
164180
ByteString id = ByteString.EMPTY;
165-
ByteString metadata = encodeMetadata(windowsCoder, data.getWindows(), data.getPaneInfo());
181+
ByteString metadata =
182+
encodeMetadata(stream, windowsCoder, data.getWindows(), data.getPaneInfo());
166183
if (valueCoder instanceof KvCoder) {
167184
KvCoder kvCoder = (KvCoder) valueCoder;
168185
KV kv = (KV) data.getValue();

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/state/WindmillStateUtil.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,50 @@
1818
package org.apache.beam.runners.dataflow.worker.windmill.state;
1919

2020
import java.io.IOException;
21+
import java.lang.ref.SoftReference;
2122
import org.apache.beam.runners.core.StateNamespace;
2223
import org.apache.beam.runners.core.StateTag;
2324
import org.apache.beam.sdk.util.ByteStringOutputStream;
2425
import org.apache.beam.vendor.grpc.v1p69p0.com.google.protobuf.ByteString;
2526
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting;
27+
import org.checkerframework.checker.nullness.qual.Nullable;
2628

2729
class WindmillStateUtil {
2830

31+
private static final ThreadLocal<@Nullable SoftReference<@Nullable ByteStringOutputStream>>
32+
threadLocalOutputStream = new ThreadLocal<>();
33+
2934
/** Encodes the given namespace and address as {@code &lt;namespace&gt;+&lt;address&gt;}. */
3035
@VisibleForTesting
3136
static ByteString encodeKey(StateNamespace namespace, StateTag<?> address) {
37+
// Use ByteStringOutputStream rather than concatenation and String.format. We build these keys
38+
// a lot, and this leads to better performance results. See associated benchmarks.
39+
ByteStringOutputStream stream = getByteStringOutputStream();
3240
try {
33-
// Use ByteStringOutputStream rather than concatenation and String.format. We build these keys
34-
// a lot, and this leads to better performance results. See associated benchmarks.
35-
ByteStringOutputStream stream = new ByteStringOutputStream();
3641
// stringKey starts and ends with a slash. We separate it from the
3742
// StateTag ID by a '+' (which is guaranteed not to be in the stringKey) because the
3843
// ID comes from the user.
3944
namespace.appendTo(stream);
4045
stream.append('+');
4146
address.appendTo(stream);
42-
return stream.toByteString();
47+
return stream.toByteStringAndReset();
4348
} catch (IOException e) {
49+
stream.toByteStringAndReset();
4450
throw new RuntimeException(e);
51+
} catch (RuntimeException e) {
52+
stream.toByteStringAndReset();
53+
throw e;
54+
}
55+
}
56+
57+
private static ByteStringOutputStream getByteStringOutputStream() {
58+
@Nullable
59+
SoftReference<@Nullable ByteStringOutputStream> refStream = threadLocalOutputStream.get();
60+
@Nullable ByteStringOutputStream stream = refStream == null ? null : refStream.get();
61+
if (stream == null) {
62+
stream = new ByteStringOutputStream();
63+
threadLocalOutputStream.set(new SoftReference<>(stream));
4564
}
65+
return stream;
4666
}
4767
}

0 commit comments

Comments
 (0)