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 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 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 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 = 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 public String toString() {
417 StringBuilder sb = new StringBuilder(1024);
418 for (int i = 0; i < buckets.length; ++i) {
419 Bucket b = buckets[i];
420 if (i > 0) sb.append(", ");
421 sb.append("bucket.").append(i).append(": size=").append(b.getItemAllocationSize());
422 sb.append(", freeCount=").append(b.freeCount()).append(", used=").append(b.usedCount());
423 }
424 return sb.toString();
425 }
426
427 public long getUsedSize() {
428 return this.usedSize;
429 }
430
431 public long getFreeSize() {
432 return this.totalSize - getUsedSize();
433 }
434
435 public long getTotalSize() {
436 return this.totalSize;
437 }
438
439
440
441
442
443
444
445
446 public synchronized long allocateBlock(int blockSize) throws CacheFullException,
447 BucketAllocatorException {
448 assert blockSize > 0;
449 BucketSizeInfo bsi = roundUpToBucketSizeInfo(blockSize);
450 if (bsi == null) {
451 throw new BucketAllocatorException("Allocation too big size=" + blockSize +
452 "; adjust BucketCache sizes " + CacheConfig.BUCKET_CACHE_BUCKETS_KEY +
453 " to accomodate if size seems reasonable and you want it cached.");
454 }
455 long offset = bsi.allocateBlock();
456
457
458 if (offset < 0)
459 throw new CacheFullException(blockSize, bsi.sizeIndex());
460 usedSize += bucketSizes[bsi.sizeIndex()];
461 return offset;
462 }
463
464 private Bucket grabGlobalCompletelyFreeBucket() {
465 for (BucketSizeInfo bsi : bucketSizeInfos) {
466 Bucket b = bsi.findAndRemoveCompletelyFreeBucket();
467 if (b != null) return b;
468 }
469 return null;
470 }
471
472
473
474
475
476
477 public synchronized int freeBlock(long offset) {
478 int bucketNo = (int) (offset / bucketCapacity);
479 assert bucketNo >= 0 && bucketNo < buckets.length;
480 Bucket targetBucket = buckets[bucketNo];
481 bucketSizeInfos[targetBucket.sizeIndex()].freeBlock(targetBucket, offset);
482 usedSize -= targetBucket.getItemAllocationSize();
483 return targetBucket.getItemAllocationSize();
484 }
485
486 public int sizeIndexOfAllocation(long offset) {
487 int bucketNo = (int) (offset / bucketCapacity);
488 assert bucketNo >= 0 && bucketNo < buckets.length;
489 Bucket targetBucket = buckets[bucketNo];
490 return targetBucket.sizeIndex();
491 }
492
493 public int sizeOfAllocation(long offset) {
494 int bucketNo = (int) (offset / bucketCapacity);
495 assert bucketNo >= 0 && bucketNo < buckets.length;
496 Bucket targetBucket = buckets[bucketNo];
497 return targetBucket.getItemAllocationSize();
498 }
499
500 static class IndexStatistics {
501 private long freeCount, usedCount, itemSize, totalCount;
502
503 public long freeCount() {
504 return freeCount;
505 }
506
507 public long usedCount() {
508 return usedCount;
509 }
510
511 public long totalCount() {
512 return totalCount;
513 }
514
515 public long freeBytes() {
516 return freeCount * itemSize;
517 }
518
519 public long usedBytes() {
520 return usedCount * itemSize;
521 }
522
523 public long totalBytes() {
524 return totalCount * itemSize;
525 }
526
527 public long itemSize() {
528 return itemSize;
529 }
530
531 public IndexStatistics(long free, long used, long itemSize) {
532 setTo(free, used, itemSize);
533 }
534
535 public IndexStatistics() {
536 setTo(-1, -1, 0);
537 }
538
539 public void setTo(long free, long used, long itemSize) {
540 this.itemSize = itemSize;
541 this.freeCount = free;
542 this.usedCount = used;
543 this.totalCount = free + used;
544 }
545 }
546
547 public Bucket [] getBuckets() {
548 return this.buckets;
549 }
550
551 void logStatistics() {
552 IndexStatistics total = new IndexStatistics();
553 IndexStatistics[] stats = getIndexStatistics(total);
554 LOG.info("Bucket allocator statistics follow:\n");
555 LOG.info(" Free bytes=" + total.freeBytes() + "+; used bytes="
556 + total.usedBytes() + "; total bytes=" + total.totalBytes());
557 for (IndexStatistics s : stats) {
558 LOG.info(" Object size " + s.itemSize() + " used=" + s.usedCount()
559 + "; free=" + s.freeCount() + "; total=" + s.totalCount());
560 }
561 }
562
563 IndexStatistics[] getIndexStatistics(IndexStatistics grandTotal) {
564 IndexStatistics[] stats = getIndexStatistics();
565 long totalfree = 0, totalused = 0;
566 for (IndexStatistics stat : stats) {
567 totalfree += stat.freeBytes();
568 totalused += stat.usedBytes();
569 }
570 grandTotal.setTo(totalfree, totalused, 1);
571 return stats;
572 }
573
574 IndexStatistics[] getIndexStatistics() {
575 IndexStatistics[] stats = new IndexStatistics[bucketSizes.length];
576 for (int i = 0; i < stats.length; ++i)
577 stats[i] = bucketSizeInfos[i].statistics();
578 return stats;
579 }
580
581 public long freeBlock(long freeList[]) {
582 long sz = 0;
583 for (int i = 0; i < freeList.length; ++i)
584 sz += freeBlock(freeList[i]);
585 return sz;
586 }
587
588 public int getBucketIndex(long offset) {
589 return (int) (offset / bucketCapacity);
590 }
591
592
593
594
595
596
597
598
599
600
601
602
603 public Set<Integer> getLeastFilledBuckets(Set<Integer> excludedBuckets,
604 int bucketCount) {
605 Queue<Integer> queue = MinMaxPriorityQueue.<Integer>orderedBy(
606 new Comparator<Integer>() {
607 @Override
608 public int compare(Integer left, Integer right) {
609
610 return Float.compare(
611 ((float) buckets[left].usedCount) / buckets[left].itemCount,
612 ((float) buckets[right].usedCount) / buckets[right].itemCount);
613 }
614 }).maximumSize(bucketCount).create();
615
616 for (int i = 0; i < buckets.length; i ++ ) {
617 if (!excludedBuckets.contains(i) && !buckets[i].isUninstantiated() &&
618
619 bucketSizeInfos[buckets[i].sizeIndex()].bucketList.size() != 1) {
620 queue.add(i);
621 }
622 }
623
624 Set<Integer> result = new HashSet<>(bucketCount);
625 result.addAll(queue);
626
627 return result;
628 }
629 }