1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002, 2014 Oracle and/or its affiliates. All rights reserved. 5 * 6 */ 7 package com.sleepycat.je.tree; 8 9 import com.sleepycat.je.dbi.MemoryBudget; 10 import com.sleepycat.je.evictor.Evictor; 11 import com.sleepycat.je.utilint.SizeofMarker; 12 13 /** 14 * The abstract class that defines the various representation used to represent 15 * the keys associated with the IN node. There are currently two supported 16 * representations: 17 * <ol> 18 * <li>A default representation <code>Default</code> that's capable of holding 19 * any set of keys.</li> 20 * <li> 21 * A compact representation <code>MaxKeySize</code> that's more efficient for 22 * holding small keys (LTE 16 bytes) in length. If key prefixing is in use this 23 * represents the unprefixed part of the key, since that's what is stored in 24 * this array.</li> 25 * </ol> 26 * <p> 27 * The choice of representation is made when an IN node is first read in from 28 * the log. The <code>MaxKeySize</code> representation is only used when it is 29 * more storage efficient than the default representation for the set of keys 30 * currently associated with the IN. 31 * <p> 32 * Note that no attempt is currently made to optimize the storage 33 * representation as keys are added to, or removed from, the 34 * <code>Default</code> representation to minimize the chances of transitionary 35 * "back and forth" representation changes that could prove to be expensive. 36 */ 37 public abstract class INKeyRep 38 extends INArrayRep<INKeyRep, INKeyRep.Type, byte[]> { 39 40 /* The different representations for keys. */ 41 public enum Type { DEFAULT, MAX_KEY_SIZE }; 42 INKeyRep()43 public INKeyRep() { 44 } 45 length()46 public abstract int length(); 47 48 /** 49 * Returns true if the key bytes mem usage is accounted for internally 50 * here, or false if each key has a separate byte array and its mem usage 51 * is accounted for by the parent. 52 */ accountsForKeyByteMemUsage()53 public abstract boolean accountsForKeyByteMemUsage(); 54 55 /** 56 * The default representation that's capable of storing keys of any size. 57 */ 58 public static class Default extends INKeyRep { 59 60 private final byte[][] keys; 61 Default(int nodeMaxEntries)62 Default(int nodeMaxEntries) { 63 this.keys = new byte[nodeMaxEntries][]; 64 } 65 Default(@uppressWarningsR) SizeofMarker marker)66 public Default(@SuppressWarnings("unused") SizeofMarker marker) { 67 keys = null; 68 } 69 70 @Override getType()71 public Type getType() { 72 return Type.DEFAULT; 73 } 74 75 @Override length()76 public int length() { 77 return keys.length; 78 } 79 80 @Override get(int idx)81 public byte[] get(int idx) { 82 return keys[idx]; 83 } 84 85 @Override set(int idx, byte[] key, IN parent)86 public INKeyRep set(int idx, byte[] key, IN parent) { 87 keys[idx] = key; 88 return this; 89 } 90 91 @Override copy(int from, int to, int n, IN parent)92 public INKeyRep copy(int from, int to, int n, IN parent) { 93 System.arraycopy(keys, from, keys, to, n); 94 return this; 95 } 96 97 /** 98 * Evolves to the MaxKeySize representation if that is more efficient 99 * for the current set of keys. Note that since all the keys must be 100 * examined to make the decision, there is a reasonable cost to the 101 * method and it should not be invoked indiscriminately. 102 */ 103 @Override compact(IN parent)104 public INKeyRep compact(IN parent) { 105 106 if (keys.length > MaxKeySize.MAX_KEYS) { 107 return this; 108 } 109 110 final int compactMaxKeyLength = parent.getCompactMaxKeyLength(); 111 if (compactMaxKeyLength <= 0) { 112 return this; 113 } 114 115 int keyCount = 0; 116 int maxKeyLength = 0; 117 int defaultKeyBytes = 0; 118 119 for (byte[] key : keys) { 120 if (key != null) { 121 keyCount++; 122 if (key.length > maxKeyLength) { 123 maxKeyLength = key.length; 124 if (maxKeyLength > compactMaxKeyLength) { 125 return this; 126 } 127 } 128 defaultKeyBytes += MemoryBudget.byteArraySize(key.length); 129 } 130 } 131 132 if (keyCount == 0) { 133 return this; 134 } 135 136 long defaultSizeWithKeys = calculateMemorySize() + defaultKeyBytes; 137 138 if (defaultSizeWithKeys > 139 MaxKeySize.calculateMemorySize(keys.length, maxKeyLength)) { 140 return compactToMaxKeySizeRep(maxKeyLength, parent); 141 } 142 143 return this; 144 } 145 compactToMaxKeySizeRep( int maxKeyLength, IN parent)146 private MaxKeySize compactToMaxKeySizeRep( 147 int maxKeyLength, 148 IN parent) { 149 150 MaxKeySize newRep = 151 new MaxKeySize(keys.length, (short) maxKeyLength); 152 153 for (int i = 0; i < keys.length; i++) { 154 INKeyRep rep = newRep.set(i, keys[i], parent); 155 assert rep == newRep; /* Rep remains unchanged. */ 156 } 157 158 noteRepChange(newRep, parent); 159 160 return newRep; 161 } 162 163 @Override calculateMemorySize()164 public long calculateMemorySize() { 165 166 /* 167 * Assume empty keys array. The memory consumed by the actual keys 168 * is accounted for by the IN.getEntryInMemorySize() method. 169 */ 170 return MemoryBudget.DEFAULT_KEYVALS_OVERHEAD + 171 MemoryBudget.objectArraySize(keys.length); 172 } 173 174 @Override accountsForKeyByteMemUsage()175 public boolean accountsForKeyByteMemUsage() { 176 return false; 177 } 178 179 @Override updateCacheStats(@uppressWarningsR) boolean increment, @SuppressWarnings(R) Evictor evictor)180 void updateCacheStats(@SuppressWarnings("unused") boolean increment, 181 @SuppressWarnings("unused") Evictor evictor) { 182 /* No stats for the default representation. */ 183 } 184 } 185 186 /** 187 * The compact representation that can be used to represent keys LTE 16 188 * bytes in length. The keys are all represented inside a single byte array 189 * instead of having one byte array per key. Within the array, all keys are 190 * assigned a storage size equal to that taken up by the longest key, plus 191 * one byte to hold the actual key length. This makes key retreival fast. 192 * However, insertion and deletion for larger keys moves bytes proportional 193 * to the storage length of the keys. This is why the representation is 194 * restricted to keys LTE 16 bytes in size. 195 * 196 * On a 32 bit VM the per key overhead for the Default representation is 4 197 * bytes for the pointer + 16 bytes for each byte array key object, for a 198 * total of 20 bytes/key. On a 64 bit machine the overheads are much 199 * larger: 8 bytes for the pointer plus 24 bytes per key. 200 * 201 * The more fully populated the IN the more the savings with this 202 * representation since the single byte array is sized to hold all the keys 203 * regardless of the actual number of keys that are present. 204 * 205 * It's worth noting that the storage savings here are realized in addition 206 * to the storage benefits of key prefixing, since the keys stored in the 207 * key array are the smaller key values after the prefix has been stripped, 208 * reducing the length of the key and making it more likely that it's small 209 * enough for this specialized representation. 210 */ 211 public static class MaxKeySize extends INKeyRep { 212 213 private static final int LENGTH_BYTES = 1; 214 public static final byte DEFAULT_MAX_KEY_LENGTH = 16; 215 public static final int MAX_KEYS = 256; 216 private static final byte NULL_KEY = Byte.MAX_VALUE; 217 218 /* 219 * The array is sized to hold all the keys associated with the IN node. 220 * Each key is allocated a fixed amount of storage equal to the maximum 221 * length of all the keys in the IN node + 1 byte to hold the size of 222 * each key. The length is biased, by -128. That is, a zero length 223 * key is represented by -128, a 1 byte key by -127, etc. 224 */ 225 private final byte[] keys; 226 227 /* 228 * The number of butes used to store each key == 229 * DEFAULT_MAX_KEY_LENGTH (16) + LENGTH_BYTES (1) 230 */ 231 private final short keyLength; 232 MaxKeySize(int nodeMaxEntries, short maxKeyLen)233 public MaxKeySize(int nodeMaxEntries, short maxKeyLen) { 234 235 assert maxKeyLen < 255; 236 this.keyLength = (short) (maxKeyLen + LENGTH_BYTES); 237 this.keys = new byte[keyLength * nodeMaxEntries]; 238 239 for (int i = 0; i < nodeMaxEntries; i++) { 240 INKeyRep rep = set(i, null, null); 241 assert rep == this; /* Rep remains unchanged. */ 242 } 243 } 244 245 /* Only for use by Sizeof */ 246 public MaxKeySize(@SuppressWarnings("unused") SizeofMarker marker) { 247 keys = null; 248 keyLength = 0; 249 } 250 251 @Override 252 public Type getType() { 253 return Type.MAX_KEY_SIZE; 254 } 255 256 @Override 257 public int length() { 258 return keys.length / keyLength; 259 } 260 261 @Override 262 public byte[] get(int idx) { 263 264 final int k = idx * keyLength; 265 266 if (keys[k] == NULL_KEY) { 267 return null; 268 } 269 270 final int length = keys[k] - Byte.MIN_VALUE; 271 final byte[] key = new byte[length]; 272 273 for (int i = 0; i < length; ++i) { 274 key[i] = keys[k + LENGTH_BYTES + i]; 275 } 276 return key; 277 } 278 279 @Override 280 public INKeyRep set(int idx, byte[] key, IN parent) { 281 282 final int ki = idx * keyLength; 283 284 if (key == null) { 285 keys[ki] = NULL_KEY; 286 return this; 287 } 288 289 if (key.length >= keyLength) { 290 Default newRep = expandToDefaultRep(parent); 291 return newRep.set(idx, key, parent); 292 } 293 294 keys[ki] = (byte) (key.length + Byte.MIN_VALUE); 295 296 for (int i = 0; i < key.length; ++i) { 297 keys[ki + LENGTH_BYTES + i] = key[i]; 298 } 299 return this; 300 } 301 302 private Default expandToDefaultRep(IN parent) { 303 304 final int capacity = length(); 305 final Default newRep = new Default(capacity); 306 307 for (int i = 0; i < capacity; i++) { 308 final byte[] k = get(i); 309 INKeyRep rep = newRep.set(i, k, parent); 310 assert rep == newRep; /* Rep remains unchanged. */ 311 } 312 313 noteRepChange(newRep, parent); 314 return newRep; 315 } 316 317 @Override 318 public INKeyRep copy(int from, int to, int n, IN parent) { 319 System.arraycopy(keys, (from * keyLength), 320 keys, (to * keyLength), 321 n * keyLength); 322 return this; 323 } 324 325 @Override 326 public INKeyRep compact(@SuppressWarnings("unused") IN parent) { 327 /* It's as compact as it gets. */ 328 return this; 329 } 330 331 @Override 332 public long calculateMemorySize() { 333 return MemoryBudget.MAX_KEY_SIZE_KEYVALS_OVERHEAD + 334 MemoryBudget.byteArraySize(keys.length); 335 } 336 337 private static long calculateMemorySize(int maxKeys, int maxKeySize) { 338 return MemoryBudget.MAX_KEY_SIZE_KEYVALS_OVERHEAD + 339 MemoryBudget.byteArraySize(maxKeys * 340 (maxKeySize + LENGTH_BYTES)); 341 } 342 343 @Override 344 public boolean accountsForKeyByteMemUsage() { 345 return true; 346 } 347 348 @Override 349 void updateCacheStats(boolean increment, Evictor evictor) { 350 if (increment) { 351 evictor.getNINCompactKey().incrementAndGet(); 352 } else { 353 evictor.getNINCompactKey().decrementAndGet(); 354 } 355 } 356 } 357 } 358