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, user,
307 smallestReadPoint);
308 if (scanner == null) {
309 scanner = scannerFactory.createScanner(scanners, scanType, fd, smallestReadPoint);
310 }
311 scanner = postCreateCoprocScanner(request, scanType, scanner, user);
312 if (scanner == null) {
313
314 return new ArrayList<Path>();
315 }
316 boolean cleanSeqId = false;
317 if (fd.minSeqIdToKeep > 0) {
318 smallestReadPoint = Math.min(fd.minSeqIdToKeep, smallestReadPoint);
319 cleanSeqId = true;
320 }
321 writer = sinkFactory.createWriter(scanner, fd, dropCache);
322 finished =
323 performCompaction(scanner, writer, smallestReadPoint, cleanSeqId, throughputController);
324 if (!finished) {
325 throw new InterruptedIOException("Aborting compaction of store " + store + " in region "
326 + store.getRegionInfo().getRegionNameAsString() + " because it was interrupted.");
327 }
328 } finally {
329 Closeables.close(scanner, true);
330 for (StoreFile f : readersToClose) {
331 try {
332 f.closeReader(true);
333 } catch (IOException e) {
334 LOG.warn("Exception closing " + f, e);
335 }
336 }
337 if (!finished && writer != null) {
338 abortWriter(writer);
339 }
340 }
341 assert finished : "We should have exited the method on all error paths";
342 assert writer != null : "Writer should be non-null if no error";
343 return commitWriter(writer, fd, request);
344 }
345
346 protected abstract List<Path> commitWriter(T writer, FileDetails fd, CompactionRequest request)
347 throws IOException;
348
349 protected abstract void abortWriter(T writer) throws IOException;
350
351
352
353
354
355
356
357
358
359
360
361 protected InternalScanner preCreateCoprocScanner(final CompactionRequest request,
362 final ScanType scanType, final long earliestPutTs, final List<StoreFileScanner> scanners,
363 User user, final long readPoint) throws IOException {
364 if (store.getCoprocessorHost() == null) {
365 return null;
366 }
367 if (user == null) {
368 return store.getCoprocessorHost().preCompactScannerOpen(store, scanners, scanType,
369 earliestPutTs, request, readPoint);
370 } else {
371 try {
372 return user.getUGI().doAs(new PrivilegedExceptionAction<InternalScanner>() {
373 @Override
374 public InternalScanner run() throws Exception {
375 return store.getCoprocessorHost().preCompactScannerOpen(store, scanners,
376 scanType, earliestPutTs, request, readPoint);
377 }
378 });
379 } catch (InterruptedException ie) {
380 InterruptedIOException iioe = new InterruptedIOException();
381 iioe.initCause(ie);
382 throw iioe;
383 }
384 }
385 }
386
387
388
389
390
391
392
393
394 protected InternalScanner postCreateCoprocScanner(final CompactionRequest request,
395 final ScanType scanType, final InternalScanner scanner, User user) throws IOException {
396 if (store.getCoprocessorHost() == null) {
397 return scanner;
398 }
399 if (user == null) {
400 return store.getCoprocessorHost().preCompact(store, scanner, scanType, request);
401 } else {
402 try {
403 return user.getUGI().doAs(new PrivilegedExceptionAction<InternalScanner>() {
404 @Override
405 public InternalScanner run() throws Exception {
406 return store.getCoprocessorHost().preCompact(store, scanner, scanType, request);
407 }
408 });
409 } catch (InterruptedException ie) {
410 InterruptedIOException iioe = new InterruptedIOException();
411 iioe.initCause(ie);
412 throw iioe;
413 }
414 }
415 }
416
417
418
419
420
421
422
423
424
425
426 protected boolean performCompaction(InternalScanner scanner, CellSink writer,
427 long smallestReadPoint, boolean cleanSeqId,
428 ThroughputController throughputController) throws IOException {
429 long bytesWritten = 0;
430 long bytesWrittenProgress = 0;
431
432
433 List<Cell> cells = new ArrayList<Cell>();
434 long closeCheckInterval = HStore.getCloseCheckInterval();
435 long lastMillis = 0;
436 if (LOG.isDebugEnabled()) {
437 lastMillis = EnvironmentEdgeManager.currentTime();
438 }
439 String compactionName = ThroughputControlUtil.getNameForThrottling(store, "compaction");
440 long now = 0;
441 boolean hasMore;
442 ScannerContext scannerContext =
443 ScannerContext.newBuilder().setBatchLimit(compactionKVMax).build();
444
445 throughputController.start(compactionName);
446 try {
447 do {
448 hasMore = scanner.next(cells, scannerContext);
449 if (LOG.isDebugEnabled()) {
450 now = EnvironmentEdgeManager.currentTime();
451 }
452
453 Cell lastCleanCell = null;
454 long lastCleanCellSeqId = 0;
455 for (Cell c : cells) {
456 if (cleanSeqId && c.getSequenceId() <= smallestReadPoint) {
457 lastCleanCell = c;
458 lastCleanCellSeqId = c.getSequenceId();
459 CellUtil.setSequenceId(c, 0);
460 } else {
461 lastCleanCell = null;
462 lastCleanCellSeqId = 0;
463 }
464 writer.append(c);
465 int len = KeyValueUtil.length(c);
466 ++progress.currentCompactedKVs;
467 progress.totalCompactedSize += len;
468 if (LOG.isDebugEnabled()) {
469 bytesWrittenProgress += len;
470 }
471 throughputController.control(compactionName, len);
472
473 if (closeCheckInterval > 0) {
474 bytesWritten += len;
475 if (bytesWritten > closeCheckInterval) {
476 bytesWritten = 0;
477 if (!store.areWritesEnabled()) {
478 progress.cancel();
479 return false;
480 }
481 }
482 }
483 }
484 if (lastCleanCell != null) {
485
486 CellUtil.setSequenceId(lastCleanCell, lastCleanCellSeqId);
487 }
488
489
490 if (LOG.isDebugEnabled()) {
491 if ((now - lastMillis) >= 60 * 1000) {
492 LOG.debug("Compaction progress: "
493 + compactionName
494 + " "
495 + progress
496 + String.format(", rate=%.2f kB/sec", (bytesWrittenProgress / 1024.0)
497 / ((now - lastMillis) / 1000.0)) + ", throughputController is "
498 + throughputController);
499 lastMillis = now;
500 bytesWrittenProgress = 0;
501 }
502 }
503 cells.clear();
504 } while (hasMore);
505 } catch (InterruptedException e) {
506 progress.cancel();
507 throw new InterruptedIOException("Interrupted while control throughput of compacting "
508 + compactionName);
509 } finally {
510 throughputController.finish(compactionName);
511 }
512 progress.complete();
513 return true;
514 }
515
516
517
518
519
520
521
522
523
524 protected InternalScanner createScanner(Store store, List<StoreFileScanner> scanners,
525 ScanType scanType, long smallestReadPoint, long earliestPutTs) throws IOException {
526 Scan scan = new Scan();
527 scan.setMaxVersions(store.getFamily().getMaxVersions());
528 return new StoreScanner(store, store.getScanInfo(), scan, scanners,
529 scanType, smallestReadPoint, earliestPutTs);
530 }
531
532
533
534
535
536
537
538
539
540
541 protected InternalScanner createScanner(Store store, List<StoreFileScanner> scanners,
542 long smallestReadPoint, long earliestPutTs, byte[] dropDeletesFromRow,
543 byte[] dropDeletesToRow) throws IOException {
544 Scan scan = new Scan();
545 scan.setMaxVersions(store.getFamily().getMaxVersions());
546 return new StoreScanner(store, store.getScanInfo(), scan, scanners, smallestReadPoint,
547 earliestPutTs, dropDeletesFromRow, dropDeletesToRow);
548 }
549 }