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 
8 package com.sleepycat.je.tree;
9 
10 import java.nio.ByteBuffer;
11 
12 import com.sleepycat.je.DatabaseException;
13 import com.sleepycat.je.cleaner.FileSummary;
14 import com.sleepycat.je.cleaner.PackedOffsets;
15 import com.sleepycat.je.cleaner.TrackedFileSummary;
16 import com.sleepycat.je.dbi.DatabaseImpl;
17 import com.sleepycat.je.dbi.MemoryBudget;
18 import com.sleepycat.je.log.LogEntryType;
19 import com.sleepycat.je.log.LogUtils;
20 import com.sleepycat.je.log.Loggable;
21 import com.sleepycat.utilint.StringUtils;
22 
23 /**
24  * A FileSummaryLN represents a Leaf Node in the UtilizationProfile database.
25  *
26  * <p>The contents of the FileSummaryLN are not fixed until the moment at which
27  * the LN is added to the log.  A base summary object contains the summary last
28  * added to the log.  A tracked summary object contains live summary info being
29  * updated in real time.  The tracked summary is added to the base summary just
30  * before logging it, and then the tracked summary is reset.  This ensures that
31  * the logged summary will accurately reflect the totals calculated at the
32  * point in the log where the LN is added.</p>
33  *
34  * <p>This is all done in the writeToLog method, which operates under the log
35  * write latch.  All utilization tracking must be done under the log write
36  * latch.</p>
37  *
38  * <p>In record version 1, obsolete offset tracking was added and multiple
39  * records are stored for a single file rather than a single record.  Each
40  * record contains the offsets that were tracked since the last record was
41  * written.
42  *
43  * <p>The key is 8 bytes: 4 bytes for the file number followed by 4 bytes for
44  * the sequence number.  The lowest valued key for a given file contains the
45  * most recent summary information, while to get a complete list of obsolete
46  * offsets all records for the file must be read.  A range search using just
47  * the first 4 bytes can be used to find the most recent record -- this is
48  * possible because the sequence number values are decreasing over time for a
49  * given file.  Here are example keys for three summary records in file 1:</p>
50  *
51  * <pre>
52  * (file=1, sequence=Integer.MAX_VALUE - 300)
53  * (file=1, sequence=Integer.MAX_VALUE - 200)
54  * (file=1, sequence=Integer.MAX_VALUE - 100)
55  * </pre>
56  *
57  * <p>The sequence number is the number of obsolete entries counted so far,
58  * subtracted from Integer.MAX_VALUE to cause the latest written record to have
59  * the lowest key.</p>
60  *
61  * <h3>Log version information</h3>
62  * <p>Version 0: Keys are old format strings. No obsolete detail is
63  * present.</p>
64  * <p>Version 1: Keys are two 4 byte integers: {file, sequence}.  Obsolete
65  * detail is present.  Some offsets may be invalid if RMW was used.</p>
66  * <p>Version 2: The RMW problem with invalid offsets was corrected.  There is
67  * no data format change; all versions of JE 2.0.x can read version 1.</p>
68  *
69  * @see com.sleepycat.je.cleaner.UtilizationProfile
70  */
71 public final class FileSummaryLN extends LN {
72 
73     private static final String BEGIN_TAG = "<fileSummaryLN>";
74     private static final String END_TAG = "</fileSummaryLN>";
75 
76     private int extraMarshaledMemorySize;
77     private final FileSummary baseSummary;
78     private TrackedFileSummary trackedSummary;
79     private PackedOffsets obsoleteOffsets;
80     private boolean needOffsets;
81     private int entryVersion;
82 
83     /**
84      * Creates a new LN with a given base summary.
85      */
FileSummaryLN(FileSummary baseSummary)86     public FileSummaryLN(FileSummary baseSummary) {
87         super(new byte[0]);
88         assert baseSummary != null;
89         this.baseSummary = baseSummary;
90         obsoleteOffsets = new PackedOffsets();
91         entryVersion = -1;
92     }
93 
94     /**
95      * Creates an empty LN to be filled in from the log.
96      */
FileSummaryLN()97     public FileSummaryLN() {
98         baseSummary = new FileSummary();
99         obsoleteOffsets = new PackedOffsets();
100     }
101 
102     /**
103      * Creates a deleted FileSummaryLN.
104      *
105      * @param deletedMarker makes this constructor signature unique, the value
106      * passed doesn't matter.
107      */
FileSummaryLN(boolean deletedMarker)108     private FileSummaryLN(boolean deletedMarker) {
109         super((byte[]) null);
110         baseSummary = new FileSummary();
111         obsoleteOffsets = new PackedOffsets();
112     }
113 
114     /**
115      * Creates a deleted FileSummaryLN.
116      */
makeDeletedLN()117     public static LN makeDeletedLN() {
118         return new FileSummaryLN(true /*deletedMarker*/);
119     }
120 
121     /**
122      * Sets the live summary object that will be added to the base summary at
123      * the time the LN is logged.
124      */
setTrackedSummary(TrackedFileSummary trackedSummary)125     public void setTrackedSummary(TrackedFileSummary trackedSummary) {
126         this.trackedSummary = trackedSummary;
127         needOffsets = true;
128     }
129 
130     /**
131      * Returns the tracked summary, or null if setTrackedSummary was not
132      * called.
133      */
getTrackedSummary()134     public TrackedFileSummary getTrackedSummary() {
135         return trackedSummary;
136     }
137 
138     /**
139      * Returns the base summary for the file that is stored in the LN.
140      */
getBaseSummary()141     public FileSummary getBaseSummary() {
142         return baseSummary;
143     }
144 
145     /**
146      * Returns the obsolete offsets for the file.
147      */
getObsoleteOffsets()148     public PackedOffsets getObsoleteOffsets() {
149         return obsoleteOffsets;
150     }
151 
152     /**
153      * Returns true if the given key for this LN is a String file number key.
154      * For the old version of the LN there will be a single record per file.
155      *
156      * If this is a version 0 log entry, the key is a string.  However, such an
157      * LN may be migrated by the cleaner, in which case the version will be 1
158      * or greater [#13061].  In the latter case, we can distinguish a string
159      * key by:
160      *
161      * 1) If the key is not 8 bytes long, it has to be a string key.
162      *
163      * 2) If the key is 8 bytes long, but bytes[4] is ascii "0" to "9", then it
164      * must be a string key.  bytes[4] to bytes[7] are a sequence number that
165      * is the number of log entries counted.  For this number to be greater
166      * than 0x30000000, the binary value of 4 digits starting with ascii "0",
167      * over 400 million log entries would have to occur in a single file; this
168      * should never happen.
169      *
170      * Note that having to rely on method (2) is unlikely.  A string key will
171      * only be 8 bytes if the file number reach 8 decimal digits (10,000,000 to
172      * 99,999,999).  This is a very large file number and unlikely to have
173      * occurred using JE 1.7.1 or earlier.
174      *
175      * In summary, the only time the algorithm here could fail is if there were
176      * more than 400 million log entries per file, and more than 10 million
177      * were written with JE 1.7.1 or earlier.
178      */
hasStringKey(byte[] bytes)179     public static boolean hasStringKey(byte[] bytes) {
180 
181         if (bytes.length != 8) {
182             return true;
183         } else {
184            return (bytes[4] >= '0' && bytes[4] <= '9');
185         }
186     }
187 
188     /**
189      * Convert a FileSummaryLN key from a byte array to a long.  The file
190      * number is the first 4 bytes of the key.
191      */
getFileNumber(byte[] bytes)192     public static long getFileNumber(byte[] bytes) {
193 
194         if (hasStringKey(bytes)) {
195             return Long.valueOf(StringUtils.fromUTF8(bytes)).longValue();
196         } else {
197             ByteBuffer buf = ByteBuffer.wrap(bytes);
198             return LogUtils.readIntMSB(buf) & 0xFFFFFFFFL;
199         }
200     }
201 
202     /**
203      * Get the sequence number from the byte array. The sequence number is the
204      * last 4 bytes of the key.
205      */
getSequence(byte[] bytes)206     private long getSequence(byte[] bytes) {
207         if (hasStringKey(bytes)) {
208             return 0;
209         } else {
210             ByteBuffer buf = ByteBuffer.wrap(bytes);
211             LogUtils.readIntMSB(buf);
212             return
213                 (Integer.MAX_VALUE - LogUtils.readIntMSB(buf)) & 0xFFFFFFFFL;
214         }
215     }
216 
217     /**
218      * Returns the first 4 bytes of the key for the given file number.  This
219      * can be used to do a range search to find the first LN for the file.
220      */
makePartialKey(long fileNum)221     public static byte[] makePartialKey(long fileNum) {
222 
223         byte[] bytes = new byte[4];
224         ByteBuffer buf = ByteBuffer.wrap(bytes);
225 
226         LogUtils.writeIntMSB(buf, (int) fileNum);
227 
228         return bytes;
229     }
230 
231     /**
232      * Returns the full two-part key for a given file number and unique
233      * sequence.  This can be used to insert a new LN.
234      *
235      * @param sequence is a unique identifier for the LN for the given file,
236      * and must be greater than the last sequence.
237      */
makeFullKey(long fileNum, int sequence)238     public static byte[] makeFullKey(long fileNum, int sequence) {
239 
240         assert sequence >= 0;
241 
242         byte[] bytes = new byte[8];
243         ByteBuffer buf = ByteBuffer.wrap(bytes);
244 
245         /*
246          * The sequence is subtracted from MAX_VALUE so that increasing values
247          * will be sorted first.  This allows a simple range search to find the
248          * most recent value.
249          */
250         LogUtils.writeIntMSB(buf, (int) fileNum);
251         LogUtils.writeIntMSB(buf, Integer.MAX_VALUE - sequence);
252 
253         return bytes;
254     }
255 
256     /**
257      * Initialize a node that has been faulted in from the log.  If this FSLN
258      * contains version 1 offsets that can be incorrect when RMW was used, and
259      * if je.cleaner.rmwFix is enabled, discard the offsets.  [#13158]
260      */
261     @Override
postFetchInit(DatabaseImpl db, long sourceLsn)262         public void postFetchInit(DatabaseImpl db, long sourceLsn)
263         throws DatabaseException {
264 
265         super.postFetchInit(db, sourceLsn);
266 
267         if (entryVersion == 1 &&
268             db.getEnv().getCleaner().isRMWFixEnabled()) {
269             obsoleteOffsets = new PackedOffsets();
270         }
271     }
272 
273     /*
274      * Dumping
275      */
276 
277     @Override
toString()278     public String toString() {
279         return dumpString(0, true);
280     }
281 
282     @Override
beginTag()283     public String beginTag() {
284         return BEGIN_TAG;
285     }
286 
287     @Override
endTag()288     public String endTag() {
289         return END_TAG;
290     }
291 
292     @Override
dumpString(int nSpaces, boolean dumpTags)293     public String dumpString(int nSpaces, boolean dumpTags) {
294         StringBuilder sb = new StringBuilder();
295         sb.append(super.dumpString(nSpaces, dumpTags));
296         sb.append('\n');
297         if (!isDeleted()) {
298             sb.append(baseSummary.toString());
299             sb.append(obsoleteOffsets.toString());
300         }
301         return sb.toString();
302     }
303 
304     /**
305      * Dump additional fields. Done this way so the additional info can
306      * be within the XML tags defining the dumped log entry.
307      */
308     @Override
dumpLogAdditional(StringBuilder sb, boolean verbose)309     protected void dumpLogAdditional(StringBuilder sb, boolean verbose) {
310         if (!isDeleted()) {
311             baseSummary.dumpLog(sb, true);
312             if (verbose) {
313                 obsoleteOffsets.dumpLog(sb, true);
314             }
315         }
316     }
317 
318     /*
319      * Logging
320      */
321 
322     /**
323      * Return the correct log type for a FileSummaryLN.
324      *
325      * Note: FileSummaryLN will never be transactional.
326      */
327     @Override
getLogType(boolean isInsert, boolean isTransactional)328     protected LogEntryType getLogType(boolean isInsert,
329                                       boolean isTransactional) {
330         assert !isTransactional : "Txnl access to UP db not allowed";
331 
332         return LogEntryType.LOG_FILESUMMARYLN;
333     }
334 
335     /**
336      * This log entry type is configured to perform marshaling (getLogSize and
337      * writeToLog) under the write log mutex.  Otherwise, the size could change
338      * in between calls to these two methods as the result of utilizaton
339      * tracking.
340      *
341      * @see LN#getLogSize
342      */
343     @Override
getLogSize()344     public int getLogSize() {
345         int size = super.getLogSize();
346         if (!isDeleted()) {
347             size += baseSummary.getLogSize();
348             getOffsets();
349             size += obsoleteOffsets.getLogSize();
350         }
351         return size;
352     }
353 
354     /**
355      * @see LN#writeToLog
356      */
357     @Override
writeToLog(ByteBuffer logBuffer)358     public void writeToLog(ByteBuffer logBuffer) {
359 
360         /*
361          * Add the tracked (live) summary to the base summary before writing it
362          * to the log, and reset the tracked summary.  When deleting the LN,
363          * the tracked summary is cleared explicitly and will be null.
364          */
365         if (trackedSummary != null && !isDeleted()) {
366             baseSummary.add(trackedSummary);
367             getOffsets();
368             /* Reset the totals to zero and clear the tracked offsets. */
369             trackedSummary.reset();
370         }
371 
372         super.writeToLog(logBuffer);
373 
374         if (!isDeleted()) {
375             baseSummary.writeToLog(logBuffer);
376             obsoleteOffsets.writeToLog(logBuffer);
377         }
378     }
379 
380     /**
381      * @see LN#readFromLog
382      */
383     @Override
readFromLog(ByteBuffer itemBuffer, int entryVersion)384     public void readFromLog(ByteBuffer itemBuffer, int entryVersion) {
385 
386         this.entryVersion = entryVersion;
387 
388         super.readFromLog(itemBuffer, entryVersion);
389 
390         if (!isDeleted()) {
391             baseSummary.readFromLog(itemBuffer, entryVersion);
392             if (entryVersion > 0) {
393                 obsoleteOffsets.readFromLog(itemBuffer, entryVersion);
394             }
395         }
396     }
397 
398     /**
399      * @see Loggable#logicalEquals
400      * Should never be replicated.
401      */
402     @Override
logicalEquals(Loggable other)403     public boolean logicalEquals(Loggable other) {
404 
405         return false;
406     }
407 
408     /**
409      * If tracked offsets may be present, get them so they are ready to be
410      * written to the log.
411      */
getOffsets()412     private void getOffsets() {
413         assert !isDeleted();
414         if (needOffsets) {
415             long[] offsets = trackedSummary.getObsoleteOffsets();
416             if (offsets != null) {
417                 int oldSize = obsoleteOffsets.getExtraMemorySize();
418                 obsoleteOffsets.pack(offsets);
419                 int newSize = obsoleteOffsets.getExtraMemorySize();
420                 extraMarshaledMemorySize = newSize - oldSize;
421             }
422             needOffsets = false;
423         }
424     }
425 
426     /**
427      * Overrides this method to add space occupied by this object's fields.
428      */
429     @Override
getMemorySizeIncludedByParent()430     public long getMemorySizeIncludedByParent() {
431         return super.getMemorySizeIncludedByParent() +
432                (MemoryBudget.FILESUMMARYLN_OVERHEAD -
433                 MemoryBudget.LN_OVERHEAD) +
434                obsoleteOffsets.getExtraMemorySize();
435     }
436 
437     /**
438      * Clear out the obsoleteOffsets to save memory when the LN is deleted.
439      */
440     @Override
makeDeleted()441     void makeDeleted() {
442         super.makeDeleted();
443         obsoleteOffsets = new PackedOffsets();
444     }
445 
446     /**
447      * Adds the extra memory used by obsoleteOffsets to the parent BIN memory
448      * size. Must be called after LN is inserted into the BIN and logged,
449      * while the cursor is still positioned on the inserted LN.  The BIN must
450      * be latched.  [#17462]
451      *
452      * <p>The obsoleteOffsets memory size is not intially budgeted in the usual
453      * way because PackedOffsets.pack (which changes the memory size) is called
454      * during marshalling (see getOffset).  This amount is not counted in the
455      * parent IN size in the usual way, because LN logging / marshalling occurs
456      * after the LN is inserted in the BIN and its memory size has been counted
457      * (see CursorImpl.putInternal).</p>
458      *
459      * <p>Note that the tree memory usage cannot be updated directly in
460      * getOffsets because the tree memory usage must always be the sum of all
461      * IN sizes, and it is reset to this sum each checkpoint.</p>
462      */
463     @Override
addExtraMarshaledMemorySize(BIN parentBIN)464     public void addExtraMarshaledMemorySize(BIN parentBIN) {
465         if (extraMarshaledMemorySize != 0) {
466             assert trackedSummary != null; /* Must be set during the insert. */
467             assert parentBIN.isLatchExclusiveOwner();
468             parentBIN.updateMemorySize(0, extraMarshaledMemorySize);
469             extraMarshaledMemorySize = 0;
470         }
471     }
472 
473     @Override
dumpKey(StringBuilder sb, byte[] key)474     public void dumpKey(StringBuilder sb, byte[] key) {
475         sb.append("<fileSummaryLNKey fileNumber=\"0x" +
476                   Long.toHexString(getFileNumber(key)) + "\" ");
477         sb.append("sequence=\"0x" +
478                   Long.toHexString(getSequence(key)) + "\"/>");
479         super.dumpKey(sb, key);
480     }
481 }
482