1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.regionserver.wal;
21
22 import java.io.EOFException;
23 import java.io.IOException;
24 import java.nio.ByteBuffer;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.hadoop.hbase.classification.InterfaceAudience;
32 import org.apache.hadoop.conf.Configuration;
33 import org.apache.hadoop.fs.Path;
34 import org.apache.hadoop.fs.FileSystem;
35 import org.apache.hadoop.fs.FSDataInputStream;
36 import org.apache.hadoop.hbase.codec.Codec;
37 import org.apache.hadoop.hbase.io.LimitInputStream;
38 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
39 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
40 import org.apache.hadoop.hbase.protobuf.generated.WALProtos;
41 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.WALHeader.Builder;
42 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.WALKey;
43 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.WALTrailer;
44 import org.apache.hadoop.hbase.util.Bytes;
45 import org.apache.hadoop.hbase.wal.WAL.Entry;
46
47 import com.google.protobuf.CodedInputStream;
48
49
50
51
52
53
54
55
56
57
58
59
60 @InterfaceAudience.LimitedPrivate({HBaseInterfaceAudience.COPROC, HBaseInterfaceAudience.PHOENIX,
61 HBaseInterfaceAudience.CONFIG})
62 public class ProtobufLogReader extends ReaderBase {
63 private static final Log LOG = LogFactory.getLog(ProtobufLogReader.class);
64
65 @InterfaceAudience.Private
66 public static final byte[] PB_WAL_MAGIC = Bytes.toBytes("PWAL");
67
68 @InterfaceAudience.Private
69 public static final byte[] PB_WAL_COMPLETE_MAGIC = Bytes.toBytes("LAWP");
70
71
72
73
74 static final String WAL_TRAILER_WARN_SIZE = "hbase.regionserver.waltrailer.warn.size";
75 static final int DEFAULT_WAL_TRAILER_WARN_SIZE = 1024 * 1024;
76
77 protected FSDataInputStream inputStream;
78 protected Codec.Decoder cellDecoder;
79 protected WALCellCodec.ByteStringUncompressor byteStringUncompressor;
80 protected boolean hasCompression = false;
81 protected boolean hasTagCompression = false;
82
83
84 private long walEditsStopOffset;
85 private boolean trailerPresent;
86 protected WALTrailer trailer;
87
88
89 protected int trailerWarnSize;
90 private static List<String> writerClsNames = new ArrayList<String>();
91 static {
92 writerClsNames.add(ProtobufLogWriter.class.getSimpleName());
93 }
94
95
96 private String codecClsName = null;
97
98 enum WALHdrResult {
99 EOF,
100 SUCCESS,
101 UNKNOWN_WRITER_CLS
102 }
103
104
105 static class WALHdrContext {
106 WALHdrResult result;
107 String cellCodecClsName;
108
109 WALHdrContext(WALHdrResult result, String cellCodecClsName) {
110 this.result = result;
111 this.cellCodecClsName = cellCodecClsName;
112 }
113 WALHdrResult getResult() {
114 return result;
115 }
116 String getCellCodecClsName() {
117 return cellCodecClsName;
118 }
119 }
120
121 public ProtobufLogReader() {
122 super();
123 }
124
125 @Override
126 public void close() throws IOException {
127 if (this.inputStream != null) {
128 this.inputStream.close();
129 this.inputStream = null;
130 }
131 }
132
133 @Override
134 public long getPosition() throws IOException {
135 return inputStream.getPos();
136 }
137
138 @Override
139 public void reset() throws IOException {
140 String clsName = initInternal(null, false);
141 initAfterCompression(clsName);
142 }
143
144 @Override
145 public void init(FileSystem fs, Path path, Configuration conf, FSDataInputStream stream)
146 throws IOException {
147 this.trailerWarnSize = conf.getInt(WAL_TRAILER_WARN_SIZE, DEFAULT_WAL_TRAILER_WARN_SIZE);
148 super.init(fs, path, conf, stream);
149 }
150
151 @Override
152 protected String initReader(FSDataInputStream stream) throws IOException {
153 return initInternal(stream, true);
154 }
155
156
157
158
159 public List<String> getWriterClsNames() {
160 return writerClsNames;
161 }
162
163
164
165
166 public String getCodecClsName() {
167 return codecClsName;
168 }
169
170 protected WALHdrContext readHeader(Builder builder, FSDataInputStream stream)
171 throws IOException {
172 boolean res = builder.mergeDelimitedFrom(stream);
173 if (!res) return new WALHdrContext(WALHdrResult.EOF, null);
174 if (builder.hasWriterClsName() &&
175 !getWriterClsNames().contains(builder.getWriterClsName())) {
176 return new WALHdrContext(WALHdrResult.UNKNOWN_WRITER_CLS, null);
177 }
178 String clsName = null;
179 if (builder.hasCellCodecClsName()) {
180 clsName = builder.getCellCodecClsName();
181 }
182 return new WALHdrContext(WALHdrResult.SUCCESS, clsName);
183 }
184
185 private String initInternal(FSDataInputStream stream, boolean isFirst)
186 throws IOException {
187 close();
188 long expectedPos = PB_WAL_MAGIC.length;
189 if (stream == null) {
190 stream = fs.open(path);
191 stream.seek(expectedPos);
192 }
193 if (stream.getPos() != expectedPos) {
194 throw new IOException("The stream is at invalid position: " + stream.getPos());
195 }
196
197 WALProtos.WALHeader.Builder builder = WALProtos.WALHeader.newBuilder();
198 WALHdrContext hdrCtxt = readHeader(builder, stream);
199 WALHdrResult walHdrRes = hdrCtxt.getResult();
200 if (walHdrRes == WALHdrResult.EOF) {
201 throw new EOFException("Couldn't read WAL PB header");
202 }
203 if (walHdrRes == WALHdrResult.UNKNOWN_WRITER_CLS) {
204 throw new IOException("Got unknown writer class: " + builder.getWriterClsName());
205 }
206 if (isFirst) {
207 WALProtos.WALHeader header = builder.build();
208 this.hasCompression = header.hasHasCompression() && header.getHasCompression();
209 this.hasTagCompression = header.hasHasTagCompression() && header.getHasTagCompression();
210 }
211 this.inputStream = stream;
212 this.walEditsStopOffset = this.fileLength;
213 long currentPosition = stream.getPos();
214 trailerPresent = setTrailerIfPresent();
215 this.seekOnFs(currentPosition);
216 if (LOG.isTraceEnabled()) {
217 LOG.trace("After reading the trailer: walEditsStopOffset: " + this.walEditsStopOffset
218 + ", fileLength: " + this.fileLength + ", " + "trailerPresent: " + trailerPresent);
219 }
220
221 codecClsName = hdrCtxt.getCellCodecClsName();
222
223 return hdrCtxt.getCellCodecClsName();
224 }
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243 private boolean setTrailerIfPresent() {
244 try {
245 long trailerSizeOffset = this.fileLength - (PB_WAL_COMPLETE_MAGIC.length + Bytes.SIZEOF_INT);
246 if (trailerSizeOffset <= 0) return false;
247 this.seekOnFs(trailerSizeOffset);
248
249 int trailerSize = this.inputStream.readInt();
250 ByteBuffer buf = ByteBuffer.allocate(ProtobufLogReader.PB_WAL_COMPLETE_MAGIC.length);
251 this.inputStream.readFully(buf.array(), buf.arrayOffset(), buf.capacity());
252 if (!Arrays.equals(buf.array(), PB_WAL_COMPLETE_MAGIC)) {
253 LOG.trace("No trailer found.");
254 return false;
255 }
256 if (trailerSize < 0) {
257 LOG.warn("Invalid trailer Size " + trailerSize + ", ignoring the trailer");
258 return false;
259 } else if (trailerSize > this.trailerWarnSize) {
260
261 LOG.warn("Please investigate WALTrailer usage. Trailer size > maximum configured size : "
262 + trailerSize + " > " + this.trailerWarnSize);
263 }
264
265 long positionOfTrailer = trailerSizeOffset - trailerSize;
266 this.seekOnFs(positionOfTrailer);
267
268 buf = ByteBuffer.allocate(trailerSize);
269 this.inputStream.readFully(buf.array(), buf.arrayOffset(), buf.capacity());
270 trailer = WALTrailer.parseFrom(buf.array());
271 this.walEditsStopOffset = positionOfTrailer;
272 return true;
273 } catch (IOException ioe) {
274 LOG.warn("Got IOE while reading the trailer. Continuing as if no trailer is present.", ioe);
275 }
276 return false;
277 }
278
279 protected WALCellCodec getCodec(Configuration conf, String cellCodecClsName,
280 CompressionContext compressionContext) throws IOException {
281 return WALCellCodec.create(conf, cellCodecClsName, compressionContext);
282 }
283
284 @Override
285 protected void initAfterCompression() throws IOException {
286 initAfterCompression(null);
287 }
288
289 @Override
290 protected void initAfterCompression(String cellCodecClsName) throws IOException {
291 WALCellCodec codec = getCodec(this.conf, cellCodecClsName, this.compressionContext);
292 this.cellDecoder = codec.getDecoder(this.inputStream);
293 if (this.hasCompression) {
294 this.byteStringUncompressor = codec.getByteStringUncompressor();
295 }
296 }
297
298 @Override
299 protected boolean hasCompression() {
300 return this.hasCompression;
301 }
302
303 @Override
304 protected boolean hasTagCompression() {
305 return this.hasTagCompression;
306 }
307
308 @Override
309 protected boolean readNext(Entry entry) throws IOException {
310 while (true) {
311
312 long originalPosition = this.inputStream.getPos();
313 if (trailerPresent && originalPosition > 0 && originalPosition == this.walEditsStopOffset) {
314 return false;
315 }
316 WALKey.Builder builder = WALKey.newBuilder();
317 long size = 0;
318 try {
319 long available = -1;
320 try {
321 int firstByte = this.inputStream.read();
322 if (firstByte == -1) {
323 throw new EOFException("First byte is negative");
324 }
325 size = CodedInputStream.readRawVarint32(firstByte, this.inputStream);
326
327 available = this.inputStream.available();
328 if (available > 0 && available < size) {
329 throw new EOFException("Available stream not enough for edit, " +
330 "inputStream.available()= " + this.inputStream.available() + ", " +
331 "entry size= " + size);
332 }
333 ProtobufUtil.mergeFrom(builder, new LimitInputStream(this.inputStream, size),
334 (int)size);
335 } catch (IOException ipbe) {
336 throw (EOFException) new EOFException("Invalid PB, EOF? Ignoring; originalPosition=" +
337 originalPosition + ", currentPosition=" + this.inputStream.getPos() +
338 ", messageSize=" + size + ", currentAvailable=" + available).initCause(ipbe);
339 }
340 if (!builder.isInitialized()) {
341
342
343 throw new EOFException("Partial PB while reading WAL, " +
344 "probably an unexpected EOF, ignoring");
345 }
346 WALKey walKey = builder.build();
347 entry.getKey().readFieldsFromPb(walKey, this.byteStringUncompressor);
348 if (!walKey.hasFollowingKvCount() || 0 == walKey.getFollowingKvCount()) {
349 LOG.trace("WALKey has no KVs that follow it; trying the next one");
350 continue;
351 }
352 int expectedCells = walKey.getFollowingKvCount();
353 long posBefore = this.inputStream.getPos();
354 try {
355 int actualCells = entry.getEdit().readFromCells(cellDecoder, expectedCells);
356 if (expectedCells != actualCells) {
357 throw new EOFException("Only read " + actualCells);
358 }
359 } catch (Exception ex) {
360 String posAfterStr = "<unknown>";
361 try {
362 posAfterStr = this.inputStream.getPos() + "";
363 } catch (Throwable t) {
364 LOG.trace("Error getting pos for error message - ignoring", t);
365 }
366 String message = " while reading " + expectedCells + " WAL KVs; started reading at "
367 + posBefore + " and read up to " + posAfterStr;
368 IOException realEofEx = extractHiddenEof(ex);
369 throw (EOFException) new EOFException("EOF " + message).
370 initCause(realEofEx != null ? realEofEx : ex);
371 }
372 if (trailerPresent && this.inputStream.getPos() > this.walEditsStopOffset) {
373 LOG.error("Read WALTrailer while reading WALEdits. wal: " + this.path
374 + ", inputStream.getPos(): " + this.inputStream.getPos() + ", walEditsStopOffset: "
375 + this.walEditsStopOffset);
376 throw new EOFException("Read WALTrailer while reading WALEdits");
377 }
378 } catch (EOFException eof) {
379 LOG.trace("Encountered a malformed edit, seeking back to last good position in file", eof);
380
381 if (originalPosition < 0) throw eof;
382
383
384 seekOnFs(originalPosition);
385 return false;
386 }
387 return true;
388 }
389 }
390
391 private IOException extractHiddenEof(Exception ex) {
392
393
394 IOException ioEx = null;
395 if (ex instanceof EOFException) {
396 return (EOFException)ex;
397 } else if (ex instanceof IOException) {
398 ioEx = (IOException)ex;
399 } else if (ex instanceof RuntimeException
400 && ex.getCause() != null && ex.getCause() instanceof IOException) {
401 ioEx = (IOException)ex.getCause();
402 }
403 if (ioEx != null) {
404 if (ioEx.getMessage().contains("EOF")) return ioEx;
405 return null;
406 }
407 return null;
408 }
409
410 @Override
411 protected void seekOnFs(long pos) throws IOException {
412 this.inputStream.seek(pos);
413 }
414 }