1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.hadoop.hbase.io.hfile.bucket;
22
23 import java.util.Arrays;
24 import java.util.Comparator;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Queue;
29 import java.util.Set;
30 import java.util.concurrent.atomic.AtomicLong;
31
32 import com.google.common.collect.MinMaxPriorityQueue;
33 import org.apache.commons.collections.map.LinkedMap;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 import org.apache.hadoop.hbase.classification.InterfaceAudience;
37 import org.apache.hadoop.hbase.io.hfile.BlockCacheKey;
38 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
39 import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.BucketEntry;
40 import org.codehaus.jackson.annotate.JsonIgnoreProperties;
41
42 import com.google.common.base.Objects;
43 import com.google.common.base.Preconditions;
44 import com.google.common.primitives.Ints;
45
46
47
48
49
50
51
52
53
54 @InterfaceAudience.Private
55 @JsonIgnoreProperties({"indexStatistics", "freeSize", "usedSize"})
56 public final class BucketAllocator {
57 private static final Log LOG = LogFactory.getLog(BucketAllocator.class);
58
59 @JsonIgnoreProperties({"completelyFree", "uninstantiated"})
60 public final static class Bucket {
61 private long baseOffset;
62 private int itemAllocationSize, sizeIndex;
63 private int itemCount;
64 private int freeList[];
65 private int freeCount, usedCount;
66
67 public Bucket(long offset) {
68 baseOffset = offset;
69 sizeIndex = -1;
70 }
71
72 void reconfigure(int sizeIndex, int[] bucketSizes, long bucketCapacity) {
73 Preconditions.checkElementIndex(sizeIndex, bucketSizes.length);
74 this.sizeIndex = sizeIndex;
75 itemAllocationSize = bucketSizes[sizeIndex];
76 itemCount = (int) (bucketCapacity / (long) itemAllocationSize);
77 freeCount = itemCount;
78 usedCount = 0;
79 freeList = new int[itemCount];
80 for (int i = 0; i < freeCount; ++i)
81 freeList[i] = i;
82 }
83
84 public boolean isUninstantiated() {
85 return sizeIndex == -1;
86 }
87
88 public int sizeIndex() {
89 return sizeIndex;
90 }
91
92 public int getItemAllocationSize() {
93 return itemAllocationSize;
94 }
95
96 public boolean hasFreeSpace() {
97 return freeCount > 0;
98 }
99
100 public boolean isCompletelyFree() {
101 return usedCount == 0;
102 }
103
104 public int freeCount() {
105 return freeCount;
106 }
107
108 public int usedCount() {
109 return usedCount;
110 }
111
112 public int getFreeBytes() {
113 return freeCount * itemAllocationSize;
114 }
115
116 public int getUsedBytes() {
117 return usedCount * itemAllocationSize;
118 }
119
120 public long getBaseOffset() {
121 return baseOffset;
122 }
123
124
125
126
127
128
129 public long allocate() {
130 assert freeCount > 0;
131 assert sizeIndex != -1;
132 ++usedCount;
133 long offset = baseOffset + (freeList[--freeCount] * itemAllocationSize);
134 assert offset >= 0;
135 return offset;
136 }
137
138 public void addAllocation(long offset) throws BucketAllocatorException {
139 offset -= baseOffset;
140 if (offset < 0 || offset % itemAllocationSize != 0)
141 throw new BucketAllocatorException(
142 "Attempt to add allocation for bad offset: " + offset + " base="
143 + baseOffset + ", bucket size=" + itemAllocationSize);
144 int idx = (int) (offset / itemAllocationSize);
145 boolean matchFound = false;
146 for (int i = 0; i < freeCount; ++i) {
147 if (matchFound) freeList[i - 1] = freeList[i];
148 else if (freeList[i] == idx) matchFound = true;
149 }
150 if (!matchFound)
151 throw new BucketAllocatorException("Couldn't find match for index "
152 + idx + " in free list");
153 ++usedCount;
154 --freeCount;
155 }
156
157 private void free(long offset) {
158 offset -= baseOffset;
159 assert offset >= 0;
160 assert offset < itemCount * itemAllocationSize;
161 assert offset % itemAllocationSize == 0;
162 assert usedCount > 0;
163 assert freeCount < itemCount;
164 int item = (int) (offset / (long) itemAllocationSize);
165 assert !freeListContains(item);
166 --usedCount;
167 freeList[freeCount++] = item;
168 }
169
170 private boolean freeListContains(int blockNo) {
171 for (int i = 0; i < freeCount; ++i) {
172 if (freeList[i] == blockNo) return true;
173 }
174 return false;
175 }
176 }
177
178 final class BucketSizeInfo {
179
180
181 private LinkedMap bucketList, freeBuckets, completelyFreeBuckets;
182 private int sizeIndex;
183
184 BucketSizeInfo(int sizeIndex) {
185 bucketList = new LinkedMap();
186 freeBuckets = new LinkedMap();
187 completelyFreeBuckets = new LinkedMap();
188 this.sizeIndex = sizeIndex;
189 }
190
191 public synchronized void instantiateBucket(Bucket b) {
192 assert b.isUninstantiated() || b.isCompletelyFree();
193 b.reconfigure(sizeIndex, bucketSizes, bucketCapacity);
194 bucketList.put(b, b);
195 freeBuckets.put(b, b);
196 completelyFreeBuckets.put(b, b);
197 }
198
199 public int sizeIndex() {
200 return sizeIndex;
201 }
202
203
204
205
206
207 public long allocateBlock() {
208 Bucket b = null;
209 if (freeBuckets.size() > 0) {
210
211 b = (Bucket) freeBuckets.lastKey();
212 }
213 if (b == null) {
214 b = grabGlobalCompletelyFreeBucket();
215 if (b != null) instantiateBucket(b);
216 }
217 if (b == null) return -1;
218 long result = b.allocate();
219 blockAllocated(b);
220 return result;
221 }
222
223 void blockAllocated(Bucket b) {
224 if (!b.isCompletelyFree()) completelyFreeBuckets.remove(b);
225 if (!b.hasFreeSpace()) freeBuckets.remove(b);
226 }
227
228 public Bucket findAndRemoveCompletelyFreeBucket() {
229 Bucket b = null;
230 assert bucketList.size() > 0;
231 if (bucketList.size() == 1) {
232
233 return null;
234 }
235
236 if (completelyFreeBuckets.size() > 0) {
237 b = (Bucket) completelyFreeBuckets.firstKey();
238 removeBucket(b);
239 }
240 return b;
241 }
242
243 private synchronized void removeBucket(Bucket b) {
244 assert b.isCompletelyFree();
245 bucketList.remove(b);
246 freeBuckets.remove(b);
247 completelyFreeBuckets.remove(b);
248 }
249
250 public void freeBlock(Bucket b, long offset) {
251 assert bucketList.containsKey(b);
252
253 assert (!completelyFreeBuckets.containsKey(b));
254 b.free(offset);
255 if (!freeBuckets.containsKey(b)) freeBuckets.put(b, b);
256 if (b.isCompletelyFree()) completelyFreeBuckets.put(b, b);
257 }
258
259 public synchronized IndexStatistics statistics() {
260 long free = 0, used = 0;
261 for (Object obj : bucketList.keySet()) {
262 Bucket b = (Bucket) obj;
263 free += b.freeCount();
264 used += b.usedCount();
265 }
266 return new IndexStatistics(free, used, bucketSizes[sizeIndex]);
267 }
268
269 @Override
270 public String toString() {
271 return Objects.toStringHelper(this.getClass())
272 .add("sizeIndex", sizeIndex)
273 .add("bucketSize", bucketSizes[sizeIndex])
274 .toString();
275 }
276 }
277
278
279
280
281
282 private static final int DEFAULT_BUCKET_SIZES[] = { 4 * 1024 + 1024, 8 * 1024 + 1024,
283 16 * 1024 + 1024, 32 * 1024 + 1024, 40 * 1024 + 1024, 48 * 1024 + 1024,
284 56 * 1024 + 1024, 64 * 1024 + 1024, 96 * 1024 + 1024, 128 * 1024 + 1024,
285 192 * 1024 + 1024, 256 * 1024 + 1024, 384 * 1024 + 1024,
286 512 * 1024 + 1024 };
287
288
289
290
291
292 public BucketSizeInfo roundUpToBucketSizeInfo(int blockSize) {
293 for (int i = 0; i < bucketSizes.length; ++i)
294 if (blockSize <= bucketSizes[i])
295 return bucketSizeInfos[i];
296 return null;
297 }
298
299
300
301
302 static public final int FEWEST_ITEMS_IN_BUCKET = 4;
303
304 private final int[] bucketSizes;
305 private final int bigItemSize;
306
307 private final long bucketCapacity;
308 private Bucket[] buckets;
309 private BucketSizeInfo[] bucketSizeInfos;
310 private final long totalSize;
311 private long usedSize = 0;
312
313 BucketAllocator(long availableSpace, int[] bucketSizes)
314 throws BucketAllocatorException {
315 this.bucketSizes = bucketSizes == null ? DEFAULT_BUCKET_SIZES : bucketSizes;
316 Arrays.sort(this.bucketSizes);
317 this.bigItemSize = Ints.max(this.bucketSizes);
318 this.bucketCapacity = (long) FEWEST_ITEMS_IN_BUCKET * bigItemSize;
319 buckets = new Bucket[(int) (availableSpace / bucketCapacity)];
320 if (buckets.length < this.bucketSizes.length)
321 throw new BucketAllocatorException("Bucket allocator size too small (" + buckets.length +
322 "); must have room for at least " + this.bucketSizes.length + " buckets");
323 bucketSizeInfos = new BucketSizeInfo[this.bucketSizes.length];
324 for (int i = 0; i < this.bucketSizes.length; ++i) {
325 bucketSizeInfos[i] = new BucketSizeInfo(i);
326 }
327 for (int i = 0; i < buckets.length; ++i) {
328 buckets[i] = new Bucket(bucketCapacity * i);
329 bucketSizeInfos[i < this.bucketSizes.length ? i : this.bucketSizes.length - 1]
330 .instantiateBucket(buckets[i]);
331 }
332 this.totalSize = ((long) buckets.length) * bucketCapacity;
333 if (LOG.isInfoEnabled()) {
334 LOG.info("Cache totalSize=" + this.totalSize + ", buckets=" + this.buckets.length +
335 ", bucket capacity=" + this.bucketCapacity +
336 "=(" + FEWEST_ITEMS_IN_BUCKET + "*" + this.bigItemSize + ")=" +
337 "(FEWEST_ITEMS_IN_BUCKET*(largest configured bucketcache size))");
338 }
339 }
340
341
342
343
344
345
346
347
348
349 BucketAllocator(long availableSpace, int[] bucketSizes, Map<BlockCacheKey, BucketEntry> map,
350 AtomicLong realCacheSize) throws BucketAllocatorException {
351 this(availableSpace, bucketSizes);
352
353
354
355
356
357 boolean[] reconfigured = new boolean[buckets.length];
358 int sizeNotMatchedCount = 0;
359 int insufficientCapacityCount = 0;
360 Iterator<Map.Entry<BlockCacheKey, BucketEntry>> iterator = map.entrySet().iterator();
361 while (iterator.hasNext()) {
362 Map.Entry<BlockCacheKey, BucketEntry> entry = iterator.next();
363 long foundOffset = entry.getValue().offset();
364 int foundLen = entry.getValue().getLength();
365 int bucketSizeIndex = -1;
366 for (int i = 0; i < this.bucketSizes.length; ++i) {
367 if (foundLen <= this.bucketSizes[i]) {
368 bucketSizeIndex = i;
369 break;
370 }
371 }
372 if (bucketSizeIndex == -1) {
373 sizeNotMatchedCount++;
374 iterator.remove();
375 continue;
376 }
377 int bucketNo = (int) (foundOffset / bucketCapacity);
378 if (bucketNo < 0 || bucketNo >= buckets.length) {
379 insufficientCapacityCount++;
380 iterator.remove();
381 continue;
382 }
383 Bucket b = buckets[bucketNo];
384 if (reconfigured[bucketNo]) {
385 if (b.sizeIndex() != bucketSizeIndex)
386 throw new BucketAllocatorException(
387 "Inconsistent allocation in bucket map;");
388 } else {
389 if (!b.isCompletelyFree())
390 throw new BucketAllocatorException("Reconfiguring bucket "
391 + bucketNo + " but it's already allocated; corrupt data");
392
393
394 BucketSizeInfo bsi = bucketSizeInfos[bucketSizeIndex];
395 BucketSizeInfo oldbsi = bucketSizeInfos[b.sizeIndex()];
396 oldbsi.removeBucket(b);
397 bsi.instantiateBucket(b);
398 reconfigured[bucketNo] = true;
399 }
400 realCacheSize.addAndGet(foundLen);
401 buckets[bucketNo].addAllocation(foundOffset);
402 usedSize += buckets[bucketNo].getItemAllocationSize();
403 bucketSizeInfos[bucketSizeIndex].blockAllocated(b);
404 }
405
406 if (sizeNotMatchedCount > 0) {
407 LOG.warn("There are " + sizeNotMatchedCount + " blocks which can't be rebuilt because "
408 + "there is no matching bucket size for these blocks");
409 }
410 if (insufficientCapacityCount > 0) {
411 LOG.warn("There are " + insufficientCapacityCount + " blocks which can't be rebuilt - "
412 + "did you shrink the cache?");
413 }
414 }
415
416 @Override
417 public String toString() {
418 StringBuilder sb = new StringBuilder(1024);
419 for (int i = 0; i < buckets.length; ++i) {
420 Bucket b = buckets[i];
421 if (i > 0) sb.append(", ");
422 sb.append("bucket.").append(i).append(": size=").append(b.getItemAllocationSize());
423 sb.append(", freeCount=").append(b.freeCount()).append(", used=").append(b.usedCount());
424 }
425 return sb.toString();
426 }
427
428 public long getUsedSize() {
429 return this.usedSize;
430 }
431
432 public long getFreeSize() {
433 return this.totalSize - getUsedSize();
434 }
435
436 public long getTotalSize() {
437 return this.totalSize;
438 }
439
440
441
442
443
444
445
446
447 public synchronized long allocateBlock(int blockSize) throws CacheFullException,
448 BucketAllocatorException {
449 assert blockSize > 0;
450 BucketSizeInfo bsi = roundUpToBucketSizeInfo(blockSize);
451 if (bsi == null) {
452 throw new BucketAllocatorException("Allocation too big size=" + blockSize +
453 "; adjust BucketCache sizes " + CacheConfig.BUCKET_CACHE_BUCKETS_KEY +
454 " to accomodate if size seems reasonable and you want it cached.");
455 }
456 long offset = bsi.allocateBlock();
457
458
459 if (offset < 0)
460 throw new CacheFullException(blockSize, bsi.sizeIndex());
461 usedSize += bucketSizes[bsi.sizeIndex()];
462 return offset;
463 }
464
465 private Bucket grabGlobalCompletelyFreeBucket() {
466 for (BucketSizeInfo bsi : bucketSizeInfos) {
467 Bucket b = bsi.findAndRemoveCompletelyFreeBucket();
468 if (b != null) return b;
469 }
470 return null;
471 }
472
473
474
475
476
477
478 public synchronized int freeBlock(long offset) {
479 int bucketNo = (int) (offset / bucketCapacity);
480 assert bucketNo >= 0 && bucketNo < buckets.length;
481 Bucket targetBucket = buckets[bucketNo];
482 bucketSizeInfos[targetBucket.sizeIndex()].freeBlock(targetBucket, offset);
483 usedSize -= targetBucket.getItemAllocationSize();
484 return targetBucket.getItemAllocationSize();
485 }
486
487 public int sizeIndexOfAllocation(long offset) {
488 int bucketNo = (int) (offset / bucketCapacity);
489 assert bucketNo >= 0 && bucketNo < buckets.length;
490 Bucket targetBucket = buckets[bucketNo];
491 return targetBucket.sizeIndex();
492 }
493
494 public int sizeOfAllocation(long offset) {
495 int bucketNo = (int) (offset / bucketCapacity);
496 assert bucketNo >= 0 && bucketNo < buckets.length;
497 Bucket targetBucket = buckets[bucketNo];
498 return targetBucket.getItemAllocationSize();
499 }
500
501 static class IndexStatistics {
502 private long freeCount, usedCount, itemSize, totalCount;
503
504 public long freeCount() {
505 return freeCount;
506 }
507
508 public long usedCount() {
509 return usedCount;
510 }
511
512 public long totalCount() {
513 return totalCount;
514 }
515
516 public long freeBytes() {
517 return freeCount * itemSize;
518 }
519
520 public long usedBytes() {
521 return usedCount * itemSize;
522 }
523
524 public long totalBytes() {
525 return totalCount * itemSize;
526 }
527
528 public long itemSize() {
529 return itemSize;
530 }
531
532 public IndexStatistics(long free, long used, long itemSize) {
533 setTo(free, used, itemSize);
534 }
535
536 public IndexStatistics() {
537 setTo(-1, -1, 0);
538 }
539
540 public void setTo(long free, long used, long itemSize) {
541 this.itemSize = itemSize;
542 this.freeCount = free;
543 this.usedCount = used;
544 this.totalCount = free + used;
545 }
546 }
547
548 public Bucket [] getBuckets() {
549 return this.buckets;
550 }
551
552 void logStatistics() {
553 IndexStatistics total = new IndexStatistics();
554 IndexStatistics[] stats = getIndexStatistics(total);
555 LOG.info("Bucket allocator statistics follow:\n");
556 LOG.info(" Free bytes=" + total.freeBytes() + "+; used bytes="
557 + total.usedBytes() + "; total bytes=" + total.totalBytes());
558 for (IndexStatistics s : stats) {
559 LOG.info(" Object size " + s.itemSize() + " used=" + s.usedCount()
560 + "; free=" + s.freeCount() + "; total=" + s.totalCount());
561 }
562 }
563
564 IndexStatistics[] getIndexStatistics(IndexStatistics grandTotal) {
565 IndexStatistics[] stats = getIndexStatistics();
566 long totalfree = 0, totalused = 0;
567 for (IndexStatistics stat : stats) {
568 totalfree += stat.freeBytes();
569 totalused += stat.usedBytes();
570 }
571 grandTotal.setTo(totalfree, totalused, 1);
572 return stats;
573 }
574
575 IndexStatistics[] getIndexStatistics() {
576 IndexStatistics[] stats = new IndexStatistics[bucketSizes.length];
577 for (int i = 0; i < stats.length; ++i)
578 stats[i] = bucketSizeInfos[i].statistics();
579 return stats;
580 }
581
582 public long freeBlock(long freeList[]) {
583 long sz = 0;
584 for (int i = 0; i < freeList.length; ++i)
585 sz += freeBlock(freeList[i]);
586 return sz;
587 }
588
589 public int getBucketIndex(long offset) {
590 return (int) (offset / bucketCapacity);
591 }
592
593
594
595
596
597
598
599
600
601
602
603
604 public Set<Integer> getLeastFilledBuckets(Set<Integer> excludedBuckets,
605 int bucketCount) {
606 Queue<Integer> queue = MinMaxPriorityQueue.<Integer>orderedBy(
607 new Comparator<Integer>() {
608 @Override
609 public int compare(Integer left, Integer right) {
610
611 return Float.compare(
612 ((float) buckets[left].usedCount) / buckets[left].itemCount,
613 ((float) buckets[right].usedCount) / buckets[right].itemCount);
614 }
615 }).maximumSize(bucketCount).create();
616
617 for (int i = 0; i < buckets.length; i ++ ) {
618 if (!excludedBuckets.contains(i) && !buckets[i].isUninstantiated() &&
619
620 bucketSizeInfos[buckets[i].sizeIndex()].bucketList.size() != 1) {
621 queue.add(i);
622 }
623 }
624
625 Set<Integer> result = new HashSet<>(bucketCount);
626 result.addAll(queue);
627
628 return result;
629 }
630 }