3939import org .apache .iceberg .exceptions .NoSuchTableException ;
4040import org .checkerframework .checker .nullness .qual .MonotonicNonNull ;
4141import org .checkerframework .checker .nullness .qual .Nullable ;
42+ import org .joda .time .Duration ;
4243import 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