From faacd72a21f362a574bbd8c58c6e21173afa65f7 Mon Sep 17 00:00:00 2001 From: Michael Stack Date: Fri, 3 Jul 2009 19:44:06 +0000 Subject: [PATCH] HBASE-1607 Redo MemStore heap sizing to be accurate, testable, and more like new LruBlockCache git-svn-id: https://svn.apache.org/repos/asf/hadoop/hbase/trunk@790999 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + .../apache/hadoop/hbase/RegionHistorian.java | 7 +- .../hadoop/hbase/regionserver/HLog.java | 8 +- .../hadoop/hbase/regionserver/HRegion.java | 65 ++++----- .../hadoop/hbase/regionserver/MemStore.java | 71 ++++++--- .../hadoop/hbase/regionserver/Store.java | 21 ++- .../apache/hadoop/hbase/util/ClassSize.java | 35 ++++- .../apache/hadoop/hbase/io/TestHeapSize.java | 138 ++++++++++++++++-- 8 files changed, 267 insertions(+), 80 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e350bd639ad..1365e08d9dc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -435,6 +435,8 @@ Release 0.20.0 - Unreleased Watcher HBASE-1597 Prevent unnecessary caching of blocks during compactions (Jon Gray via Stack) + HBASE-1607 Redo MemStore heap sizing to be accurate, testable, and more + like new LruBlockCache (Jon Gray via Stack) OPTIMIZATIONS HBASE-1412 Change values for delete column and column family in KeyValue diff --git a/src/java/org/apache/hadoop/hbase/RegionHistorian.java b/src/java/org/apache/hadoop/hbase/RegionHistorian.java index e22a5ec96e6..b6062daf03e 100644 --- a/src/java/org/apache/hadoop/hbase/RegionHistorian.java +++ b/src/java/org/apache/hadoop/hbase/RegionHistorian.java @@ -33,8 +33,8 @@ import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; -import org.apache.hadoop.hbase.io.Cell; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.ClassSize; /** * The Region Historian task is to keep track of every modification a region @@ -49,8 +49,6 @@ public class RegionHistorian implements HConstants { private HTable metaTable; - - /** Singleton reference */ private static RegionHistorian historian; @@ -333,4 +331,7 @@ public class RegionHistorian implements HConstants { LOG.debug("Offlined"); } } + + public static final long FIXED_OVERHEAD = ClassSize.align( + ClassSize.OBJECT + ClassSize.REFERENCE); } \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/regionserver/HLog.java b/src/java/org/apache/hadoop/hbase/regionserver/HLog.java index b216242fc2d..b037ba5fb10 100644 --- a/src/java/org/apache/hadoop/hbase/regionserver/HLog.java +++ b/src/java/org/apache/hadoop/hbase/regionserver/HLog.java @@ -55,6 +55,7 @@ import org.apache.hadoop.hbase.HServerInfo; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.RemoteExceptionHandler; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.ClassSize; import org.apache.hadoop.hbase.util.FSUtils; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.io.SequenceFile; @@ -111,7 +112,7 @@ public class HLog implements HConstants, Syncable { private final int flushlogentries; private final AtomicInteger unflushedEntries = new AtomicInteger(0); private volatile long lastLogFlushTime; - + /* * Current log file. */ @@ -1117,4 +1118,9 @@ public class HLog implements HConstants, Syncable { } } } + + public static final long FIXED_OVERHEAD = ClassSize.align( + ClassSize.OBJECT + (5 * ClassSize.REFERENCE) + + ClassSize.ATOMIC_INTEGER + Bytes.SIZEOF_INT + (3 * Bytes.SIZEOF_LONG)); + } diff --git a/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java b/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java index 3b8d033206d..38f66460d92 100644 --- a/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java +++ b/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java @@ -54,9 +54,11 @@ import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.io.HeapSize; import org.apache.hadoop.hbase.io.Reference.Range; import org.apache.hadoop.hbase.ipc.HRegionInterface; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.ClassSize; import org.apache.hadoop.hbase.util.FSUtils; import org.apache.hadoop.hbase.util.Writables; import org.apache.hadoop.util.Progressable; @@ -98,7 +100,7 @@ import org.apache.hadoop.util.StringUtils; * regionName is a unique identifier for this HRegion. (startKey, endKey] * defines the keyspace for this HRegion. */ -public class HRegion implements HConstants { // , Writable{ +public class HRegion implements HConstants, HeapSize { // , Writable{ static final Log LOG = LogFactory.getLog(HRegion.class); static final String SPLITDIR = "splits"; static final String MERGEDIR = "merges"; @@ -2322,45 +2324,28 @@ public class HRegion implements HConstants { // , Writable{ + " in table " + regionInfo.getTableDesc()); } } + + public static final long FIXED_OVERHEAD = ClassSize.align( + (3 * Bytes.SIZEOF_LONG) + (2 * Bytes.SIZEOF_INT) + Bytes.SIZEOF_BOOLEAN + + (21 * ClassSize.REFERENCE) + ClassSize.OBJECT); + public static final long DEEP_OVERHEAD = ClassSize.align(FIXED_OVERHEAD + + ClassSize.OBJECT + (2 * ClassSize.ATOMIC_BOOLEAN) + + ClassSize.ATOMIC_LONG + ClassSize.ATOMIC_INTEGER + + ClassSize.CONCURRENT_HASHMAP + + (16 * ClassSize.CONCURRENT_HASHMAP_ENTRY) + + (16 * ClassSize.CONCURRENT_HASHMAP_SEGMENT) + + ClassSize.CONCURRENT_SKIPLISTMAP + ClassSize.CONCURRENT_SKIPLISTMAP_ENTRY + + RegionHistorian.FIXED_OVERHEAD + HLog.FIXED_OVERHEAD + + ClassSize.align(ClassSize.OBJECT + (5 * Bytes.SIZEOF_BOOLEAN)) + + (3 * ClassSize.REENTRANT_LOCK)); -// //HBaseAdmin Debugging -// /** -// * @return number of stores in the region -// */ -// public int getNumStores() { -// return this.numStores; -// } -// /** -// * @return the name of the region -// */ -// public byte [] getRegionsName() { -// return this.name; -// } -// /** -// * @return the number of files in every store -// */ -// public int [] getStoresSize() { -// return this.storeSize; -// } -// -// //Writable, used for debugging purposes only -// public void readFields(final DataInput in) -// throws IOException { -// this.name = Bytes.readByteArray(in); -// this.numStores = in.readInt(); -// this.storeSize = new int [numStores]; -// for(int i=0; iLocking and transactions are handled at a higher level. This API should * not be called directly but by an HRegion manager. */ -public class Store implements HConstants { +public class Store implements HConstants, HeapSize { static final Log LOG = LogFactory.getLog(Store.class); /** * Comparator that looks at columns and compares their family portions. @@ -510,7 +512,7 @@ public class Store implements HConstants { if (!isExpired(kv, oldestTimestamp)) { writer.append(kv); entries++; - flushed += this.memstore.heapSize(kv, true); + flushed += this.memstore.heapSizeChange(kv, true); } } // B. Write out the log sequence number that corresponds to this output @@ -1627,4 +1629,19 @@ public class Store implements HConstants { Bytes.toBytes(newValue)); return new ICVResult(newValue, newKv.heapSize(), newKv); } + + public static final long FIXED_OVERHEAD = ClassSize.align( + ClassSize.OBJECT + (17 * ClassSize.REFERENCE) + + (5 * Bytes.SIZEOF_LONG) + (3 * Bytes.SIZEOF_INT) + Bytes.SIZEOF_BOOLEAN + + ClassSize.align(ClassSize.ARRAY)); + + public static final long DEEP_OVERHEAD = ClassSize.align(FIXED_OVERHEAD + + ClassSize.OBJECT + ClassSize.REENTRANT_LOCK + + ClassSize.CONCURRENT_SKIPLISTMAP + + ClassSize.CONCURRENT_SKIPLISTMAP_ENTRY + ClassSize.OBJECT); + + @Override + public long heapSize() { + return DEEP_OVERHEAD + this.memstore.heapSize(); + } } \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/util/ClassSize.java b/src/java/org/apache/hadoop/hbase/util/ClassSize.java index 7dc726fcaf9..a8cf26d78a7 100755 --- a/src/java/org/apache/hadoop/hbase/util/ClassSize.java +++ b/src/java/org/apache/hadoop/hbase/util/ClassSize.java @@ -73,7 +73,25 @@ public class ClassSize { /** Overhead for ConcurrentHashMap.Segment */ public static int CONCURRENT_HASHMAP_SEGMENT = 0; - + + /** Overhead for ConcurrentSkipListMap */ + public static int CONCURRENT_SKIPLISTMAP = 0; + + /** Overhead for ConcurrentSkipListMap Entry */ + public static int CONCURRENT_SKIPLISTMAP_ENTRY = 0; + + /** Overhead for ReentrantReadWriteLock */ + public static int REENTRANT_LOCK = 0; + + /** Overhead for AtomicLong */ + public static int ATOMIC_LONG = 0; + + /** Overhead for AtomicInteger */ + public static int ATOMIC_INTEGER = 0; + + /** Overhead for AtomicBoolean */ + public static int ATOMIC_BOOLEAN = 0; + private static final String THIRTY_TWO = "32"; /** @@ -118,6 +136,21 @@ public class ClassSize { CONCURRENT_HASHMAP_SEGMENT = align(REFERENCE + OBJECT + (3 * Bytes.SIZEOF_INT) + Bytes.SIZEOF_FLOAT + ARRAY); + + CONCURRENT_SKIPLISTMAP = align(Bytes.SIZEOF_INT + OBJECT + (8 * REFERENCE)); + + CONCURRENT_SKIPLISTMAP_ENTRY = align( + align(OBJECT + (3 * REFERENCE)) + /* one node per entry */ + align((OBJECT + (3 * REFERENCE))/2)); /* one index per two entries */ + + REENTRANT_LOCK = align(OBJECT + (3 * REFERENCE)); + + ATOMIC_LONG = align(OBJECT + Bytes.SIZEOF_LONG); + + ATOMIC_INTEGER = align(OBJECT + Bytes.SIZEOF_INT); + + ATOMIC_BOOLEAN = align(OBJECT + Bytes.SIZEOF_BOOLEAN); + } /** diff --git a/src/test/org/apache/hadoop/hbase/io/TestHeapSize.java b/src/test/org/apache/hadoop/hbase/io/TestHeapSize.java index b04d18b006c..39d3d6336c0 100644 --- a/src/test/org/apache/hadoop/hbase/io/TestHeapSize.java +++ b/src/test/org/apache/hadoop/hbase/io/TestHeapSize.java @@ -5,13 +5,23 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.RegionHistorian; +import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.io.hfile.CachedBlock; import org.apache.hadoop.hbase.io.hfile.LruBlockCache; +import org.apache.hadoop.hbase.regionserver.HRegion; +import org.apache.hadoop.hbase.regionserver.MemStore; +import org.apache.hadoop.hbase.regionserver.Store; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.ClassSize; @@ -99,6 +109,60 @@ public class TestHeapSize extends TestCase { ClassSize.estimateBase(cl, true); assertEquals(expected, actual); } + + // ConcurrentHashMap + cl = ConcurrentHashMap.class; + expected = ClassSize.estimateBase(cl, false); + actual = ClassSize.CONCURRENT_HASHMAP; + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } + + // ConcurrentSkipListMap + cl = ConcurrentSkipListMap.class; + expected = ClassSize.estimateBase(cl, false); + actual = ClassSize.CONCURRENT_SKIPLISTMAP; + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } + + // ReentrantReadWriteLock + cl = ReentrantReadWriteLock.class; + expected = ClassSize.estimateBase(cl, false); + actual = ClassSize.REENTRANT_LOCK; + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } + + // AtomicLong + cl = AtomicLong.class; + expected = ClassSize.estimateBase(cl, false); + actual = ClassSize.ATOMIC_LONG; + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } + + // AtomicInteger + cl = AtomicInteger.class; + expected = ClassSize.estimateBase(cl, false); + actual = ClassSize.ATOMIC_INTEGER; + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } + + // AtomicBoolean + cl = AtomicBoolean.class; + expected = ClassSize.estimateBase(cl, false); + actual = ClassSize.ATOMIC_BOOLEAN; + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } } @@ -124,18 +188,21 @@ public class TestHeapSize extends TestCase { assertEquals(expected, actual); } - //LruBlockCache Overhead - cl = LruBlockCache.class; - actual = LruBlockCache.CACHE_FIXED_OVERHEAD; + //Put + cl = Put.class; expected = ClassSize.estimateBase(cl, false); + //The actual TreeMap is not included in the above calculation + expected += ClassSize.TREEMAP; + Put put = new Put(Bytes.toBytes("")); + actual = put.heapSize(); if(expected != actual) { ClassSize.estimateBase(cl, true); assertEquals(expected, actual); } - // LruBlockCache Map Fixed Overhead - cl = ConcurrentHashMap.class; - actual = ClassSize.CONCURRENT_HASHMAP; + //LruBlockCache Overhead + cl = LruBlockCache.class; + actual = LruBlockCache.CACHE_FIXED_OVERHEAD; expected = ClassSize.estimateBase(cl, false); if(expected != actual) { ClassSize.estimateBase(cl, true); @@ -157,17 +224,64 @@ public class TestHeapSize extends TestCase { assertEquals(expected, actual); } - //Put - cl = Put.class; + // MemStore Overhead + cl = MemStore.class; + actual = MemStore.FIXED_OVERHEAD; expected = ClassSize.estimateBase(cl, false); - //The actual TreeMap is not included in the above calculation - expected += ClassSize.TREEMAP; - Put put = new Put(Bytes.toBytes("")); - actual = put.heapSize(); if(expected != actual) { ClassSize.estimateBase(cl, true); assertEquals(expected, actual); } + + // MemStore Deep Overhead + actual = MemStore.DEEP_OVERHEAD; + expected = ClassSize.estimateBase(cl, false); + expected += ClassSize.estimateBase(ReentrantReadWriteLock.class, false); + expected += ClassSize.estimateBase(AtomicLong.class, false); + expected += ClassSize.estimateBase(ConcurrentSkipListMap.class, false); + expected += ClassSize.estimateBase(ConcurrentSkipListMap.class, false); + if(expected != actual) { + ClassSize.estimateBase(cl, true); + ClassSize.estimateBase(ReentrantReadWriteLock.class, true); + ClassSize.estimateBase(AtomicLong.class, true); + ClassSize.estimateBase(ConcurrentSkipListMap.class, true); + assertEquals(expected, actual); + } + + // Store Overhead + cl = Store.class; + actual = Store.FIXED_OVERHEAD; + expected = ClassSize.estimateBase(cl, false); + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } + + // Region Overhead + cl = HRegion.class; + actual = HRegion.FIXED_OVERHEAD; + expected = ClassSize.estimateBase(cl, false); + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } + + // RegionHistorian Overhead + cl = RegionHistorian.class; + actual = RegionHistorian.FIXED_OVERHEAD; + expected = ClassSize.estimateBase(cl, false); + if(expected != actual) { + ClassSize.estimateBase(cl, true); + assertEquals(expected, actual); + } + + // Currently NOT testing Deep Overheads of many of these classes. + // Deep overheads cover a vast majority of stuff, but will not be 100% + // accurate because it's unclear when we're referencing stuff that's already + // accounted for. But we have satisfied our two core requirements. + // Sizing is quite accurate now, and our tests will throw errors if + // any of these classes are modified without updating overhead sizes. + } }