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