|
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; |
|
47 | 48 | * without error on partial bytes if you stop at some special points, but the return message will |
48 | 49 | * have incorrect field value. We should try our best to prevent this happens because loading an |
49 | 50 | * 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. |
50 | 57 | */ |
51 | 58 | @InterfaceAudience.Private |
52 | 59 | class StoreFileListFile { |
53 | 60 |
|
54 | 61 | private static final Logger LOG = LoggerFactory.getLogger(StoreFileListFile.class); |
55 | 62 |
|
| 63 | + // the current version for StoreFileList |
| 64 | + static final long VERSION = 1; |
| 65 | + |
56 | 66 | static final String TRACK_FILE_DIR = ".filelist"; |
57 | 67 |
|
58 | | - private static final String TRACK_FILE = "f1"; |
| 68 | + static final String TRACK_FILE = "f1"; |
59 | 69 |
|
60 | 70 | private static final String TRACK_FILE_ROTATE = "f2"; |
61 | 71 |
|
@@ -101,7 +111,18 @@ private StoreFileList load(Path path) throws IOException { |
101 | 111 | throw new IOException( |
102 | 112 | "Checksum mismatch, expected " + expectedChecksum + ", actual " + calculatedChecksum); |
103 | 113 | } |
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; |
105 | 126 | } |
106 | 127 |
|
107 | 128 | private int select(StoreFileList[] lists) { |
@@ -134,30 +155,38 @@ StoreFileList load() throws IOException { |
134 | 155 | return lists[winnerIndex]; |
135 | 156 | } |
136 | 157 |
|
| 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 | + |
137 | 173 | /** |
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 |
139 | 175 | */ |
140 | 176 | void update(StoreFileList.Builder builder) throws IOException { |
141 | 177 | if (nextTrackFile < 0) { |
142 | 178 | // we need to call load first to load the prevTimestamp and also the next file |
143 | 179 | load(); |
144 | 180 | } |
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 |
151 | 181 | 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()); |
157 | 185 | // record timestamp |
158 | 186 | prevTimestamp = timestamp; |
159 | 187 | // rotate the file |
160 | 188 | nextTrackFile = 1 - nextTrackFile; |
| 189 | + |
161 | 190 | try { |
162 | 191 | fs.delete(trackFiles[nextTrackFile], false); |
163 | 192 | } catch (IOException e) { |
|
0 commit comments