Skip to content

Commit 62fd95d

Browse files
authored
Merge pull request #38332: only expand update graph if needed
1 parent fffa3c0 commit 62fd95d

1 file changed

Lines changed: 107 additions & 99 deletions

File tree

sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiConvertMessages.java

Lines changed: 107 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -147,109 +147,117 @@ public PCollectionTuple expand(PCollection<KV<DestinationT, ElementT>> input) {
147147
.get(patchTableSchemaTag)
148148
.setCoder(KvCoder.of(destinationCoder, ProtoCoder.of(TableSchema.class)));
149149
result.get(elementsWaitingForSchemaTag).setCoder(KvCoder.of(destinationCoder, elementCoder));
150+
if (!hasSchemaUpdateOptions) {
151+
// Don't expand the update graph if it's not needed.
152+
return result;
153+
} else {
154+
final int numShards =
155+
input
156+
.getPipeline()
157+
.getOptions()
158+
.as(BigQueryOptions.class)
159+
.getSchemaUpgradeBufferingShards();
150160

151-
final int numShards =
152-
input
153-
.getPipeline()
154-
.getOptions()
155-
.as(BigQueryOptions.class)
156-
.getSchemaUpgradeBufferingShards();
161+
// Throttle the stream to the patch-table function so that only a single update per table per
162+
// two seconds gets processed (to match quotas). The combiner merges incremental schemas, so
163+
// we
164+
// won't miss any updates.
165+
PCollection<KV<ShardedKey<DestinationT>, ElementT>> tablesPatched =
166+
result
167+
.get(patchTableSchemaTag)
168+
.apply(
169+
"rewindow",
170+
Window.<KV<DestinationT, TableSchema>>configure()
171+
.triggering(
172+
Repeatedly.forever(
173+
AfterProcessingTime.pastFirstElementInPane()
174+
.plusDelayOf(Duration.standardSeconds(2))))
175+
.discardingFiredPanes())
176+
.apply("merge schemas", Combine.fewKeys(new MergeSchemaCombineFn()))
177+
.setCoder(KvCoder.of(destinationCoder, ProtoCoder.of(TableSchema.class)))
178+
.apply(
179+
"Patch table schema",
180+
ParDo.of(
181+
new PatchTableSchemaDoFn<>(operationName, bqServices, dynamicDestinations)))
182+
.setCoder(KvCoder.of(destinationCoder, NullableCoder.of(elementCoder)))
183+
// We need to make sure that all shards of the buffering transform are notified.
184+
.apply(
185+
"fanout to all shards",
186+
FlatMapElements.via(
187+
new SimpleFunction<
188+
KV<DestinationT, ElementT>,
189+
Iterable<KV<ShardedKey<DestinationT>, ElementT>>>() {
190+
@Override
191+
public Iterable<KV<ShardedKey<DestinationT>, ElementT>> apply(
192+
KV<DestinationT, ElementT> elem) {
193+
return IntStream.range(0, numShards)
194+
.mapToObj(
195+
i ->
196+
KV.of(
197+
StorageApiConvertMessages.AssignShardFn.getShardedKey(
198+
elem.getKey(), i, numShards),
199+
elem.getValue()))
200+
.collect(Collectors.toList());
201+
}
202+
}))
203+
.setCoder(
204+
KvCoder.of(ShardedKey.Coder.of(destinationCoder), NullableCoder.of(elementCoder)))
205+
.apply(
206+
Window.<KV<ShardedKey<DestinationT>, ElementT>>configure()
207+
.triggering(DefaultTrigger.of()));
157208

158-
// Throttle the stream to the patch-table function so that only a single update per table per
159-
// two seconds gets processed (to match quotas). The combiner merges incremental schemas, so we
160-
// won't miss any updates.
161-
PCollection<KV<ShardedKey<DestinationT>, ElementT>> tablesPatched =
162-
result
163-
.get(patchTableSchemaTag)
164-
.apply(
165-
"rewindow",
166-
Window.<KV<DestinationT, TableSchema>>configure()
167-
.triggering(
168-
Repeatedly.forever(
169-
AfterProcessingTime.pastFirstElementInPane()
170-
.plusDelayOf(Duration.standardSeconds(2))))
171-
.discardingFiredPanes())
172-
.apply("merge schemas", Combine.fewKeys(new MergeSchemaCombineFn()))
173-
.setCoder(KvCoder.of(destinationCoder, ProtoCoder.of(TableSchema.class)))
174-
.apply(
175-
"Patch table schema",
176-
ParDo.of(
177-
new PatchTableSchemaDoFn<>(operationName, bqServices, dynamicDestinations)))
178-
.setCoder(KvCoder.of(destinationCoder, NullableCoder.of(elementCoder)))
179-
// We need to make sure that all shards of the buffering transform are notified.
180-
.apply(
181-
"fanout to all shards",
182-
FlatMapElements.via(
183-
new SimpleFunction<
184-
KV<DestinationT, ElementT>,
185-
Iterable<KV<ShardedKey<DestinationT>, ElementT>>>() {
186-
@Override
187-
public Iterable<KV<ShardedKey<DestinationT>, ElementT>> apply(
188-
KV<DestinationT, ElementT> elem) {
189-
return IntStream.range(0, numShards)
190-
.mapToObj(
191-
i ->
192-
KV.of(
193-
StorageApiConvertMessages.AssignShardFn.getShardedKey(
194-
elem.getKey(), i, numShards),
195-
elem.getValue()))
196-
.collect(Collectors.toList());
197-
}
198-
}))
199-
.setCoder(
200-
KvCoder.of(ShardedKey.Coder.of(destinationCoder), NullableCoder.of(elementCoder)))
201-
.apply(
202-
Window.<KV<ShardedKey<DestinationT>, ElementT>>configure()
203-
.triggering(DefaultTrigger.of()));
209+
// Any elements that are waiting for a schema update are sent to this stateful DoFn to be
210+
// buffered.
211+
// Note: we currently do not provide the DynamicDestinations object access to the side input
212+
// in
213+
// this path.
214+
// This is because side inputs are not currently available from timer callbacks. Since side
215+
// inputs are generally
216+
// used for getSchema and in this case we read the schema from the table, this is unlikely to
217+
// be
218+
// a problem.
219+
PCollection<KV<ShardedKey<DestinationT>, ElementT>> shardedWaitingElements =
220+
result
221+
.get(elementsWaitingForSchemaTag)
222+
// TODO: Consider using GroupIntoBatchs.withShardingKey to get auto sharding here
223+
// instead of fixed sharding.
224+
.apply("assignShard", ParDo.of(new AssignShardFn<>(numShards)))
225+
.setCoder(
226+
KvCoder.of(
227+
ShardedKey.Coder.of(destinationCoder), NullableCoder.of(elementCoder)));
204228

205-
// Any elements that are waiting for a schema update are sent to this stateful DoFn to be
206-
// buffered.
207-
// Note: we currently do not provide the DynamicDestinations object access to the side input in
208-
// this path.
209-
// This is because side inputs are not currently available from timer callbacks. Since side
210-
// inputs are generally
211-
// used for getSchema and in this case we read the schema from the table, this is unlikely to be
212-
// a problem.
213-
PCollection<KV<ShardedKey<DestinationT>, ElementT>> shardedWaitingElements =
214-
result
215-
.get(elementsWaitingForSchemaTag)
216-
// TODO: Consider using GroupIntoBatchs.withShardingKey to get auto sharding here
217-
// instead of fixed sharding.
218-
.apply("assignShard", ParDo.of(new AssignShardFn<>(numShards)))
219-
.setCoder(
220-
KvCoder.of(ShardedKey.Coder.of(destinationCoder), NullableCoder.of(elementCoder)));
229+
PCollectionList<KV<ShardedKey<DestinationT>, ElementT>> waitingElementsList =
230+
PCollectionList.of(shardedWaitingElements).and(tablesPatched);
231+
PCollectionTuple retryResult =
232+
waitingElementsList
233+
.apply("Buffered flatten", Flatten.pCollections())
234+
.apply(
235+
"bufferElements",
236+
ParDo.of(new SchemaUpdateHoldingFn<>(elementCoder, convertMessagesDoFn))
237+
.withOutputTags(
238+
successfulWritesTag,
239+
TupleTagList.of(ImmutableList.of(failedWritesTag, BAD_RECORD_TAG))));
240+
retryResult.get(successfulWritesTag).setCoder(successCoder);
241+
retryResult.get(failedWritesTag).setCoder(errorCoder);
242+
retryResult.get(BAD_RECORD_TAG).setCoder(BadRecord.getCoder(input.getPipeline()));
221243

222-
PCollectionList<KV<ShardedKey<DestinationT>, ElementT>> waitingElementsList =
223-
PCollectionList.of(shardedWaitingElements).and(tablesPatched);
224-
PCollectionTuple retryResult =
225-
waitingElementsList
226-
.apply("Buffered flatten", Flatten.pCollections())
227-
.apply(
228-
"bufferElements",
229-
ParDo.of(new SchemaUpdateHoldingFn<>(elementCoder, convertMessagesDoFn))
230-
.withOutputTags(
231-
successfulWritesTag,
232-
TupleTagList.of(ImmutableList.of(failedWritesTag, BAD_RECORD_TAG))));
233-
retryResult.get(successfulWritesTag).setCoder(successCoder);
234-
retryResult.get(failedWritesTag).setCoder(errorCoder);
235-
retryResult.get(BAD_RECORD_TAG).setCoder(BadRecord.getCoder(input.getPipeline()));
236-
237-
// Flatten successes and failures from both the regular transform and the retry transform.
238-
PCollection<KV<DestinationT, StorageApiWritePayload>> allSuccesses =
239-
PCollectionList.of(result.get(successfulWritesTag))
240-
.and(retryResult.get(successfulWritesTag))
241-
.apply("flattenSuccesses", Flatten.pCollections());
242-
PCollection<BigQueryStorageApiInsertError> allFailures =
243-
PCollectionList.of(result.get(failedWritesTag))
244-
.and(retryResult.get(failedWritesTag))
245-
.apply("flattenFailures", Flatten.pCollections());
246-
PCollection<BadRecord> allBadRecords =
247-
PCollectionList.of(result.get(BAD_RECORD_TAG))
248-
.and(retryResult.get(BAD_RECORD_TAG))
249-
.apply("flattenBadRecords", Flatten.pCollections());
250-
return PCollectionTuple.of(successfulWritesTag, allSuccesses)
251-
.and(failedWritesTag, allFailures)
252-
.and(BAD_RECORD_TAG, allBadRecords);
244+
// Flatten successes and failures from both the regular transform and the retry transform.
245+
PCollection<KV<DestinationT, StorageApiWritePayload>> allSuccesses =
246+
PCollectionList.of(result.get(successfulWritesTag))
247+
.and(retryResult.get(successfulWritesTag))
248+
.apply("flattenSuccesses", Flatten.pCollections());
249+
PCollection<BigQueryStorageApiInsertError> allFailures =
250+
PCollectionList.of(result.get(failedWritesTag))
251+
.and(retryResult.get(failedWritesTag))
252+
.apply("flattenFailures", Flatten.pCollections());
253+
PCollection<BadRecord> allBadRecords =
254+
PCollectionList.of(result.get(BAD_RECORD_TAG))
255+
.and(retryResult.get(BAD_RECORD_TAG))
256+
.apply("flattenBadRecords", Flatten.pCollections());
257+
return PCollectionTuple.of(successfulWritesTag, allSuccesses)
258+
.and(failedWritesTag, allFailures)
259+
.and(BAD_RECORD_TAG, allBadRecords);
260+
}
253261
}
254262

255263
static class AssignShardFn<K, V> extends DoFn<KV<K, V>, KV<ShardedKey<K>, V>> {

0 commit comments

Comments
 (0)