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 ", seqNum=" + seqNum +
203 (allFiles ? ", earliestPutTs=" + earliestPutTs: ""));
204 }
205 }
206 return fd;
207 }
208
209
210
211
212
213
214 protected List<StoreFileScanner> createFileScanners(
215 final Collection<StoreFile> filesToCompact,
216 long smallestReadPoint,
217 boolean useDropBehind) throws IOException {
218 return StoreFileScanner.getScannersForStoreFiles(filesToCompact,
219
220
221
222
223 smallestReadPoint);
224 }
225
226 protected long getSmallestReadPoint() {
227 return store.getSmallestReadPoint();
228 }
229
230 protected interface InternalScannerFactory {
231
232 ScanType getScanType(CompactionRequest request);
233
234 InternalScanner createScanner(List<StoreFileScanner> scanners, ScanType scanType,
235 FileDetails fd, long smallestReadPoint) throws IOException;
236 }
237
238 protected final InternalScannerFactory defaultScannerFactory = new InternalScannerFactory() {
239
240 @Override
241 public ScanType getScanType(CompactionRequest request) {
242 return request.isAllFiles() ? ScanType.COMPACT_DROP_DELETES
243 : ScanType.COMPACT_RETAIN_DELETES;
244 }
245
246 @Override
247 public InternalScanner createScanner(List<StoreFileScanner> scanners, ScanType scanType,
248 FileDetails fd, long smallestReadPoint) throws IOException {
249 return Compactor.this.createScanner(store, scanners, scanType, smallestReadPoint,
250 fd.earliestPutTs);
251 }
252 };
253
254
255
256
257
258
259
260 protected Writer createTmpWriter(FileDetails fd, boolean shouldDropBehind) throws IOException {
261
262
263 return store.createWriterInTmp(fd.maxKeyCount, this.compactionCompression,
264
265
266
267 }
268
269 protected List<Path> compact(final CompactionRequest request,
270 InternalScannerFactory scannerFactory, CellSinkFactory<T> sinkFactory,
271 ThroughputController throughputController, User user) throws IOException {
272 FileDetails fd = getFileDetails(request.getFiles(), request.isAllFiles());
273 this.progress = new CompactionProgress(fd.maxKeyCount);
274
275
276 long smallestReadPoint = getSmallestReadPoint();
277
278 List<StoreFileScanner> scanners;
279 Collection<StoreFile> readersToClose;
280 T writer = null;
281 boolean dropCache;
282 if (request.isMajor() || request.isAllFiles()) {
283 dropCache = this.dropCacheMajor;
284 } else {
285 dropCache = this.dropCacheMinor;
286 }
287
288 if (this.conf.getBoolean("hbase.regionserver.compaction.private.readers", true)) {
289
290
291 readersToClose = new ArrayList<StoreFile>(request.getFiles().size());
292 for (StoreFile f : request.getFiles()) {
293 readersToClose.add(f.cloneForReader());
294 }
295 scanners = createFileScanners(readersToClose, smallestReadPoint, dropCache);
296 } else {
297 readersToClose = Collections.emptyList();
298 scanners = createFileScanners(request.getFiles(), smallestReadPoint, dropCache);
299 }
300 InternalScanner scanner = null;
301 boolean finished = false;
302 try {
303
304 ScanType scanType = scannerFactory.getScanType(request);
305 scanner = preCreateCoprocScanner(request, scanType, fd.earliestPutTs, scanners);
306 if (scanner == null) {
307 scanner = scannerFactory.createScanner(scanners, scanType, fd, smallestReadPoint);
308 }
309 scanner = postCreateCoprocScanner(request, scanType, scanner, user);
310 if (scanner == null) {
311
312 return new ArrayList<Path>();
313 }
314 boolean cleanSeqId = false;
315 if (fd.minSeqIdToKeep > 0) {
316 smallestReadPoint = Math.min(fd.minSeqIdToKeep, smallestReadPoint);
317 cleanSeqId = true;
318 }
319 writer = sinkFactory.createWriter(scanner, fd, dropCache);
320 finished =
321 performCompaction(scanner, writer, smallestReadPoint, cleanSeqId, throughputController);
322 if (!finished) {
323 throw new InterruptedIOException("Aborting compaction of store " + store + " in region "
324 + store.getRegionInfo().getRegionNameAsString() + " because it was interrupted.");
325 }
326 } finally {
327 Closeables.close(scanner, true);
328 for (StoreFile f : readersToClose) {
329 try {
330 f.closeReader(true);
331 } catch (IOException e) {
332 LOG.warn("Exception closing " + f, e);
333 }
334 }
335 if (!finished && writer != null) {
336 abortWriter(writer);
337 }
338 }
339 assert finished : "We should have exited the method on all error paths";
340 assert writer != null : "Writer should be non-null if no error";
341 return commitWriter(writer, fd, request);
342 }
343
344 protected abstract List<Path> commitWriter(T writer, FileDetails fd, CompactionRequest request)
345 throws IOException;
346
347 protected abstract void abortWriter(T writer) throws IOException;
348
349
350
351
352
353
354
355
356
357 protected InternalScanner preCreateCoprocScanner(final CompactionRequest request,
358 ScanType scanType, long earliestPutTs, List<StoreFileScanner> scanners) throws IOException {
359 return preCreateCoprocScanner(request, scanType, earliestPutTs, scanners, null);
360 }
361
362 protected InternalScanner preCreateCoprocScanner(final CompactionRequest request,
363 final ScanType scanType, final long earliestPutTs, final List<StoreFileScanner> scanners,
364 User user) throws IOException {
365 if (store.getCoprocessorHost() == null) {
366 return null;
367 }
368 if (user == null) {
369 return store.getCoprocessorHost().preCompactScannerOpen(store, scanners, scanType,
370 earliestPutTs, request);
371 } else {
372 try {
373 return user.getUGI().doAs(new PrivilegedExceptionAction<InternalScanner>() {
374 @Override
375 public InternalScanner run() throws Exception {
376 return store.getCoprocessorHost().preCompactScannerOpen(store, scanners,
377 scanType, earliestPutTs, request);
378 }
379 });
380 } catch (InterruptedException ie) {
381 InterruptedIOException iioe = new InterruptedIOException();
382 iioe.initCause(ie);
383 throw iioe;
384 }
385 }
386 }
387
388
389
390
391
392
393
394
395 protected InternalScanner postCreateCoprocScanner(final CompactionRequest request,
396 final ScanType scanType, final InternalScanner scanner, User user) throws IOException {
397 if (store.getCoprocessorHost() == null) {
398 return scanner;
399 }
400 if (user == null) {
401 return store.getCoprocessorHost().preCompact(store, scanner, scanType, request);
402 } else {
403 try {
404 return user.getUGI().doAs(new PrivilegedExceptionAction<InternalScanner>() {
405 @Override
406 public InternalScanner run() throws Exception {
407 return store.getCoprocessorHost().preCompact(store, scanner, scanType, request);
408 }
409 });
410 } catch (InterruptedException ie) {
411 InterruptedIOException iioe = new InterruptedIOException();
412 iioe.initCause(ie);
413 throw iioe;
414 }
415 }
416 }
417
418
419
420
421
422
423
424
425
426
427 protected boolean performCompaction(InternalScanner scanner, CellSink writer,
428 long smallestReadPoint, boolean cleanSeqId,
429 ThroughputController throughputController) throws IOException {
430 long bytesWritten = 0;
431 long bytesWrittenProgress = 0;
432
433
434 List<Cell> cells = new ArrayList<Cell>();
435 long closeCheckInterval = HStore.getCloseCheckInterval();
436 long lastMillis = 0;
437 if (LOG.isDebugEnabled()) {
438 lastMillis = EnvironmentEdgeManager.currentTime();
439 }
440 String compactionName = ThroughputControlUtil.getNameForThrottling(store, "compaction");
441 long now = 0;
442 boolean hasMore;
443 ScannerContext scannerContext =
444 ScannerContext.newBuilder().setBatchLimit(compactionKVMax).build();
445
446 throughputController.start(compactionName);
447 try {
448 do {
449 hasMore = scanner.next(cells, scannerContext);
450 if (LOG.isDebugEnabled()) {
451 now = EnvironmentEdgeManager.currentTime();
452 }
453
454 Cell lastCleanCell = null;
455 long lastCleanCellSeqId = 0;
456 for (Cell c : cells) {
457 if (cleanSeqId && c.getSequenceId() <= smallestReadPoint) {
458 lastCleanCell = c;
459 lastCleanCellSeqId = c.getSequenceId();
460 CellUtil.setSequenceId(c, 0);
461 } else {
462 lastCleanCell = null;
463 lastCleanCellSeqId = 0;
464 }
465 writer.append(c);
466 int len = KeyValueUtil.length(c);
467 ++progress.currentCompactedKVs;
468 progress.totalCompactedSize += len;
469 if (LOG.isDebugEnabled()) {
470 bytesWrittenProgress += len;
471 }
472 throughputController.control(compactionName, len);
473
474 if (closeCheckInterval > 0) {
475 bytesWritten += len;
476 if (bytesWritten > closeCheckInterval) {
477 bytesWritten = 0;
478 if (!store.areWritesEnabled()) {
479 progress.cancel();
480 return false;
481 }
482 }
483 }
484 }
485 if (lastCleanCell != null) {
486
487 CellUtil.setSequenceId(lastCleanCell, lastCleanCellSeqId);
488 }
489
490
491 if (LOG.isDebugEnabled()) {
492 if ((now - lastMillis) >= 60 * 1000) {
493 LOG.debug("Compaction progress: "
494 + compactionName
495 + " "
496 + progress
497 + String.format(", rate=%.2f kB/sec", (bytesWrittenProgress / 1024.0)
498 / ((now - lastMillis) / 1000.0)) + ", throughputController is "
499 + throughputController);
500 lastMillis = now;
501 bytesWrittenProgress = 0;
502 }
503 }
504 cells.clear();
505 } while (hasMore);
506 } catch (InterruptedException e) {
507 progress.cancel();
508 throw new InterruptedIOException("Interrupted while control throughput of compacting "
509 + compactionName);
510 } finally {
511 throughputController.finish(compactionName);
512 }
513 progress.complete();
514 return true;
515 }
516
517
518
519
520
521
522
523
524
525 protected InternalScanner createScanner(Store store, List<StoreFileScanner> scanners,
526 ScanType scanType, long smallestReadPoint, long earliestPutTs) throws IOException {
527 Scan scan = new Scan();
528 scan.setMaxVersions(store.getFamily().getMaxVersions());
529 return new StoreScanner(store, store.getScanInfo(), scan, scanners,
530 scanType, smallestReadPoint, earliestPutTs);
531 }
532
533
534
535
536
537
538
539
540
541
542 protected InternalScanner createScanner(Store store, List<StoreFileScanner> scanners,
543 long smallestReadPoint, long earliestPutTs, byte[] dropDeletesFromRow,
544 byte[] dropDeletesToRow) throws IOException {
545 Scan scan = new Scan();
546 scan.setMaxVersions(store.getFamily().getMaxVersions());
547 return new StoreScanner(store, store.getScanInfo(), scan, scanners, smallestReadPoint,
548 earliestPutTs, dropDeletesFromRow, dropDeletesToRow);
549 }
550 }