Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@
* (Python) Added typed decision questions and the `EvaluateDecisions` transform, with an optional Jev model adapter ([#40220](https://github.com/apache/beam/pull/40220)).
* `QdrantWriteConfig` can now optionally store chunk content in the point payload via `content_payload_key`, so retrieved points include the chunk text (Python) ([#40476](https://github.com/apache/beam/issues/40476)).
* ReadFromBigQuery now supports Lakehouse runtime catalog (BigLake metastore) tables with `method=DIRECT_READ`, using 4-part `project.catalog.namespace.table` identifiers. Previously `project:catalog.namespace.table` was silently mis-parsed and tables that report no `numBytes` failed to split (Python) ([#39597](https://github.com/apache/beam/issues/39597)).
* (Java) `TextIO`, `FileIO`, and `WriteFiles`: streaming writes can skip the shuffle for large bundles by writing them directly to temp files (`withDirectWriteByteThreshold`). Off by default ([#40533](https://github.com/apache/beam/issues/40533)).
* (Python) Added `AwsOptions` with `--aws_*` pipeline options (e.g. `--aws_access_key_id`, `--aws_region_name`, `--aws_endpoint_url`) that set defaults for all AWS service clients. The existing `--s3_*` options in `S3Options` override these defaults for the S3 client only ([#40528](https://github.com/apache/beam/issues/40528)).
* (Python) Added `--aws_credentials_secret_manager` so AWS credentials (`--aws_access_key_id`, `--aws_secret_access_key`, `--aws_session_token` and their `--s3_*` overrides) can be secret specs resolved through a secret manager such as `GoogleCloudSecretManager`, instead of plain-text values ([#40528](https://github.com/apache/beam/issues/40528)).
* (Python) Added `AwsSecret` (`AwsSecretsManager` / `type:awssecret`) to retrieve secrets from AWS Secrets Manager, configured from `AwsOptions` (`--aws_*`) with optional per-secret connection overrides ([#40528](https://github.com/apache/beam/issues/40528)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.hash.Hashing;
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.io.ByteStreams;
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.io.Files;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
Expand Down Expand Up @@ -2644,6 +2645,7 @@ static class StreamingShardedWriteFactory<UserT, DestinationT, OutputT>
// We pick 10 as a default, as it works well with the default number of workers started
// by Dataflow.
static final int DEFAULT_NUM_SHARDS = 10;

DataflowPipelineWorkerPoolOptions options;

StreamingShardedWriteFactory(PipelineOptions options) {
Expand Down Expand Up @@ -2683,6 +2685,11 @@ static class StreamingShardedWriteFactory<UserT, DestinationT, OutputT>
replacement = replacement.withWindowedWrites();
}

Long directWriteThreshold = getDirectWriteThreshold(transform);
if (directWriteThreshold != null) {
replacement = replacement.withDirectWriteByteThreshold(directWriteThreshold);
}

if (WriteFilesTranslation.isAutoSharded(transform)) {
replacement = replacement.withAutoSharding();
return PTransformReplacement.of(
Expand All @@ -2692,11 +2699,24 @@ static class StreamingShardedWriteFactory<UserT, DestinationT, OutputT>
return PTransformReplacement.of(
PTransformReplacements.getSingletonMainInput(transform),
replacement.withNumShards(numShards));
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* Returns the direct write threshold set on the original {@link WriteFiles}, else {@code null}.
*/
private @Nullable Long getDirectWriteThreshold(AppliedPTransform<?, ?, ?> transform) {
Object original = transform.getTransform();
if (original instanceof WriteFiles) {
return ((WriteFiles<?, ?, ?>) original).getDirectWriteByteThreshold();
}
return null;
}

@Override
public Map<PCollection<?>, ReplacementOutput> mapOutputs(
Map<TupleTag<?>, PCollection<?>> outputs, WriteFilesResult<DestinationT> newOutput) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2189,9 +2189,66 @@ public void testStreamingWriteWithShardingReturnsSameTransform() {

assertNull(replacement.getNumShardsProvider());
assertNull(replacement.getComputeNumShards());
assertNull(replacement.getDirectWriteByteThreshold());
assertTrue(replacement.getWithAutoSharding());
}

@Test
public void testStreamingWriteWithDirectWriteByteThreshold() {
PipelineOptions options = TestPipeline.testingPipelineOptions();

TestPipeline p = TestPipeline.fromOptions(options);
StreamingShardedWriteFactory<Object, Void, Object> factory =
new StreamingShardedWriteFactory<>(p.getOptions());
WriteFiles<Object, Void, Object> original =
WriteFiles.to(new TestSink(tmpFolder.toString()))
.withRunnerDeterminedSharding()
.withDirectWriteByteThreshold(1000L);
PCollection<Object> objs = (PCollection) p.apply(Create.empty(VoidCoder.of()));
AppliedPTransform<PCollection<Object>, WriteFilesResult<Void>, WriteFiles<Object, Void, Object>>
originalApplication =
AppliedPTransform.of(
"writefiles",
PValues.expandInput(objs),
Collections.emptyMap(),
original,
ResourceHints.create(),
p);

WriteFiles<Object, Void, Object> replacement =
(WriteFiles<Object, Void, Object>)
factory.getReplacementTransform(originalApplication).getTransform();

assertEquals(Long.valueOf(1000L), replacement.getDirectWriteByteThreshold());
}

@Test
public void testStreamingWriteDirectWriteThresholdDefaultNull() {
PipelineOptions options = TestPipeline.testingPipelineOptions();

TestPipeline p = TestPipeline.fromOptions(options);
StreamingShardedWriteFactory<Object, Void, Object> factory =
new StreamingShardedWriteFactory<>(p.getOptions());
WriteFiles<Object, Void, Object> original =
WriteFiles.to(new TestSink(tmpFolder.toString())).withRunnerDeterminedSharding();
PCollection<Object> objs = (PCollection) p.apply(Create.empty(VoidCoder.of()));
AppliedPTransform<PCollection<Object>, WriteFilesResult<Void>, WriteFiles<Object, Void, Object>>
originalApplication =
AppliedPTransform.of(
"writefiles",
PValues.expandInput(objs),
Collections.emptyMap(),
original,
ResourceHints.create(),
p);

WriteFiles<Object, Void, Object> replacement =
(WriteFiles<Object, Void, Object>)
factory.getReplacementTransform(originalApplication).getTransform();

assertNull(replacement.getDirectWriteByteThreshold());
}

private void verifyMergingStatefulParDoRejected(PipelineOptions options) throws Exception {
Pipeline p = Pipeline.create(options);

Expand Down
15 changes: 15 additions & 0 deletions sdks/java/core/src/main/java/org/apache/beam/sdk/io/FileIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@ public static FileNaming relativeFileNaming(

abstract @Nullable Duration getBatchMaxBufferingDuration();

abstract @Nullable Long getDirectWriteByteThreshold();

abstract @Nullable ErrorHandler<BadRecord, ?> getBadRecordErrorHandler();

abstract Builder<DestinationT, UserT> toBuilder();
Expand Down Expand Up @@ -1133,6 +1135,9 @@ abstract Builder<DestinationT, UserT> setMaxNumWritersPerBundle(
abstract Builder<DestinationT, UserT> setBatchMaxBufferingDuration(
@Nullable Duration batchMaxBufferingDuration);

abstract Builder<DestinationT, UserT> setDirectWriteByteThreshold(
@Nullable Long directWriteByteThreshold);

abstract Builder<DestinationT, UserT> setBadRecordErrorHandler(
@Nullable ErrorHandler<BadRecord, ?> badRecordErrorHandler);

Expand Down Expand Up @@ -1436,6 +1441,13 @@ public Write<DestinationT, UserT> withBatchMaxBufferingDuration(
return toBuilder().setBatchMaxBufferingDuration(batchMaxBufferingDuration).build();
}

/** See {@link WriteFiles#withDirectWriteByteThreshold(long)}. */
public Write<DestinationT, UserT> withDirectWriteByteThreshold(long thresholdBytes) {
checkArgument(
thresholdBytes > 0, "thresholdBytes must be positive, but was: %s", thresholdBytes);
return toBuilder().setDirectWriteByteThreshold(thresholdBytes).build();
}

@VisibleForTesting
Contextful<Fn<DestinationT, FileNaming>> resolveFileNamingFn() {
if (getDynamic()) {
Expand Down Expand Up @@ -1565,6 +1577,9 @@ public WriteFilesResult<DestinationT> expand(PCollection<UserT> input) {
if (getBatchMaxBufferingDuration() != null) {
writeFiles = writeFiles.withBatchMaxBufferingDuration(getBatchMaxBufferingDuration());
}
if (getDirectWriteByteThreshold() != null) {
writeFiles = writeFiles.withDirectWriteByteThreshold(getDirectWriteByteThreshold());
}
return input.apply(writeFiles);
}

Expand Down
21 changes: 21 additions & 0 deletions sdks/java/core/src/main/java/org/apache/beam/sdk/io/TextIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,9 @@ public abstract static class TypedWrite<UserT, DestinationT>
/** Batch max buffering duration for input records. */
abstract @Nullable Duration getBatchMaxBufferingDuration();

/** Byte threshold above which a streaming bundle is written directly to temporary files. */
abstract @Nullable Long getDirectWriteByteThreshold();

/** Whether to write windowed output files. */
abstract boolean getWindowedWrites();

Expand Down Expand Up @@ -780,6 +783,9 @@ abstract Builder<UserT, DestinationT> setNumShards(
abstract Builder<UserT, DestinationT> setBatchMaxBufferingDuration(
@Nullable Duration batchMaxBufferingDuration);

abstract Builder<UserT, DestinationT> setDirectWriteByteThreshold(
@Nullable Long directWriteByteThreshold);

abstract Builder<UserT, DestinationT> setWindowedWrites(boolean windowedWrites);

abstract Builder<UserT, DestinationT> setAutoSharding(boolean windowedWrites);
Expand Down Expand Up @@ -926,6 +932,13 @@ public TypedWrite<UserT, DestinationT> withBatchMaxBufferingDuration(
return toBuilder().setBatchMaxBufferingDuration(batchMaxBufferingDuration).build();
}

/** See {@link WriteFiles#withDirectWriteByteThreshold(long)}. */
public TypedWrite<UserT, DestinationT> withDirectWriteByteThreshold(long thresholdBytes) {
checkArgument(
thresholdBytes > 0, "thresholdBytes must be positive, but was: %s", thresholdBytes);
return toBuilder().setDirectWriteByteThreshold(thresholdBytes).build();
}

/** Set the base directory used to generate temporary files. */
public TypedWrite<UserT, DestinationT> withTempDirectory(
ValueProvider<ResourceId> tempDirectory) {
Expand Down Expand Up @@ -1195,6 +1208,9 @@ public WriteFilesResult<DestinationT> expand(PCollection<UserT> input) {
if (getBatchMaxBufferingDuration() != null) {
write = write.withBatchMaxBufferingDuration(getBatchMaxBufferingDuration());
}
if (getDirectWriteByteThreshold() != null) {
write = write.withDirectWriteByteThreshold(getDirectWriteByteThreshold());
}
return input.apply("WriteFiles", write);
}

Expand Down Expand Up @@ -1388,6 +1404,11 @@ public Write withBatchMaxBufferingDuration(@Nullable Duration batchMaxBufferingD
return new Write(inner.withBatchMaxBufferingDuration(batchMaxBufferingDuration));
}

/** See {@link TypedWrite#withDirectWriteByteThreshold(long)}. */
public Write withDirectWriteByteThreshold(long thresholdBytes) {
return new Write(inner.withDirectWriteByteThreshold(thresholdBytes));
}

/**
* Specify that output filenames are wanted.
*
Expand Down
Loading
Loading