Skip to content

Commit 2375fed

Browse files
Refresh Iceberg partition specs periodically (#38408)
1 parent ba63482 commit 2375fed

1 file changed

Lines changed: 44 additions & 3 deletions

File tree

sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/AssignDestinationsAndPartitions.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.apache.iceberg.exceptions.NoSuchTableException;
4040
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
4141
import org.checkerframework.checker.nullness.qual.Nullable;
42+
import org.joda.time.Duration;
4243
import org.joda.time.Instant;
4344

4445
/**
@@ -51,8 +52,10 @@ class AssignDestinationsAndPartitions
5152

5253
private final DynamicDestinations dynamicDestinations;
5354
private final IcebergCatalogConfig catalogConfig;
55+
5456
static final String DESTINATION = "destination";
5557
static final String PARTITION = "partition";
58+
5659
static final org.apache.beam.sdk.schemas.Schema OUTPUT_SCHEMA =
5760
org.apache.beam.sdk.schemas.Schema.builder()
5861
.addStringField(DESTINATION)
@@ -75,8 +78,13 @@ public PCollection<KV<Row, Row>> expand(PCollection<Row> input) {
7578
}
7679

7780
static class AssignDoFn extends DoFn<Row, KV<Row, Row>> {
81+
82+
private static final Duration REFRESH_INTERVAL = Duration.standardMinutes(5);
83+
7884
private transient @MonotonicNonNull Map<String, PartitionKey> partitionKeys;
7985
private transient @MonotonicNonNull Map<String, BeamRowWrapper> wrappers;
86+
private transient @MonotonicNonNull Map<String, Instant> lastRefreshTimes;
87+
8088
private final DynamicDestinations dynamicDestinations;
8189
private final IcebergCatalogConfig catalogConfig;
8290

@@ -89,6 +97,7 @@ static class AssignDoFn extends DoFn<Row, KV<Row, Row>> {
8997
public void setup() {
9098
this.wrappers = new HashMap<>();
9199
this.partitionKeys = new HashMap<>();
100+
this.lastRefreshTimes = new HashMap<>();
92101
}
93102

94103
@ProcessElement
@@ -98,42 +107,74 @@ public void processElement(
98107
PaneInfo paneInfo,
99108
@Timestamp Instant timestamp,
100109
OutputReceiver<KV<Row, Row>> out) {
110+
101111
String tableIdentifier =
102112
dynamicDestinations.getTableStringIdentifier(
103113
ValueInSingleWindow.of(element, timestamp, window, paneInfo));
114+
104115
Row data = dynamicDestinations.getData(element);
105116

106117
@Nullable PartitionKey partitionKey = checkStateNotNull(partitionKeys).get(tableIdentifier);
118+
107119
@Nullable BeamRowWrapper wrapper = checkStateNotNull(wrappers).get(tableIdentifier);
108-
if (partitionKey == null || wrapper == null) {
120+
121+
@Nullable Instant lastRefresh = checkStateNotNull(lastRefreshTimes).get(tableIdentifier);
122+
123+
Instant now = Instant.now();
124+
125+
boolean shouldRefresh =
126+
partitionKey == null
127+
|| wrapper == null
128+
|| lastRefresh == null
129+
|| now.isAfter(lastRefresh.plus(REFRESH_INTERVAL));
130+
131+
if (shouldRefresh) {
132+
109133
PartitionSpec spec = PartitionSpec.unpartitioned();
134+
110135
Schema schema = IcebergUtils.beamSchemaToIcebergSchema(data.getSchema());
136+
111137
@Nullable
112138
IcebergTableCreateConfig createConfig =
113139
dynamicDestinations.instantiateDestination(tableIdentifier).getTableCreateConfig();
140+
114141
if (createConfig != null && createConfig.getPartitionFields() != null) {
142+
115143
spec =
116144
PartitionUtils.toPartitionSpec(createConfig.getPartitionFields(), data.getSchema());
145+
117146
} else {
147+
118148
try {
119149
// see if table already exists with a spec
120-
// TODO(https://github.com/apache/beam/issues/38337): improve this by periodically
121-
// refreshing the table to fetch updated specs
122150
spec = catalogConfig.catalog().loadTable(TableIdentifier.parse(tableIdentifier)).spec();
151+
123152
} catch (NoSuchTableException ignored) {
124153
// no partition to apply
125154
}
126155
}
156+
127157
partitionKey = new PartitionKey(spec, schema);
158+
128159
wrapper = new BeamRowWrapper(data.getSchema(), schema.asStruct());
160+
129161
checkStateNotNull(partitionKeys).put(tableIdentifier, partitionKey);
162+
130163
checkStateNotNull(wrappers).put(tableIdentifier, wrapper);
164+
165+
checkStateNotNull(lastRefreshTimes).put(tableIdentifier, now);
131166
}
167+
168+
partitionKey = checkStateNotNull(partitionKey);
169+
wrapper = checkStateNotNull(wrapper);
170+
132171
partitionKey.partition(wrapper.wrap(data));
172+
133173
String partitionPath = partitionKey.toPath();
134174

135175
Row destAndPartition =
136176
Row.withSchema(OUTPUT_SCHEMA).addValues(tableIdentifier, partitionPath).build();
177+
137178
out.output(KV.of(destAndPartition, data));
138179
}
139180
}

0 commit comments

Comments
 (0)