1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.regionserver.compactions;
20
21 import com.google.common.annotations.VisibleForTesting;
22 import com.google.common.collect.Iterators;
23 import com.google.common.collect.Lists;
24 import com.google.common.collect.PeekingIterator;
25 import com.google.common.math.LongMath;
26
27 import java.io.IOException;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.List;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.hadoop.conf.Configuration;
36 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
37 import org.apache.hadoop.hbase.HDFSBlocksDistribution;
38 import org.apache.hadoop.hbase.classification.InterfaceAudience;
39 import org.apache.hadoop.hbase.regionserver.RSRpcServices;
40 import org.apache.hadoop.hbase.regionserver.StoreConfigInformation;
41 import org.apache.hadoop.hbase.regionserver.StoreFile;
42 import org.apache.hadoop.hbase.regionserver.StoreUtils;
43 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
44 import org.apache.hadoop.hbase.util.Pair;
45 import org.apache.hadoop.hbase.util.ReflectionUtils;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
65 public class DateTieredCompactionPolicy extends SortedCompactionPolicy {
66
67 private static final Log LOG = LogFactory.getLog(DateTieredCompactionPolicy.class);
68
69 private final RatioBasedCompactionPolicy compactionPolicyPerWindow;
70
71 private final CompactionWindowFactory windowFactory;
72
73 public DateTieredCompactionPolicy(Configuration conf, StoreConfigInformation storeConfigInfo)
74 throws IOException {
75 super(conf, storeConfigInfo);
76 try {
77 compactionPolicyPerWindow = ReflectionUtils.instantiateWithCustomCtor(
78 comConf.getCompactionPolicyForDateTieredWindow(),
79 new Class[] { Configuration.class, StoreConfigInformation.class },
80 new Object[] { conf, storeConfigInfo });
81 } catch (Exception e) {
82 throw new IOException("Unable to load configured compaction policy '"
83 + comConf.getCompactionPolicyForDateTieredWindow() + "'", e);
84 }
85 try {
86 windowFactory = ReflectionUtils.instantiateWithCustomCtor(
87 comConf.getDateTieredCompactionWindowFactory(),
88 new Class[] { CompactionConfiguration.class }, new Object[] { comConf });
89 } catch (Exception e) {
90 throw new IOException("Unable to load configured window factory '"
91 + comConf.getDateTieredCompactionWindowFactory() + "'", e);
92 }
93 }
94
95
96
97
98 @Override
99 @VisibleForTesting
100 public boolean needsCompaction(final Collection<StoreFile> storeFiles,
101 final List<StoreFile> filesCompacting) {
102 ArrayList<StoreFile> candidates = new ArrayList<StoreFile>(storeFiles);
103 try {
104 return !selectMinorCompaction(candidates, false, true).getFiles().isEmpty();
105 } catch (Exception e) {
106 LOG.error("Can not check for compaction: ", e);
107 return false;
108 }
109 }
110
111 public boolean shouldPerformMajorCompaction(final Collection<StoreFile> filesToCompact)
112 throws IOException {
113 long mcTime = getNextMajorCompactTime(filesToCompact);
114 if (filesToCompact == null || mcTime == 0) {
115 if (LOG.isDebugEnabled()) {
116 LOG.debug("filesToCompact: " + filesToCompact + " mcTime: " + mcTime);
117 }
118 return false;
119 }
120
121
122 long lowTimestamp = StoreUtils.getLowestTimestamp(filesToCompact);
123 long now = EnvironmentEdgeManager.currentTime();
124 if (lowTimestamp <= 0L || lowTimestamp >= (now - mcTime)) {
125 if (LOG.isDebugEnabled()) {
126 LOG.debug("lowTimestamp: " + lowTimestamp + " lowTimestamp: " + lowTimestamp + " now: " +
127 now + " mcTime: " + mcTime);
128 }
129 return false;
130 }
131
132 long cfTTL = this.storeConfigInfo.getStoreFileTtl();
133 HDFSBlocksDistribution hdfsBlocksDistribution = new HDFSBlocksDistribution();
134 List<Long> boundaries = getCompactBoundariesForMajor(filesToCompact, now);
135 boolean[] filesInWindow = new boolean[boundaries.size()];
136
137 for (StoreFile file: filesToCompact) {
138 Long minTimestamp = file.getMinimumTimestamp();
139 long oldest = (minTimestamp == null) ? Long.MIN_VALUE : now - minTimestamp.longValue();
140 if (cfTTL != Long.MAX_VALUE && oldest >= cfTTL) {
141 LOG.debug("Major compaction triggered on store " + this
142 + "; for TTL maintenance");
143 return true;
144 }
145 if (!file.isMajorCompaction() || file.isBulkLoadResult()) {
146 LOG.debug("Major compaction triggered on store " + this
147 + ", because there are new files and time since last major compaction "
148 + (now - lowTimestamp) + "ms");
149 return true;
150 }
151
152 int lowerWindowIndex = Collections.binarySearch(boundaries,
153 minTimestamp == null ? (Long)Long.MAX_VALUE : minTimestamp);
154 int upperWindowIndex = Collections.binarySearch(boundaries,
155 file.getMaximumTimestamp() == null ? (Long)Long.MAX_VALUE : file.getMaximumTimestamp());
156
157 lowerWindowIndex = (lowerWindowIndex < 0) ? Math.abs(lowerWindowIndex + 2) : lowerWindowIndex;
158 upperWindowIndex = (upperWindowIndex < 0) ? Math.abs(upperWindowIndex + 2) : upperWindowIndex;
159 if (lowerWindowIndex != upperWindowIndex) {
160 LOG.debug("Major compaction triggered on store " + this + "; because file "
161 + file.getPath() + " has data with timestamps cross window boundaries");
162 return true;
163 } else if (filesInWindow[upperWindowIndex]) {
164 LOG.debug("Major compaction triggered on store " + this +
165 "; because there are more than one file in some windows");
166 return true;
167 } else {
168 filesInWindow[upperWindowIndex] = true;
169 }
170 hdfsBlocksDistribution.add(file.getHDFSBlockDistribution());
171 }
172
173 float blockLocalityIndex = hdfsBlocksDistribution
174 .getBlockLocalityIndex(RSRpcServices.getHostname(comConf.conf, false));
175 if (blockLocalityIndex < comConf.getMinLocalityToForceCompact()) {
176 LOG.debug("Major compaction triggered on store " + this
177 + "; to make hdfs blocks local, current blockLocalityIndex is "
178 + blockLocalityIndex + " (min " + comConf.getMinLocalityToForceCompact() + ")");
179 return true;
180 }
181
182 LOG.debug("Skipping major compaction of " + this +
183 ", because the files are already major compacted");
184 return false;
185 }
186
187 @Override
188 protected CompactionRequest createCompactionRequest(ArrayList<StoreFile> candidateSelection,
189 boolean tryingMajor, boolean mayUseOffPeak, boolean mayBeStuck) throws IOException {
190 CompactionRequest result = tryingMajor ? selectMajorCompaction(candidateSelection)
191 : selectMinorCompaction(candidateSelection, mayUseOffPeak, mayBeStuck);
192 if (LOG.isDebugEnabled()) {
193 LOG.debug("Generated compaction request: " + result);
194 }
195 return result;
196 }
197
198 public CompactionRequest selectMajorCompaction(ArrayList<StoreFile> candidateSelection) {
199 long now = EnvironmentEdgeManager.currentTime();
200 return new DateTieredCompactionRequest(candidateSelection,
201 this.getCompactBoundariesForMajor(candidateSelection, now));
202 }
203
204
205
206
207
208
209
210
211
212 public CompactionRequest selectMinorCompaction(ArrayList<StoreFile> candidateSelection,
213 boolean mayUseOffPeak, boolean mayBeStuck) throws IOException {
214 long now = EnvironmentEdgeManager.currentTime();
215 long oldestToCompact = getOldestToCompact(comConf.getDateTieredMaxStoreFileAgeMillis(), now);
216
217 List<Pair<StoreFile, Long>> storefileMaxTimestampPairs =
218 Lists.newArrayListWithCapacity(candidateSelection.size());
219 long maxTimestampSeen = Long.MIN_VALUE;
220 for (StoreFile storeFile : candidateSelection) {
221
222
223 maxTimestampSeen = Math.max(maxTimestampSeen,
224 storeFile.getMaximumTimestamp() == null? Long.MIN_VALUE : storeFile.getMaximumTimestamp());
225 storefileMaxTimestampPairs.add(new Pair<StoreFile, Long>(storeFile, maxTimestampSeen));
226 }
227 Collections.reverse(storefileMaxTimestampPairs);
228
229 CompactionWindow window = getIncomingWindow(now);
230 int minThreshold = comConf.getDateTieredIncomingWindowMin();
231 PeekingIterator<Pair<StoreFile, Long>> it =
232 Iterators.peekingIterator(storefileMaxTimestampPairs.iterator());
233 while (it.hasNext()) {
234 if (window.compareToTimestamp(oldestToCompact) < 0) {
235 break;
236 }
237 int compResult = window.compareToTimestamp(it.peek().getSecond());
238 if (compResult > 0) {
239
240 window = window.nextEarlierWindow();
241 minThreshold = comConf.getMinFilesToCompact();
242 } else {
243
244 ArrayList<StoreFile> fileList = Lists.newArrayList();
245
246
247 while (it.hasNext() && window.compareToTimestamp(it.peek().getSecond()) <= 0) {
248 fileList.add(it.next().getFirst());
249 }
250 if (fileList.size() >= minThreshold) {
251 if (LOG.isDebugEnabled()) {
252 LOG.debug("Processing files: " + fileList + " for window: " + window);
253 }
254 DateTieredCompactionRequest request = generateCompactionRequest(fileList, window,
255 mayUseOffPeak, mayBeStuck, minThreshold);
256 if (request != null) {
257 return request;
258 }
259 }
260 }
261 }
262
263 return new CompactionRequest(Collections.<StoreFile> emptyList());
264 }
265
266 private DateTieredCompactionRequest generateCompactionRequest(ArrayList<StoreFile> storeFiles,
267 CompactionWindow window, boolean mayUseOffPeak, boolean mayBeStuck, int minThreshold)
268 throws IOException {
269
270
271 Collections.reverse(storeFiles);
272
273
274 compactionPolicyPerWindow.setMinThreshold(minThreshold);
275 ArrayList<StoreFile> storeFileSelection = mayBeStuck ? storeFiles
276 : compactionPolicyPerWindow.applyCompactionPolicy(storeFiles, mayUseOffPeak, false);
277 if (storeFileSelection != null && !storeFileSelection.isEmpty()) {
278
279
280 boolean singleOutput = storeFiles.size() != storeFileSelection.size() ||
281 comConf.useDateTieredSingleOutputForMinorCompaction();
282 List<Long> boundaries = getCompactionBoundariesForMinor(window, singleOutput);
283 DateTieredCompactionRequest result = new DateTieredCompactionRequest(storeFileSelection,
284 boundaries);
285 return result;
286 }
287 return null;
288 }
289
290
291
292
293
294 private List<Long> getCompactBoundariesForMajor(Collection<StoreFile> filesToCompact, long now) {
295 long minTimestamp = Long.MAX_VALUE;
296 for (StoreFile file : filesToCompact) {
297 minTimestamp =
298 Math.min(minTimestamp,
299 file.getMinimumTimestamp() == null ? Long.MAX_VALUE : file.getMinimumTimestamp());
300 }
301
302 List<Long> boundaries = new ArrayList<Long>();
303
304
305 for (CompactionWindow window = getIncomingWindow(now);
306 window.compareToTimestamp(minTimestamp) > 0;
307 window = window.nextEarlierWindow()) {
308 boundaries.add(window.startMillis());
309 }
310 boundaries.add(Long.MIN_VALUE);
311 Collections.reverse(boundaries);
312 return boundaries;
313 }
314
315
316
317
318 private static List<Long> getCompactionBoundariesForMinor(CompactionWindow window,
319 boolean singleOutput) {
320 List<Long> boundaries = new ArrayList<Long>();
321 boundaries.add(Long.MIN_VALUE);
322 if (!singleOutput) {
323 boundaries.add(window.startMillis());
324 }
325 return boundaries;
326 }
327
328 private CompactionWindow getIncomingWindow(long now) {
329 return windowFactory.newIncomingWindow(now);
330 }
331
332 private static long getOldestToCompact(long maxAgeMillis, long now) {
333 try {
334 return LongMath.checkedSubtract(now, maxAgeMillis);
335 } catch (ArithmeticException ae) {
336 LOG.warn("Value for " + CompactionConfiguration.DATE_TIERED_MAX_AGE_MILLIS_KEY + ": "
337 + maxAgeMillis + ". All the files will be eligible for minor compaction.");
338 return Long.MIN_VALUE;
339 }
340 }
341 }