1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.regionserver.compactions;
19
20 import java.io.IOException;
21 import java.io.InterruptedIOException;
22 import java.security.PrivilegedExceptionAction;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Map;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.hadoop.conf.Configuration;
32 import org.apache.hadoop.fs.Path;
33 import org.apache.hadoop.hbase.Cell;
34 import org.apache.hadoop.hbase.CellUtil;
35 import org.apache.hadoop.hbase.HConstants;
36 import org.apache.hadoop.hbase.KeyValueUtil;
37 import org.apache.hadoop.hbase.classification.InterfaceAudience;
38 import org.apache.hadoop.hbase.client.Scan;
39 import org.apache.hadoop.hbase.io.compress.Compression;
40 import org.apache.hadoop.hbase.io.hfile.HFile.FileInfo;
41 import org.apache.hadoop.hbase.io.hfile.HFileWriterV2;
42 import org.apache.hadoop.hbase.regionserver.HStore;
43 import org.apache.hadoop.hbase.regionserver.InternalScanner;
44 import org.apache.hadoop.hbase.regionserver.ScanType;
45 import org.apache.hadoop.hbase.regionserver.ScannerContext;
46 import org.apache.hadoop.hbase.regionserver.Store;
47 import org.apache.hadoop.hbase.regionserver.StoreFile;
48 import org.apache.hadoop.hbase.regionserver.StoreFile.Writer;
49 import org.apache.hadoop.hbase.regionserver.StoreFileScanner;
50 import org.apache.hadoop.hbase.regionserver.StoreScanner;
51 import org.apache.hadoop.hbase.regionserver.compactions.Compactor.CellSink;
52 import org.apache.hadoop.hbase.regionserver.throttle.ThroughputControlUtil;
53 import org.apache.hadoop.hbase.regionserver.throttle.ThroughputController;
54 import org.apache.hadoop.hbase.security.User;
55 import org.apache.hadoop.hbase.util.Bytes;
56 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
57 import org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix;
58
59 import com.google.common.io.Closeables;
60
61
62
63
64
65 @InterfaceAudience.Private
66 public abstract class Compactor<T extends CellSink> {
67 private static final Log LOG = LogFactory.getLog(Compactor.class);
68
69 protected volatile CompactionProgress progress;
70
71 protected final Configuration conf;
72 protected final Store store;
73
74 protected final int compactionKVMax;
75 protected final Compression.Algorithm compactionCompression;
76
77
78 protected int keepSeqIdPeriod;
79
80
81 protected static final String MAJOR_COMPACTION_DROP_CACHE =
82 "hbase.regionserver.majorcompaction.pagecache.drop";
83 protected static final String MINOR_COMPACTION_DROP_CACHE =
84 "hbase.regionserver.minorcompaction.pagecache.drop";
85
86 private boolean dropCacheMajor;
87 private boolean dropCacheMinor;
88
89
90 Compactor(final Configuration conf, final Store store) {
91 this.conf = conf;
92 this.store = store;
93 this.compactionKVMax =
94 this.conf.getInt(HConstants.COMPACTION_KV_MAX, HConstants.COMPACTION_KV_MAX_DEFAULT);
95 this.compactionCompression = (this.store.getFamily() == null) ?
96 Compression.Algorithm.NONE : this.store.getFamily().getCompactionCompression();
97 this.keepSeqIdPeriod = Math.max(this.conf.getInt(HConstants.KEEP_SEQID_PERIOD,
98 HConstants.MIN_KEEP_SEQID_PERIOD), HConstants.MIN_KEEP_SEQID_PERIOD);
99 this.dropCacheMajor = conf.getBoolean(MAJOR_COMPACTION_DROP_CACHE, true);
100 this.dropCacheMinor = conf.getBoolean(MINOR_COMPACTION_DROP_CACHE, true);
101 }
102
103 public interface CellSink {
104 void append(Cell cell) throws IOException;
105 }
106
107 protected interface CellSinkFactory<S> {
108 S createWriter(InternalScanner scanner, FileDetails fd, boolean shouldDropBehind)
109 throws IOException;
110 }
111
112 public CompactionProgress getProgress() {
113 return this.progress;
114 }
115
116
117 protected static class FileDetails {
118
119 public long maxKeyCount = 0;
120
121 public long earliestPutTs = HConstants.LATEST_TIMESTAMP;
122
123 public long maxSeqId = 0;
124
125 public long maxMVCCReadpoint = 0;
126
127 public int maxTagsLength = 0;
128
129 public long minSeqIdToKeep = 0;
130 }
131
132
133
134
135
136
137
138 protected FileDetails getFileDetails(
139 Collection<StoreFile> filesToCompact, boolean allFiles) throws IOException {
140 FileDetails fd = new FileDetails();
141 long oldestHFileTimeStampToKeepMVCC = System.currentTimeMillis() -
142 (1000L * 60 * 60 * 24 * this.keepSeqIdPeriod);
143
144 for (StoreFile file : filesToCompact) {
145 if(allFiles && (file.getModificationTimeStamp() < oldestHFileTimeStampToKeepMVCC)) {
146
147
148 if(fd.minSeqIdToKeep < file.getMaxMemstoreTS()) {
149 fd.minSeqIdToKeep = file.getMaxMemstoreTS();
150 }
151 }
152 long seqNum = file.getMaxSequenceId();
153 fd.maxSeqId = Math.max(fd.maxSeqId, seqNum);
154 StoreFile.Reader r = file.getReader();
155 if (r == null) {
156 LOG.warn("Null reader for " + file.getPath());
157 continue;
158 }
159
160
161
162 long keyCount = r.getEntries();
163 fd.maxKeyCount += keyCount;
164
165 Map<byte[], byte[]> fileInfo = r.loadFileInfo();
166 byte[] tmp = null;
167
168
169 if (r.isBulkLoaded()) {
170 fd.maxMVCCReadpoint = Math.max(fd.maxMVCCReadpoint, r.getSequenceID());
171 }
172 else {
173 tmp = fileInfo.get(HFileWriterV2.MAX_MEMSTORE_TS_KEY);
174 if (tmp != null) {
175 fd.maxMVCCReadpoint = Math.max(fd.maxMVCCReadpoint, Bytes.toLong(tmp));
176 }
177 }
178 tmp = fileInfo.get(FileInfo.MAX_TAGS_LEN);
179 if (tmp != null) {
180 fd.maxTagsLength = Math.max(fd.maxTagsLength, Bytes.toInt(tmp));
181 }
182
183
184 long earliestPutTs = 0;
185 if (allFiles) {
186 tmp = fileInfo.get(StoreFile.EARLIEST_PUT_TS);
187 if (tmp == null) {
188
189
190 fd.earliestPutTs = earliestPutTs = HConstants.OLDEST_TIMESTAMP;
191 } else {
192 earliestPutTs = Bytes.toLong(tmp);
193 fd.earliestPutTs = Math.min(fd.earliestPutTs, earliestPutTs);
194 }
195 }
196 if (LOG.isDebugEnabled()) {
197 LOG.debug("Compacting " + file +
198 ", keycount=" + keyCount +
199 ", bloomtype=" + r.getBloomFilterType().toString() +
200 ", size=" + TraditionalBinaryPrefix.long2String(r.length(), "", 1) +
201 ", encoding=" + r.getHFileReader().getDataBlockEncoding() +
202 ", compression=" + compactionCompression +
203 ", seqNum=" + seqNum +
204 (allFiles ? ", earliestPutTs=" + earliestPutTs: ""));
205 }
206 }
207 return fd;
208 }
209
210
211
212
213
214
215 protected List<StoreFileScanner> createFileScanners(
216 final Collection<StoreFile> filesToCompact,
217 long smallestReadPoint,
218 boolean useDropBehind) throws IOException {
219 return StoreFileScanner.getScannersForStoreFiles(filesToCompact,
220
221
222
223
224 smallestReadPoint);
225 }
226
227 protected long getSmallestReadPoint() {
228 return store.getSmallestReadPoint();
229 }
230
231 protected interface InternalScannerFactory {
232
233 ScanType getScanType(CompactionRequest request);
234
235 InternalScanner createScanner(List<StoreFileScanner> scanners, ScanType scanType,
236 FileDetails fd, long smallestReadPoint) throws IOException;
237 }
238
239 protected final InternalScannerFactory defaultScannerFactory = new InternalScannerFactory() {
240
241 @Override
242 public ScanType getScanType(CompactionRequest request) {
243 return request.isAllFiles() ? ScanType.COMPACT_DROP_DELETES
244 : ScanType.COMPACT_RETAIN_DELETES;
245 }
246
247 @Override
248 public InternalScanner createScanner(List<StoreFileScanner> scanners, ScanType scanType,
249 FileDetails fd, long smallestReadPoint) throws IOException {
250 return Compactor.this.createScanner(store, scanners, scanType, smallestReadPoint,
251 fd.earliestPutTs);
252 }
253 };
254
255
256
257
258
259
260
261 protected Writer createTmpWriter(FileDetails fd, boolean shouldDropBehind) throws IOException {
262
263
264 return store.createWriterInTmp(fd.maxKeyCount, this.compactionCompression,
265
266
267
268 }
269
270 protected List<Path> compact(final CompactionRequest request,
271 InternalScannerFactory scannerFactory, CellSinkFactory<T> sinkFactory,
272 ThroughputController throughputController, User user) throws IOException {
273 FileDetails fd = getFileDetails(request.getFiles(), request.isAllFiles());
274 this.progress = new CompactionProgress(fd.maxKeyCount);
275
276
277 long smallestReadPoint = getSmallestReadPoint();
278
279 List<StoreFileScanner> scanners;
280 Collection<StoreFile> readersToClose;
281 T writer = null;
282 boolean dropCache;
283 if (request.isMajor() || request.isAllFiles()) {
284 dropCache = this.dropCacheMajor;
285 } else {
286 dropCache = this.dropCacheMinor;
287 }
288
289 if (this.conf.getBoolean("hbase.regionserver.compaction.private.readers", true)) {
290
291
292 readersToClose = new ArrayList<StoreFile>(request.getFiles().size());
293 for (StoreFile f : request.getFiles()) {
294 readersToClose.add(f.cloneForReader());
295 }
296 scanners = createFileScanners(readersToClose, smallestReadPoint, dropCache);
297 } else {
298 readersToClose = Collections.emptyList();
299 scanners = createFileScanners(request.getFiles(), smallestReadPoint, dropCache);
300 }
301 InternalScanner scanner = null;
302 boolean finished = false;
303 try {
304
305 ScanType scanType = scannerFactory.getScanType(request);
306 scanner = preCreateCoprocScanner(request, scanType, fd.earliestPutTs, scanners);
307 if (scanner == null) {
308 scanner = scannerFactory.createScanner(scanners, scanType, fd, smallestReadPoint);
309 }
310 scanner = postCreateCoprocScanner(request, scanType, scanner, user);
311 if (scanner == null) {
312
313 return new ArrayList<Path>();
314 }
315 boolean cleanSeqId = false;
316 if (fd.minSeqIdToKeep > 0) {
317 smallestReadPoint = Math.min(fd.minSeqIdToKeep, smallestReadPoint);
318 cleanSeqId = true;
319 }
320 writer = sinkFactory.createWriter(scanner, fd, dropCache);
321 finished =
322 performCompaction(scanner, writer, smallestReadPoint, cleanSeqId, throughputController);
323 if (!finished) {
324 throw new InterruptedIOException("Aborting compaction of store " + store + " in region "
325 + store.getRegionInfo().getRegionNameAsString() + " because it was interrupted.");
326 }
327 } finally {
328 Closeables.close(scanner, true);
329 for (StoreFile f : readersToClose) {
330 try {
331 f.closeReader(true);
332 } catch (IOException e) {
333 LOG.warn("Exception closing " + f, e);
334 }
335 }
336 if (!finished && writer != null) {
337 abortWriter(writer);
338 }
339 }
340 assert finished : "We should have exited the method on all error paths";
341 assert writer != null : "Writer should be non-null if no error";
342 return commitWriter(writer, fd, request);
343 }
344
345 protected abstract List<Path> commitWriter(T writer, FileDetails fd, CompactionRequest request)
346 throws IOException;
347
348 protected abstract void abortWriter(T writer) throws IOException;
349
350
351
352
353
354
355
356
357
358 protected InternalScanner preCreateCoprocScanner(final CompactionRequest request,
359 ScanType scanType, long earliestPutTs, List<StoreFileScanner> scanners) throws IOException {
360 return preCreateCoprocScanner(request, scanType, earliestPutTs, scanners, null);
361 }
362
363 protected InternalScanner preCreateCoprocScanner(final CompactionRequest request,
364 final ScanType scanType, final long earliestPutTs, final List<StoreFileScanner> scanners,
365 User user) throws IOException {
366 if (store.getCoprocessorHost() == null) {
367 return null;
368 }
369 if (user == null) {
370 return store.getCoprocessorHost().preCompactScannerOpen(store, scanners, scanType,
371 earliestPutTs, request);
372 } else {
373 try {
374 return user.getUGI().doAs(new PrivilegedExceptionAction<InternalScanner>() {
375 @Override
376 public InternalScanner run() throws Exception {
377 return store.getCoprocessorHost().preCompactScannerOpen(store, scanners,
378 scanType, earliestPutTs, request);
379 }
380 });
381 } catch (InterruptedException ie) {
382 InterruptedIOException iioe = new InterruptedIOException();
383 iioe.initCause(ie);
384 throw iioe;
385 }
386 }
387 }
388
389
390
391
392
393
394
395
396 protected InternalScanner postCreateCoprocScanner(final CompactionRequest request,
397 final ScanType scanType, final InternalScanner scanner, User user) throws IOException {
398 if (store.getCoprocessorHost() == null) {
399 return scanner;
400 }
401 if (user == null) {
402 return store.getCoprocessorHost().preCompact(store, scanner, scanType, request);
403 } else {
404 try {
405 return user.getUGI().doAs(new PrivilegedExceptionAction<InternalScanner>() {
406 @Override
407 public InternalScanner run() throws Exception {
408 return store.getCoprocessorHost().preCompact(store, scanner, scanType, request);
409 }
410 });
411 } catch (InterruptedException ie) {
412 InterruptedIOException iioe = new InterruptedIOException();
413 iioe.initCause(ie);
414 throw iioe;
415 }
416 }
417 }
418
419
420
421
422
423
424
425
426
427
428 protected boolean performCompaction(InternalScanner scanner, CellSink writer,
429 long smallestReadPoint, boolean cleanSeqId,
430 ThroughputController throughputController) throws IOException {
431 long bytesWritten = 0;
432 long bytesWrittenProgress = 0;
433
434
435 List<Cell> cells = new ArrayList<Cell>();
436 long closeCheckInterval = HStore.getCloseCheckInterval();
437 long lastMillis = 0;
438 if (LOG.isDebugEnabled()) {
439 lastMillis = EnvironmentEdgeManager.currentTime();
440 }
441 String compactionName = ThroughputControlUtil.getNameForThrottling(store, "compaction");
442 long now = 0;
443 boolean hasMore;
444 ScannerContext scannerContext =
445 ScannerContext.newBuilder().setBatchLimit(compactionKVMax).build();
446
447 throughputController.start(compactionName);
448 try {
449 do {
450 hasMore = scanner.next(cells, scannerContext);
451 if (LOG.isDebugEnabled()) {
452 now = EnvironmentEdgeManager.currentTime();
453 }
454
455 Cell lastCleanCell = null;
456 long lastCleanCellSeqId = 0;
457 for (Cell c : cells) {
458 if (cleanSeqId && c.getSequenceId() <= smallestReadPoint) {
459 lastCleanCell = c;
460 lastCleanCellSeqId = c.getSequenceId();
461 CellUtil.setSequenceId(c, 0);
462 } else {
463 lastCleanCell = null;
464 lastCleanCellSeqId = 0;
465 }
466 writer.append(c);
467 int len = KeyValueUtil.length(c);
468 ++progress.currentCompactedKVs;
469 progress.totalCompactedSize += len;
470 if (LOG.isDebugEnabled()) {
471 bytesWrittenProgress += len;
472 }
473 throughputController.control(compactionName, len);
474
475 if (closeCheckInterval > 0) {
476 bytesWritten += len;
477 if (bytesWritten > closeCheckInterval) {
478 bytesWritten = 0;
479 if (!store.areWritesEnabled()) {
480 progress.cancel();
481 return false;
482 }
483 }
484 }
485 }
486 if (lastCleanCell != null) {
487
488 CellUtil.setSequenceId(lastCleanCell, lastCleanCellSeqId);
489 }
490
491
492 if (LOG.isDebugEnabled()) {
493 if ((now - lastMillis) >= 60 * 1000) {
494 LOG.debug("Compaction progress: "
495 + compactionName
496 + " "
497 + progress
498 + String.format(", rate=%.2f kB/sec", (bytesWrittenProgress / 1024.0)
499 / ((now - lastMillis) / 1000.0)) + ", throughputController is "
500 + throughputController);
501 lastMillis = now;
502 bytesWrittenProgress = 0;
503 }
504 }
505 cells.clear();
506 } while (hasMore);
507 } catch (InterruptedException e) {
508 progress.cancel();
509 throw new InterruptedIOException("Interrupted while control throughput of compacting "
510 + compactionName);
511 } finally {
512 throughputController.finish(compactionName);
513 }
514 progress.complete();
515 return true;
516 }
517
518
519
520
521
522
523
524
525
526 protected InternalScanner createScanner(Store store, List<StoreFileScanner> scanners,
527 ScanType scanType, long smallestReadPoint, long earliestPutTs) throws IOException {
528 Scan scan = new Scan();
529 scan.setMaxVersions(store.getFamily().getMaxVersions());
530 return new StoreScanner(store, store.getScanInfo(), scan, scanners,
531 scanType, smallestReadPoint, earliestPutTs);
532 }
533
534
535
536
537
538
539
540
541
542
543 protected InternalScanner createScanner(Store store, List<StoreFileScanner> scanners,
544 long smallestReadPoint, long earliestPutTs, byte[] dropDeletesFromRow,
545 byte[] dropDeletesToRow) throws IOException {
546 Scan scan = new Scan();
547 scan.setMaxVersions(store.getFamily().getMaxVersions());
548 return new StoreScanner(store, store.getScanInfo(), scan, scanners, smallestReadPoint,
549 earliestPutTs, dropDeletesFromRow, dropDeletesToRow);
550 }
551 }