Skip to content

Commit 479c756

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

File tree

3 files changed

+60
-14
lines changed

3 files changed

+60
-14
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: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
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;
@@ -47,15 +48,24 @@
4748
* without error on partial bytes if you stop at some special points, but the return message will
4849
* have incorrect field value. We should try our best to prevent this happens because loading an
4950
* incorrect store file list file usually leads to data loss.
51+
* <p/>
52+
* To prevent failing silently while downgrading, where we may miss some newly introduced fields in
53+
* {@link StoreFileList} which are necessary, we introduce a 'version' field in
54+
* {@link StoreFileList}. If we find out that we are reading a {@link StoreFileList} with higher
55+
* version, we will fail immediately and tell users that you need extra steps while downgrading, to
56+
* prevent potential data loss.
5057
*/
5158
@InterfaceAudience.Private
5259
class StoreFileListFile {
5360

5461
private static final Logger LOG = LoggerFactory.getLogger(StoreFileListFile.class);
5562

63+
// the current version for StoreFileList
64+
static final long VERSION = 1;
65+
5666
static final String TRACK_FILE_DIR = ".filelist";
5767

58-
private static final String TRACK_FILE = "f1";
68+
static final String TRACK_FILE = "f1";
5969

6070
private static final String TRACK_FILE_ROTATE = "f2";
6171

@@ -101,7 +111,18 @@ private StoreFileList load(Path path) throws IOException {
101111
throw new IOException(
102112
"Checksum mismatch, expected " + expectedChecksum + ", actual " + calculatedChecksum);
103113
}
104-
return StoreFileList.parseFrom(data);
114+
StoreFileList storeFileList = StoreFileList.parseFrom(data);
115+
if (storeFileList.getVersion() > VERSION) {
116+
LOG.error(
117+
"The loaded store file list is in version {}, which is higher than expected"
118+
+ " version {}. Stop loading to prevent potential data loss. This usually because your"
119+
+ " cluster is downgraded from a newer version. You need extra steps before downgrading,"
120+
+ " like switching back to default store file tracker.",
121+
storeFileList.getVersion(), VERSION);
122+
throw new IOException("Higher store file list version detected, expected " + VERSION
123+
+ ", got " + storeFileList.getVersion());
124+
}
125+
return storeFileList;
105126
}
106127

107128
private int select(StoreFileList[] lists) {
@@ -134,30 +155,38 @@ StoreFileList load() throws IOException {
134155
return lists[winnerIndex];
135156
}
136157

158+
@RestrictedApi(explanation = "Should only be called in tests", link = "",
159+
allowedOnPath = ".*/StoreFileListFile.java|.*/src/test/.*")
160+
static void write(FileSystem fs, Path file, StoreFileList storeFileList) throws IOException {
161+
byte[] data = storeFileList.toByteArray();
162+
CRC32 crc32 = new CRC32();
163+
crc32.update(data);
164+
int checksum = (int) crc32.getValue();
165+
// 4 bytes length at the beginning, plus 4 bytes checksum
166+
try (FSDataOutputStream out = fs.create(file, true)) {
167+
out.writeInt(data.length);
168+
out.write(data);
169+
out.writeInt(checksum);
170+
}
171+
}
172+
137173
/**
138-
* We will set the timestamp in this method so just pass the builder in
174+
* We will set the timestamp and version in this method so just pass the builder in
139175
*/
140176
void update(StoreFileList.Builder builder) throws IOException {
141177
if (nextTrackFile < 0) {
142178
// we need to call load first to load the prevTimestamp and also the next file
143179
load();
144180
}
145-
long timestamp = Math.max(prevTimestamp + 1, EnvironmentEdgeManager.currentTime());
146-
byte[] actualData = builder.setTimestamp(timestamp).build().toByteArray();
147-
CRC32 crc32 = new CRC32();
148-
crc32.update(actualData);
149-
int checksum = (int) crc32.getValue();
150-
// 4 bytes length at the beginning, plus 4 bytes checksum
151181
FileSystem fs = ctx.getRegionFileSystem().getFileSystem();
152-
try (FSDataOutputStream out = fs.create(trackFiles[nextTrackFile], true)) {
153-
out.writeInt(actualData.length);
154-
out.write(actualData);
155-
out.writeInt(checksum);
156-
}
182+
long timestamp = Math.max(prevTimestamp + 1, EnvironmentEdgeManager.currentTime());
183+
write(fs, trackFiles[nextTrackFile],
184+
builder.setTimestamp(timestamp).setVersion(VERSION).build());
157185
// record timestamp
158186
prevTimestamp = timestamp;
159187
// rotate the file
160188
nextTrackFile = 1 - nextTrackFile;
189+
161190
try {
162191
fs.delete(trackFiles[nextTrackFile], false);
163192
} catch (IOException e) {

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

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

20+
import static org.junit.Assert.assertEquals;
2021
import static org.junit.Assert.assertNull;
2122
import static org.junit.Assert.assertThrows;
2223
import static org.mockito.Mockito.mock;
@@ -35,6 +36,7 @@
3536
import org.apache.hadoop.hbase.testclassification.RegionServerTests;
3637
import org.apache.hadoop.hbase.testclassification.SmallTests;
3738
import org.apache.hadoop.hbase.util.Bytes;
39+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
3840
import org.junit.AfterClass;
3941
import org.junit.Before;
4042
import org.junit.ClassRule;
@@ -162,4 +164,18 @@ public void testChecksumMismatch() throws IOException {
162164
write(fs, trackerFileStatus.getPath(), content, 0, content.length);
163165
assertThrows(IOException.class, () -> storeFileListFile.load());
164166
}
167+
168+
@Test
169+
public void testLoadHigherVersion() throws IOException {
170+
// write a fake StoreFileList file with higher version
171+
StoreFileList storeFileList =
172+
StoreFileList.newBuilder().setVersion(StoreFileListFile.VERSION + 1)
173+
.setTimestamp(EnvironmentEdgeManager.currentTime()).build();
174+
Path trackFileDir = new Path(testDir, StoreFileListFile.TRACK_FILE_DIR);
175+
StoreFileListFile.write(FileSystem.get(UTIL.getConfiguration()),
176+
new Path(trackFileDir, StoreFileListFile.TRACK_FILE), storeFileList);
177+
IOException error = assertThrows(IOException.class, () -> storeFileListFile.load());
178+
assertEquals("Higher store file list version detected, expected " + StoreFileListFile.VERSION
179+
+ ", got " + (StoreFileListFile.VERSION + 1), error.getMessage());
180+
}
165181
}

0 commit comments

Comments
 (0)