|
17 | 17 | */ |
18 | 18 | package org.apache.hadoop.hbase.regionserver.storefiletracker; |
19 | 19 |
|
| 20 | +import com.google.errorprone.annotations.RestrictedApi; |
20 | 21 | import java.io.EOFException; |
21 | 22 | import java.io.FileNotFoundException; |
22 | 23 | import java.io.IOException; |
23 | 24 | import java.util.ArrayList; |
24 | 25 | import java.util.Collections; |
| 26 | +import java.util.Comparator; |
25 | 27 | import java.util.List; |
26 | 28 | import java.util.Map; |
27 | 29 | import java.util.NavigableMap; |
|
59 | 61 | * without error on partial bytes if you stop at some special points, but the return message will |
60 | 62 | * have incorrect field value. We should try our best to prevent this happens because loading an |
61 | 63 | * 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. |
62 | 70 | */ |
63 | 71 | @InterfaceAudience.Private |
64 | 72 | class StoreFileListFile { |
65 | 73 |
|
66 | 74 | private static final Logger LOG = LoggerFactory.getLogger(StoreFileListFile.class); |
67 | 75 |
|
| 76 | + // the current version for StoreFileList |
| 77 | + static final long VERSION = 1; |
| 78 | + |
68 | 79 | static final String TRACK_FILE_DIR = ".filelist"; |
69 | 80 |
|
70 | | - private static final String TRACK_FILE_PREFIX = "f1"; |
| 81 | + static final String TRACK_FILE_PREFIX = "f1"; |
71 | 82 |
|
72 | 83 | private static final String TRACK_FILE_ROTATE_PREFIX = "f2"; |
73 | 84 |
|
74 | | - private static final char TRACK_FILE_SEPARATOR = '.'; |
| 85 | + static final char TRACK_FILE_SEPARATOR = '.'; |
75 | 86 |
|
76 | 87 | static final Pattern TRACK_FILE_PATTERN = Pattern.compile("^f(1|2)\\.\\d+$"); |
77 | 88 |
|
@@ -114,7 +125,18 @@ static StoreFileList load(FileSystem fs, Path path) throws IOException { |
114 | 125 | throw new IOException( |
115 | 126 | "Checksum mismatch, expected " + expectedChecksum + ", actual " + calculatedChecksum); |
116 | 127 | } |
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; |
118 | 140 | } |
119 | 141 |
|
120 | 142 | StoreFileList load(Path path) throws IOException { |
@@ -145,7 +167,7 @@ private NavigableMap<Long, List<Path>> listFiles() throws IOException { |
145 | 167 | if (statuses == null || statuses.length == 0) { |
146 | 168 | return Collections.emptyNavigableMap(); |
147 | 169 | } |
148 | | - TreeMap<Long, List<Path>> map = new TreeMap<>((l1, l2) -> l2.compareTo(l1)); |
| 170 | + TreeMap<Long, List<Path>> map = new TreeMap<>(Comparator.reverseOrder()); |
149 | 171 | for (FileStatus status : statuses) { |
150 | 172 | Path file = status.getPath(); |
151 | 173 | if (!status.isFile()) { |
@@ -232,31 +254,39 @@ StoreFileList load(boolean readOnly) throws IOException { |
232 | 254 | return lists[winnerIndex]; |
233 | 255 | } |
234 | 256 |
|
| 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 | + |
235 | 272 | /** |
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 |
237 | 274 | */ |
238 | 275 | void update(StoreFileList.Builder builder) throws IOException { |
239 | 276 | if (nextTrackFile < 0) { |
240 | 277 | // we need to call load first to load the prevTimestamp and also the next file |
241 | 278 | // we are already in the update method, which is not read only, so pass false |
242 | 279 | load(false); |
243 | 280 | } |
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 |
250 | 281 | 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()); |
256 | 285 | // record timestamp |
257 | 286 | prevTimestamp = timestamp; |
258 | 287 | // rotate the file |
259 | 288 | nextTrackFile = 1 - nextTrackFile; |
| 289 | + |
260 | 290 | try { |
261 | 291 | fs.delete(trackFiles[nextTrackFile], false); |
262 | 292 | } catch (IOException e) { |
|
0 commit comments