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