@@ -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