Skip to content

Commit f1122aa

Browse files
HDDS-14939. Implement version file rewrite logic for path migration across metadata history (#10067)
1 parent be2a915 commit f1122aa

7 files changed

Lines changed: 531 additions & 72 deletions

File tree

hadoop-ozone/dist/src/main/license/bin/LICENSE.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ Apache License 2.0
387387
org.apache.hadoop:hadoop-shaded-protobuf_3_25
388388
org.apache.httpcomponents:httpcore
389389
org.apache.iceberg:iceberg-api
390+
org.apache.iceberg:iceberg-bundled-guava
390391
org.apache.iceberg:iceberg-common
391392
org.apache.iceberg:iceberg-core
392393
org.apache.kerby:kerb-admin

hadoop-ozone/dist/src/main/license/jar-report.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ share/ozone/lib/hppc.jar
8787
share/ozone/lib/httpclient.jar
8888
share/ozone/lib/httpcore.jar
8989
share/ozone/lib/iceberg-api.jar
90+
share/ozone/lib/iceberg-bundled-guava.jar
9091
share/ozone/lib/iceberg-common.jar
9192
share/ozone/lib/iceberg-core.jar
9293
share/ozone/lib/istack-commons-runtime.jar

hadoop-ozone/iceberg/pom.xml

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,6 @@
3737
<dependency>
3838
<groupId>org.apache.iceberg</groupId>
3939
<artifactId>iceberg-api</artifactId>
40-
<exclusions>
41-
<exclusion>
42-
<groupId>org.apache.iceberg</groupId>
43-
<artifactId>iceberg-bundled-guava</artifactId>
44-
</exclusion>
45-
</exclusions>
4640
</dependency>
4741
<dependency>
4842
<groupId>org.apache.iceberg</groupId>
@@ -60,10 +54,6 @@
6054
<groupId>org.apache.httpcomponents.core5</groupId>
6155
<artifactId>httpcore5-h2</artifactId>
6256
</exclusion>
63-
<exclusion>
64-
<groupId>org.apache.iceberg</groupId>
65-
<artifactId>iceberg-bundled-guava</artifactId>
66-
</exclusion>
6757
<exclusion>
6858
<groupId>org.checkerframework</groupId>
6959
<artifactId>checker-qual</artifactId>
@@ -74,6 +64,22 @@
7464
</exclusion>
7565
</exclusions>
7666
</dependency>
67+
68+
<dependency>
69+
<groupId>org.slf4j</groupId>
70+
<artifactId>slf4j-api</artifactId>
71+
</dependency>
72+
<dependency>
73+
<groupId>org.apache.hadoop</groupId>
74+
<artifactId>hadoop-common</artifactId>
75+
<scope>test</scope>
76+
<exclusions>
77+
<exclusion>
78+
<groupId>org.apache.avro</groupId>
79+
<artifactId>avro</artifactId>
80+
</exclusion>
81+
</exclusions>
82+
</dependency>
7783
</dependencies>
7884

7985
<build>
Lines changed: 97 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,55 @@
11
/*
2-
* Licensed to the Apache Software Foundation (ASF) under one
3-
* or more contributor license agreements. See the NOTICE file
4-
* distributed with this work for additional information
5-
* regarding copyright ownership. The ASF licenses this file
6-
* to you under the Apache License, Version 2.0 (the
7-
* "License"); you may not use this file except in compliance
8-
* with the License. You may obtain a copy of the License at
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
98
*
10-
* http://www.apache.org/licenses/LICENSE-2.0
9+
* http://www.apache.org/licenses/LICENSE-2.0
1110
*
12-
* Unless required by applicable law or agreed to in writing,
13-
* software distributed under the License is distributed on an
14-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15-
* KIND, either express or implied. See the License for the
16-
* specific language governing permissions and limitations
17-
* under the License.
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
1816
*/
17+
1918
package org.apache.hadoop.ozone.iceberg;
2019

20+
import java.util.HashSet;
21+
import java.util.List;
2122
import java.util.Objects;
23+
import java.util.Set;
2224
import java.util.UUID;
2325
import java.util.concurrent.ExecutorService;
2426
import java.util.concurrent.Executors;
2527
import java.util.concurrent.TimeUnit;
26-
2728
import org.apache.iceberg.HasTableOperations;
29+
import org.apache.iceberg.PartitionStatisticsFile;
2830
import org.apache.iceberg.RewriteTablePathUtil;
31+
import org.apache.iceberg.RewriteTablePathUtil.RewriteResult;
32+
import org.apache.iceberg.Snapshot;
33+
import org.apache.iceberg.StaticTableOperations;
2934
import org.apache.iceberg.Table;
3035
import org.apache.iceberg.TableMetadata;
3136
import org.apache.iceberg.TableMetadata.MetadataLogEntry;
37+
import org.apache.iceberg.TableMetadataParser;
3238
import org.apache.iceberg.actions.ImmutableRewriteTablePath;
3339
import org.apache.iceberg.actions.RewriteTablePath;
40+
import org.apache.iceberg.util.Pair;
3441

42+
/**
43+
* An implementation of {@link RewriteTablePath} for Apache Ozone backed Iceberg tables.
44+
*
45+
* <p>This action rewrites table's metadata and position delete file paths by replacing a source
46+
* prefix with a target prefix. It processes table versions, snapshots, manifests and position delete files.</p>
47+
*
48+
* <p>The rewrite can be scoped between optional start and end metadata versions,
49+
* and all rewritten files are staged in a temporary directory.</p>
50+
*/
3551
public class RewriteTablePathOzoneAction implements RewriteTablePath {
3652

37-
private static final String RESULT_LOCATION = "file-list";
38-
3953
private String sourcePrefix;
4054
private String targetPrefix;
4155
private String startVersionName;
@@ -44,7 +58,6 @@ public class RewriteTablePathOzoneAction implements RewriteTablePath {
4458
private int parallelism;
4559

4660
private final Table table;
47-
private ExecutorService executorService;
4861

4962
public RewriteTablePathOzoneAction(Table table) {
5063
this.table = table;
@@ -58,38 +71,39 @@ public RewriteTablePathOzoneAction(Table table, int parallelism) {
5871

5972
@Override
6073
public RewriteTablePath rewriteLocationPrefix(String sPrefix, String tPrefix) {
61-
checkNonNullNonEmpty(sPrefix, "Source prefix");
62-
checkNonNullNonEmpty(tPrefix, "Target prefix");
74+
RewriteTablePathOzoneUtils.checkNonNullNonEmpty(sPrefix, "Source prefix");
75+
RewriteTablePathOzoneUtils.checkNonNullNonEmpty(tPrefix, "Target prefix");
6376
this.sourcePrefix = sPrefix;
6477
this.targetPrefix = tPrefix;
6578
return this;
6679
}
6780

6881
@Override
6982
public RewriteTablePath startVersion(String sVersion) {
70-
checkNonNullNonEmpty(sVersion, "Start version");
83+
RewriteTablePathOzoneUtils.checkNonNullNonEmpty(sVersion, "Start version");
7184
this.startVersionName = sVersion;
7285
return this;
7386
}
7487

7588
@Override
7689
public RewriteTablePath endVersion(String eVersion) {
77-
checkNonNullNonEmpty(eVersion, "End version");
90+
RewriteTablePathOzoneUtils.checkNonNullNonEmpty(eVersion, "End version");
7891
this.endVersionName = eVersion;
7992
return this;
8093
}
8194

8295
@Override
8396
public RewriteTablePath stagingLocation(String stagingLocation) {
84-
checkNonNullNonEmpty(stagingLocation, "Staging location");
97+
RewriteTablePathOzoneUtils.checkNonNullNonEmpty(stagingLocation, "Staging location");
8598
this.stagingDir = stagingLocation;
8699
return this;
87100
}
88101

89102
@Override
90103
public Result execute() {
91104
validateInputs();
92-
this.executorService = Executors.newFixedThreadPool(parallelism);
105+
// TODO: should use for parallel manifest and position delete file rewriting.
106+
ExecutorService executorService = Executors.newFixedThreadPool(parallelism);
93107
try {
94108
return doExecute();
95109
} finally {
@@ -121,12 +135,13 @@ private void validateInputs() {
121135
"Source prefix cannot be the same as target prefix (%s)", sourcePrefix));
122136
}
123137

124-
validateAndSetEndVersion();
125-
validateAndSetStartVersion();
138+
TableMetadata tableMetadata = ((HasTableOperations) table).operations().current();
139+
validateAndSetEndVersion(tableMetadata);
140+
validateAndSetStartVersion(tableMetadata);
126141

127142
if (stagingDir == null) {
128143
stagingDir =
129-
getMetadataLocation(table)
144+
RewriteTablePathOzoneUtils.getMetadataLocation(table)
130145
+ "copy-table-staging-"
131146
+ UUID.randomUUID()
132147
+ RewriteTablePathUtil.FILE_SEPARATOR;
@@ -135,9 +150,7 @@ private void validateInputs() {
135150
}
136151
}
137152

138-
private void validateAndSetEndVersion() {
139-
TableMetadata tableMetadata = ((HasTableOperations) table).operations().current();
140-
153+
private void validateAndSetEndVersion(TableMetadata tableMetadata) {
141154
if (endVersionName == null) {
142155
Objects.requireNonNull(
143156
tableMetadata.metadataFileLocation(), "Metadata file location should not be null");
@@ -147,9 +160,7 @@ private void validateAndSetEndVersion() {
147160
}
148161
}
149162

150-
private void validateAndSetStartVersion() {
151-
TableMetadata tableMetadata = ((HasTableOperations) table).operations().current();
152-
163+
private void validateAndSetStartVersion(TableMetadata tableMetadata) {
153164
if (startVersionName != null) {
154165
this.startVersionName = validateVersion(tableMetadata, startVersionName);
155166
}
@@ -159,11 +170,12 @@ private String validateVersion(TableMetadata tableMetadata, String versionFileNa
159170
String versionFile = null;
160171
if (versionInFilePath(tableMetadata.metadataFileLocation(), versionFileName)) {
161172
versionFile = tableMetadata.metadataFileLocation();
162-
}
163-
164-
for (MetadataLogEntry log : tableMetadata.previousFiles()) {
165-
if (versionInFilePath(log.file(), versionFileName)) {
166-
versionFile = log.file();
173+
} else {
174+
for (MetadataLogEntry log : tableMetadata.previousFiles()) {
175+
if (versionInFilePath(log.file(), versionFileName)) {
176+
versionFile = log.file();
177+
break;
178+
}
167179
}
168180
}
169181

@@ -172,7 +184,7 @@ private String validateVersion(TableMetadata tableMetadata, String versionFileNa
172184
String.format(
173185
"Cannot find provided version file %s in metadata log.", versionFileName));
174186
}
175-
if (!fileExist(versionFile)) {
187+
if (!RewriteTablePathOzoneUtils.fileExist(versionFile, table.io())) {
176188
throw new IllegalArgumentException(
177189
String.format("Version file %s does not exist.", versionFile));
178190
}
@@ -184,36 +196,59 @@ private boolean versionInFilePath(String path, String version) {
184196
}
185197

186198
private String rebuildMetadata() {
187-
//TODO need to implement rewrite of metadata files , manifest list , manifest files and position delete files.
188-
return null;
189-
}
199+
//TODO need to implement rewrite of manifest list , manifest files and position delete files.
200+
TableMetadata endMetadata = new StaticTableOperations(endVersionName, table.io()).current();
190201

191-
private boolean fileExist(String path) {
192-
if (path == null || path.trim().isEmpty()) {
193-
return false;
202+
List<PartitionStatisticsFile> partitionStats = endMetadata.partitionStatisticsFiles();
203+
if (partitionStats != null && !partitionStats.isEmpty()) {
204+
throw new IllegalArgumentException("Partition statistics files are not supported yet.");
194205
}
195-
return table.io().newInputFile(path).exists();
206+
207+
RewriteResult<Snapshot> rewriteVersionResult = rewriteVersionFiles(endMetadata);
208+
209+
Set<Pair<String, String>> copyPlan = new HashSet<>();
210+
copyPlan.addAll(rewriteVersionResult.copyPlan());
211+
212+
return RewriteTablePathOzoneUtils.saveFileList(copyPlan, stagingDir, table.io());
196213
}
197214

198-
private String getMetadataLocation(Table tbl) {
199-
String currentMetadataPath =
200-
((HasTableOperations) tbl).operations().current().metadataFileLocation();
201-
int lastIndex = currentMetadataPath.lastIndexOf(RewriteTablePathUtil.FILE_SEPARATOR);
202-
String metadataDir = "";
203-
if (lastIndex != -1) {
204-
metadataDir = currentMetadataPath.substring(0, lastIndex + 1);
205-
}
215+
private RewriteResult<Snapshot> rewriteVersionFiles(TableMetadata endMetadata) {
216+
RewriteResult<Snapshot> result = new RewriteResult<>();
217+
result.toRewrite().addAll(endMetadata.snapshots());
218+
result.copyPlan().addAll(rewriteVersionFile(endMetadata, endVersionName));
219+
220+
List<MetadataLogEntry> versions = endMetadata.previousFiles();
221+
for (int i = versions.size() - 1; i >= 0; i--) {
222+
String versionFilePath = versions.get(i).file();
223+
if (versionFilePath.equals(startVersionName)) {
224+
break;
225+
}
226+
227+
if (!RewriteTablePathOzoneUtils.fileExist(versionFilePath, table.io())) {
228+
throw new IllegalArgumentException(String.format("Version file %s doesn't exist", versionFilePath));
229+
}
230+
231+
TableMetadata tableMetadata = new StaticTableOperations(versionFilePath, table.io()).current();
206232

207-
if (metadataDir.isEmpty()) {
208-
throw new IllegalArgumentException("Failed to get the metadata file root directory");
233+
result.toRewrite().addAll(tableMetadata.snapshots());
234+
result.copyPlan().addAll(rewriteVersionFile(tableMetadata, versionFilePath));
209235
}
210-
return metadataDir;
236+
237+
return result;
211238
}
212239

213-
private static void checkNonNullNonEmpty(String value, String name) {
214-
Objects.requireNonNull(value, () -> name + " is null");
215-
if (value.trim().isEmpty()) {
216-
throw new IllegalArgumentException(name + " is empty");
217-
}
240+
private Set<Pair<String, String>> rewriteVersionFile(TableMetadata metadata, String versionFilePath) {
241+
Set<Pair<String, String>> result = new HashSet<>();
242+
String stagingPath = RewriteTablePathUtil.stagingPath(versionFilePath, sourcePrefix, stagingDir);
243+
244+
System.out.println("Processing version file " + versionFilePath);
245+
TableMetadata newTableMetadata = RewriteTablePathUtil.replacePaths(metadata, sourcePrefix, targetPrefix);
246+
TableMetadataParser.overwrite(newTableMetadata, table.io().newOutputFile(stagingPath));
247+
248+
result.add(Pair.of(stagingPath, RewriteTablePathUtil.newPath(versionFilePath, sourcePrefix, targetPrefix)));
249+
result.addAll(RewriteTablePathOzoneUtils.statsFileCopyPlan(
250+
metadata.statisticsFiles(), newTableMetadata.statisticsFiles()));
251+
252+
return result;
218253
}
219254
}

0 commit comments

Comments
 (0)