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, user,
306 smallestReadPoint);
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
359
360 protected InternalScanner preCreateCoprocScanner(final CompactionRequest request,
361 final ScanType scanType, final long earliestPutTs, final List<StoreFileScanner> scanners,
362 User user, final long readPoint) throws IOException {
363 if (store.getCoprocessorHost() == null) {
364 return null;
365 }
366 if (user == null) {
367 return store.getCoprocessorHost().preCompactScannerOpen(store, scanners, scanType,
368 earliestPutTs, request, readPoint);
369 } else {
370 try {
371 return user.getUGI().doAs(new PrivilegedExceptionAction<InternalScanner>() {
372 @Override
373 public InternalScanner run() throws Exception {
374 return store.getCoprocessorHost().preCompactScannerOpen(store, scanners,
375 scanType, earliestPutTs, request, readPoint);
376 }
377 });
378 } catch (InterruptedException ie) {
379 InterruptedIOException iioe = new InterruptedIOException();
380 iioe.initCause(ie);
381 throw iioe;
382 }
383 }
384 }
385
386
387
388
389
390
391
392
393 protected InternalScanner postCreateCoprocScanner(final CompactionRequest request,
394 final ScanType scanType, final InternalScanner scanner, User user) throws IOException {
395 if (store.getCoprocessorHost() == null) {
396 return scanner;
397 }
398 if (user == null) {
399 return store.getCoprocessorHost().preCompact(store, scanner, scanType, request);
400 } else {
401 try {
402 return user.getUGI().doAs(new PrivilegedExceptionAction<InternalScanner>() {
403 @Override
404 public InternalScanner run() throws Exception {
405 return store.getCoprocessorHost().preCompact(store, scanner, scanType, request);
406 }
407 });
408 } catch (InterruptedException ie) {
409 InterruptedIOException iioe = new InterruptedIOException();
410 iioe.initCause(ie);
411 throw iioe;
412 }
413 }
414 }
415
416
417
418
419
420
421
422
423
424
425 protected boolean performCompaction(InternalScanner scanner, CellSink writer,
426 long smallestReadPoint, boolean cleanSeqId,
427 ThroughputController throughputController) throws IOException {
428 long bytesWritten = 0;
429 long bytesWrittenProgress = 0;
430
431
432 List<Cell> cells = new ArrayList<Cell>();
433 long closeCheckInterval = HStore.getCloseCheckInterval();
434 long lastMillis = 0;
435 if (LOG.isDebugEnabled()) {
436 lastMillis = EnvironmentEdgeManager.currentTime();
437 }
438 String compactionName = ThroughputControlUtil.getNameForThrottling(store, "compaction");
439 long now = 0;
440 boolean hasMore;
441 ScannerContext scannerContext =
442 ScannerContext.newBuilder().setBatchLimit(compactionKVMax).build();
443
444 throughputController.start(compactionName);
445 try {
446 do {
447 hasMore = scanner.next(cells, scannerContext);
448 if (LOG.isDebugEnabled()) {
449 now = EnvironmentEdgeManager.currentTime();
450 }
451
452 Cell lastCleanCell = null;
453 long lastCleanCellSeqId = 0;
454 for (Cell c : cells) {
455 if (cleanSeqId && c.getSequenceId() <= smallestReadPoint) {
456 lastCleanCell = c;
457 lastCleanCellSeqId = c.getSequenceId();
458 CellUtil.setSequenceId(c, 0);
459 } else {
460 lastCleanCell = null;
461 lastCleanCellSeqId = 0;
462 }
463 writer.append(c);
464 int len = KeyValueUtil.length(c);
465 ++progress.currentCompactedKVs;
466 progress.totalCompactedSize += len;
467 if (LOG.isDebugEnabled()) {
468 bytesWrittenProgress += len;
469 }
470 throughputController.control(compactionName, len);
471
472 if (closeCheckInterval > 0) {
473 bytesWritten += len;
474 if (bytesWritten > closeCheckInterval) {
475 bytesWritten = 0;
476 if (!store.areWritesEnabled()) {
477 progress.cancel();
478 return false;
479 }
480 }
481 }
482 }
483 if (lastCleanCell != null) {
484
485 CellUtil.setSequenceId(lastCleanCell, lastCleanCellSeqId);
486 }
487
488
489 if (LOG.isDebugEnabled()) {
490 if ((now - lastMillis) >= 60 * 1000) {
491 LOG.debug("Compaction progress: "
492 + compactionName
493 + " "
494 + progress
495 + String.format(", rate=%.2f kB/sec", (bytesWrittenProgress / 1024.0)
496 / ((now - lastMillis) / 1000.0)) + ", throughputController is "
497 + throughputController);
498 lastMillis = now;
499 bytesWrittenProgress = 0;
500 }
501 }
502 cells.clear();
503 } while (hasMore);
504 } catch (InterruptedException e) {
505 progress.cancel();
506 throw new InterruptedIOException("Interrupted while control throughput of compacting "
507 + compactionName);
508 } finally {
509 throughputController.finish(compactionName);
510 }
511 progress.complete();
512 return true;
513 }
514
515
516
517
518
519
520
521
522
523 protected InternalScanner createScanner(Store store, List<StoreFileScanner> scanners,
524 ScanType scanType, long smallestReadPoint, long earliestPutTs) throws IOException {
525 Scan scan = new Scan();
526 scan.setMaxVersions(store.getFamily().getMaxVersions());
527 return new StoreScanner(store, store.getScanInfo(), scan, scanners,
528 scanType, smallestReadPoint, earliestPutTs);
529 }
530
531
532
533
534
535
536
537
538
539
540 protected InternalScanner createScanner(Store store, List<StoreFileScanner> scanners,
541 long smallestReadPoint, long earliestPutTs, byte[] dropDeletesFromRow,
542 byte[] dropDeletesToRow) throws IOException {
543 Scan scan = new Scan();
544 scan.setMaxVersions(store.getFamily().getMaxVersions());
545 return new StoreScanner(store, store.getScanInfo(), scan, scanners, smallestReadPoint,
546 earliestPutTs, dropDeletesFromRow, dropDeletesToRow);
547 }
548 }