private void openHeadLogFile() throws ChangelogException { final LogFile<K, V> head = LogFile.newAppendableLogFile(new File(logPath, HEAD_LOG_FILE_NAME), recordParser); final Record<K, V> newestRecord = head.getNewestRecord(); lastAppendedKey = newestRecord != null ? newestRecord.getKey() : null; logFiles.put(recordParser.getMaxKey(), head); }
/** * Add the provided record at the end of this log. * * <p>The record must have a key strictly higher than the key of the last record added. If it is * not the case, the record is not appended and the method returns immediately. * * <p>In order to ensure that record is written out of buffers and persisted to file system, it is * necessary to explicitely call the {@code syncToFileSystem()} method. * * @param record The record to add. * @throws ChangelogException If an error occurs while adding the record to the log. */ public void append(final Record<K, V> record) throws ChangelogException { // If this exclusive lock happens to be a bottleneck : // 1. use a shared lock for appending the record first // 2. switch to an exclusive lock only if rotation is needed // See http://sources.forgerock.org/cru/CR-3548#c27521 for full detail exclusiveLock.lock(); try { if (isClosed) { return; } if (recordIsBreakingKeyOrdering(record)) { logger.info( LocalizableMessage.raw( "Rejecting append to log '%s' for record: [%s], last key appended: [%s]", logPath.getPath(), record, lastAppendedKey != null ? lastAppendedKey : "null")); return; } LogFile<K, V> headLogFile = getHeadLogFile(); if (mustRotate(headLogFile)) { logger.trace( INFO_CHANGELOG_LOG_FILE_ROTATION.get(logPath.getPath(), headLogFile.getSizeInBytes())); rotateHeadLogFile(); headLogFile = getHeadLogFile(); } headLogFile.append(record); lastAppendedKey = record.getKey(); } finally { exclusiveLock.unlock(); } }
/** * Find the highest key that corresponds to a record that is the oldest (or first) of a read-only * log file and where value mapped from the record is lower or equals to provided limit value. * * <p>Example<br> * Given a log with 3 log files, with Record<Int, String> and Mapper<String, Long> mapping a * string to its long value * * <ul> * <li>1_10.log where oldest record is (key=1, value="50") * <li>11_20.log where oldest record is (key=11, value="150") * <li>head.log where oldest record is (key=25, value="250") * </ul> * * Then * * <ul> * <li>findBoundaryKeyFromRecord(mapper, 20) => null * <li>findBoundaryKeyFromRecord(mapper, 50) => 1 * <li>findBoundaryKeyFromRecord(mapper, 100) => 1 * <li>findBoundaryKeyFromRecord(mapper, 150) => 11 * <li>findBoundaryKeyFromRecord(mapper, 200) => 11 * <li>findBoundaryKeyFromRecord(mapper, 250) => 25 * <li>findBoundaryKeyFromRecord(mapper, 300) => 25 * </ul> * * @param <V2> Type of the value extracted from the record * @param mapper The mapper to extract a value from a record. It is expected that extracted values * are ordered according to an order consistent with this log ordering, i.e. for two records, * if key(R1) > key(R2) then extractedValue(R1) > extractedValue(R2). * @param limitValue The limit value to search for * @return the key or {@code null} if no key corresponds * @throws ChangelogException If a problem occurs */ <V2 extends Comparable<V2>> K findBoundaryKeyFromRecord( Record.Mapper<V, V2> mapper, V2 limitValue) throws ChangelogException { sharedLock.lock(); try { K key = null; for (LogFile<K, V> logFile : logFiles.values()) { final Record<K, V> record = logFile.getOldestRecord(); final V2 oldestValue = mapper.map(record.getValue()); if (oldestValue.compareTo(limitValue) > 0) { return key; } key = record.getKey(); } return key; } finally { sharedLock.unlock(); } }
/** Indicates if the provided record has a key that would break the key ordering in the log. */ private boolean recordIsBreakingKeyOrdering(final Record<K, V> record) { return lastAppendedKey != null && record.getKey().compareTo(lastAppendedKey) <= 0; }