Skip to content

Commit d668c58

Browse files
committed
HBASE-28457 Introduce a version field in file based tracker record (#5784)
Signed-off-by: Wellington Chevreuil <[email protected]> (cherry picked from commit c1012a9)
1 parent 6a5f9ca commit d668c58

File tree

3 files changed

+64
-16
lines changed

3 files changed

+64
-16
lines changed

hbase-protocol-shaded/src/main/protobuf/StoreFileTracker.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ message StoreFileEntry {
3333
message StoreFileList {
3434
required uint64 timestamp = 1;
3535
repeated StoreFileEntry store_file = 2;
36+
optional uint64 version = 3 [default = 1];
3637
}

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileListFile.java

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
*/
1818
package org.apache.hadoop.hbase.regionserver.storefiletracker;
1919

20+
import com.google.errorprone.annotations.RestrictedApi;
2021
import java.io.EOFException;
2122
import java.io.FileNotFoundException;
2223
import java.io.IOException;
2324
import java.util.ArrayList;
2425
import java.util.Collections;
26+
import java.util.Comparator;
2527
import java.util.List;
2628
import java.util.Map;
2729
import java.util.NavigableMap;
@@ -59,19 +61,28 @@
5961
* without error on partial bytes if you stop at some special points, but the return message will
6062
* have incorrect field value. We should try our best to prevent this happens because loading an
6163
* incorrect store file list file usually leads to data loss.
64+
* <p/>
65+
* To prevent failing silently while downgrading, where we may miss some newly introduced fields in
66+
* {@link StoreFileList} which are necessary, we introduce a 'version' field in
67+
* {@link StoreFileList}. If we find out that we are reading a {@link StoreFileList} with higher
68+
* version, we will fail immediately and tell users that you need extra steps while downgrading, to
69+
* prevent potential data loss.
6270
*/
6371
@InterfaceAudience.Private
6472
class StoreFileListFile {
6573

6674
private static final Logger LOG = LoggerFactory.getLogger(StoreFileListFile.class);
6775

76+
// the current version for StoreFileList
77+
static final long VERSION = 1;
78+
6879
static final String TRACK_FILE_DIR = ".filelist";
6980

70-
private static final String TRACK_FILE_PREFIX = "f1";
81+
static final String TRACK_FILE_PREFIX = "f1";
7182

7283
private static final String TRACK_FILE_ROTATE_PREFIX = "f2";
7384

74-
private static final char TRACK_FILE_SEPARATOR = '.';
85+
static final char TRACK_FILE_SEPARATOR = '.';
7586

7687
static final Pattern TRACK_FILE_PATTERN = Pattern.compile("^f(1|2)\\.\\d+$");
7788

@@ -114,7 +125,18 @@ static StoreFileList load(FileSystem fs, Path path) throws IOException {
114125
throw new IOException(
115126
"Checksum mismatch, expected " + expectedChecksum + ", actual " + calculatedChecksum);
116127
}
117-
return StoreFileList.parseFrom(data);
128+
StoreFileList storeFileList = StoreFileList.parseFrom(data);
129+
if (storeFileList.getVersion() > VERSION) {
130+
LOG.error(
131+
"The loaded store file list is in version {}, which is higher than expected"
132+
+ " version {}. Stop loading to prevent potential data loss. This usually because your"
133+
+ " cluster is downgraded from a newer version. You need extra steps before downgrading,"
134+
+ " like switching back to default store file tracker.",
135+
storeFileList.getVersion(), VERSION);
136+
throw new IOException("Higher store file list version detected, expected " + VERSION
137+
+ ", got " + storeFileList.getVersion());
138+
}
139+
return storeFileList;
118140
}
119141

120142
StoreFileList load(Path path) throws IOException {
@@ -145,7 +167,7 @@ private NavigableMap<Long, List<Path>> listFiles() throws IOException {
145167
if (statuses == null || statuses.length == 0) {
146168
return Collections.emptyNavigableMap();
147169
}
148-
TreeMap<Long, List<Path>> map = new TreeMap<>((l1, l2) -> l2.compareTo(l1));
170+
TreeMap<Long, List<Path>> map = new TreeMap<>(Comparator.reverseOrder());
149171
for (FileStatus status : statuses) {
150172
Path file = status.getPath();
151173
if (!status.isFile()) {
@@ -232,31 +254,39 @@ StoreFileList load(boolean readOnly) throws IOException {
232254
return lists[winnerIndex];
233255
}
234256

257+
@RestrictedApi(explanation = "Should only be called in tests", link = "",
258+
allowedOnPath = ".*/StoreFileListFile.java|.*/src/test/.*")
259+
static void write(FileSystem fs, Path file, StoreFileList storeFileList) throws IOException {
260+
byte[] data = storeFileList.toByteArray();
261+
CRC32 crc32 = new CRC32();
262+
crc32.update(data);
263+
int checksum = (int) crc32.getValue();
264+
// 4 bytes length at the beginning, plus 4 bytes checksum
265+
try (FSDataOutputStream out = fs.create(file, true)) {
266+
out.writeInt(data.length);
267+
out.write(data);
268+
out.writeInt(checksum);
269+
}
270+
}
271+
235272
/**
236-
* We will set the timestamp in this method so just pass the builder in
273+
* We will set the timestamp and version in this method so just pass the builder in
237274
*/
238275
void update(StoreFileList.Builder builder) throws IOException {
239276
if (nextTrackFile < 0) {
240277
// we need to call load first to load the prevTimestamp and also the next file
241278
// we are already in the update method, which is not read only, so pass false
242279
load(false);
243280
}
244-
long timestamp = Math.max(prevTimestamp + 1, EnvironmentEdgeManager.currentTime());
245-
byte[] actualData = builder.setTimestamp(timestamp).build().toByteArray();
246-
CRC32 crc32 = new CRC32();
247-
crc32.update(actualData);
248-
int checksum = (int) crc32.getValue();
249-
// 4 bytes length at the beginning, plus 4 bytes checksum
250281
FileSystem fs = ctx.getRegionFileSystem().getFileSystem();
251-
try (FSDataOutputStream out = fs.create(trackFiles[nextTrackFile], true)) {
252-
out.writeInt(actualData.length);
253-
out.write(actualData);
254-
out.writeInt(checksum);
255-
}
282+
long timestamp = Math.max(prevTimestamp + 1, EnvironmentEdgeManager.currentTime());
283+
write(fs, trackFiles[nextTrackFile],
284+
builder.setTimestamp(timestamp).setVersion(VERSION).build());
256285
// record timestamp
257286
prevTimestamp = timestamp;
258287
// rotate the file
259288
nextTrackFile = 1 - nextTrackFile;
289+
260290
try {
261291
fs.delete(trackFiles[nextTrackFile], false);
262292
} catch (IOException e) {

hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/storefiletracker/TestStoreFileListFile.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.apache.hadoop.hbase.testclassification.RegionServerTests;
3838
import org.apache.hadoop.hbase.testclassification.SmallTests;
3939
import org.apache.hadoop.hbase.util.Bytes;
40+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
4041
import org.junit.AfterClass;
4142
import org.junit.Before;
4243
import org.junit.ClassRule;
@@ -222,4 +223,20 @@ public void testConcurrentUpdate() throws IOException {
222223
assertEquals("hehe", entry.getName());
223224
assertEquals(10, entry.getSize());
224225
}
226+
227+
@Test
228+
public void testLoadHigherVersion() throws IOException {
229+
// write a fake StoreFileList file with higher version
230+
StoreFileList storeFileList =
231+
StoreFileList.newBuilder().setVersion(StoreFileListFile.VERSION + 1)
232+
.setTimestamp(EnvironmentEdgeManager.currentTime()).build();
233+
Path trackFileDir = new Path(testDir, StoreFileListFile.TRACK_FILE_DIR);
234+
StoreFileListFile.write(FileSystem.get(UTIL.getConfiguration()),
235+
new Path(trackFileDir, StoreFileListFile.TRACK_FILE_PREFIX
236+
+ StoreFileListFile.TRACK_FILE_SEPARATOR + EnvironmentEdgeManager.currentTime()),
237+
storeFileList);
238+
IOException error = assertThrows(IOException.class, () -> create().load(false));
239+
assertEquals("Higher store file list version detected, expected " + StoreFileListFile.VERSION
240+
+ ", got " + (StoreFileListFile.VERSION + 1), error.getMessage());
241+
}
225242
}

0 commit comments

Comments
 (0)