@Override public void completeCacheFlush(byte[] encodedRegionName) { this.sequenceIdAccounting.completeCacheFlush(encodedRegionName); }
@Test public void testFindLower() { SequenceIdAccounting sida = new SequenceIdAccounting(); sida.getOrCreateLowestSequenceIds(ENCODED_REGION_NAME); Map<byte[], Long> m = new HashMap<>(); m.put(ENCODED_REGION_NAME, HConstants.NO_SEQNUM); long sequenceid = 1; sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid, true); sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid++, true); sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid++, true); assertTrue(sida.findLower(m) == null); m.put(ENCODED_REGION_NAME, sida.getLowestSequenceId(ENCODED_REGION_NAME)); assertTrue(sida.findLower(m).length == 1); m.put(ENCODED_REGION_NAME, sida.getLowestSequenceId(ENCODED_REGION_NAME) - 1); assertTrue(sida.findLower(m) == null); } }
@Override public Long startCacheFlush(byte[] encodedRegionName, Set<byte[]> families) { return this.sequenceIdAccounting.startCacheFlush(encodedRegionName, families); }
@Test public void testAreAllLower() { SequenceIdAccounting sida = new SequenceIdAccounting(); sida.getOrCreateLowestSequenceIds(ENCODED_REGION_NAME); Map<byte[], Long> m = new HashMap<>(); m.put(ENCODED_REGION_NAME, HConstants.NO_SEQNUM); assertTrue(sida.areAllLower(m, null)); long sequenceid = 1; sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid, true); sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid++, true); sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid++, true); assertTrue(sida.areAllLower(m, null)); m.put(ENCODED_REGION_NAME, sequenceid); assertFalse(sida.areAllLower(m, null)); ArrayList<byte[]> regions = new ArrayList<>(); assertFalse(sida.areAllLower(m, regions)); assertEquals(1, regions.size()); assertArrayEquals(ENCODED_REGION_NAME, regions.get(0)); long lowest = sida.getLowestSequenceId(ENCODED_REGION_NAME); assertEquals("Lowest should be first sequence id inserted", 1, lowest); m.put(ENCODED_REGION_NAME, lowest); assertFalse(sida.areAllLower(m, null)); sida.startCacheFlush(ENCODED_REGION_NAME, FAMILIES); assertFalse(sida.areAllLower(m, null)); m.put(ENCODED_REGION_NAME, HConstants.NO_SEQNUM); assertTrue(sida.areAllLower(m, null)); sida.completeCacheFlush(ENCODED_REGION_NAME); m.put(ENCODED_REGION_NAME, sequenceid);
@Test public void testStartCacheFlush() { SequenceIdAccounting sida = new SequenceIdAccounting(); sida.getOrCreateLowestSequenceIds(ENCODED_REGION_NAME); Map<byte[], Long> m = new HashMap<>(); m.put(ENCODED_REGION_NAME, HConstants.NO_SEQNUM); assertEquals(HConstants.NO_SEQNUM, (long)sida.startCacheFlush(ENCODED_REGION_NAME, FAMILIES)); sida.completeCacheFlush(ENCODED_REGION_NAME); long sequenceid = 1; sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid, true); // Only one family so should return NO_SEQNUM still. assertEquals(HConstants.NO_SEQNUM, (long)sida.startCacheFlush(ENCODED_REGION_NAME, FAMILIES)); sida.completeCacheFlush(ENCODED_REGION_NAME); long currentSequenceId = sequenceid; sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid, true); final Set<byte[]> otherFamily = new HashSet<>(1); otherFamily.add(Bytes.toBytes("otherCf")); sida.update(ENCODED_REGION_NAME, FAMILIES, ++sequenceid, true); // Should return oldest sequence id in the region. assertEquals(currentSequenceId, (long)sida.startCacheFlush(ENCODED_REGION_NAME, otherFamily)); sida.completeCacheFlush(ENCODED_REGION_NAME); }
@Override public long getEarliestMemStoreSeqNum(byte[] encodedRegionName, byte[] familyName) { // This method is used by tests and for figuring if we should flush or not because our // sequenceids are too old. It is also used reporting the master our oldest sequenceid for use // figuring what edits can be skipped during log recovery. getEarliestMemStoreSequenceId // from this.sequenceIdAccounting is looking first in flushingOldestStoreSequenceIds, the // currently flushing sequence ids, and if anything found there, it is returning these. This is // the right thing to do for the reporting oldest sequenceids to master; we won't skip edits if // we crash during the flush. For figuring what to flush, we might get requeued if our sequence // id is old even though we are currently flushing. This may mean we do too much flushing. return this.sequenceIdAccounting.getLowestSequenceId(encodedRegionName, familyName); }
/** * We've been passed a new sequenceid for the region. Set it as highest seen for this region and * if we are to record oldest, or lowest sequenceids, save it as oldest seen if nothing * currently older. * @param encodedRegionName * @param families * @param sequenceid * @param lowest Whether to keep running account of oldest sequence id. */ void update(byte[] encodedRegionName, Set<byte[]> families, long sequenceid, final boolean lowest) { Long l = Long.valueOf(sequenceid); this.highestSequenceIds.put(encodedRegionName, l); if (lowest) { ConcurrentMap<ImmutableByteArray, Long> m = getOrCreateLowestSequenceIds(encodedRegionName); for (byte[] familyName : families) { m.putIfAbsent(ImmutableByteArray.wrap(familyName), l); } } }
/** * If the number of un-archived WAL files is greater than maximum allowed, check the first * (oldest) WAL file, and returns those regions which should be flushed so that it can be * archived. * @return regions (encodedRegionNames) to flush in order to archive oldest WAL file. */ byte[][] findRegionsToForceFlush() throws IOException { byte[][] regions = null; int logCount = getNumRolledLogFiles(); if (logCount > this.maxLogs && logCount > 0) { Map.Entry<Path, WalProps> firstWALEntry = this.walFile2Props.firstEntry(); regions = this.sequenceIdAccounting.findLower(firstWALEntry.getValue().encodedName2HighestSequenceId); } if (regions != null) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < regions.length; i++) { if (i > 0) { sb.append(", "); } sb.append(Bytes.toStringBinary(regions[i])); } LOG.info("Too many WALs; count=" + logCount + ", max=" + this.maxLogs + "; forcing flush of " + regions.length + " regions(s): " + sb.toString()); } return regions; }
protected final boolean append(W writer, FSWALEntry entry) throws IOException { // TODO: WORK ON MAKING THIS APPEND FASTER. DOING WAY TOO MUCH WORK WITH CPs, PBing, etc. atHeadOfRingBufferEventHandlerAppend(); long start = EnvironmentEdgeManager.currentTime(); byte[] encodedRegionName = entry.getKey().getEncodedRegionName(); long regionSequenceId = entry.getKey().getSequenceId(); // Edits are empty, there is nothing to append. Maybe empty when we are looking for a // region sequence id only, a region edit/sequence id that is not associated with an actual // edit. It has to go through all the rigmarole to be sure we have the right ordering. if (entry.getEdit().isEmpty()) { return false; } // Coprocessor hook. coprocessorHost.preWALWrite(entry.getRegionInfo(), entry.getKey(), entry.getEdit()); if (!listeners.isEmpty()) { for (WALActionsListener i : listeners) { i.visitLogEntryBeforeWrite(entry.getKey(), entry.getEdit()); } } doAppend(writer, entry); assert highestUnsyncedTxid < entry.getTxid(); highestUnsyncedTxid = entry.getTxid(); sequenceIdAccounting.update(encodedRegionName, entry.getFamilyNames(), regionSequenceId, entry.isInMemStore()); coprocessorHost.postWALWrite(entry.getRegionInfo(), entry.getKey(), entry.getEdit()); // Update metrics. postAppend(entry, EnvironmentEdgeManager.currentTime() - start); numEntries.incrementAndGet(); return true; }
if (this.sequenceIdAccounting.areAllLower(sequenceNums, regionsBlockingThisWal)) { if (logsToArchive == null) { logsToArchive = new ArrayList<>();
@Override public void abortCacheFlush(byte[] encodedRegionName) { this.sequenceIdAccounting.abortCacheFlush(encodedRegionName); }
protected final void logRollAndSetupWalProps(Path oldPath, Path newPath, long oldFileLen) { int oldNumEntries = this.numEntries.getAndSet(0); String newPathString = newPath != null ? CommonFSUtils.getPath(newPath) : null; if (oldPath != null) { this.walFile2Props.put(oldPath, new WalProps(this.sequenceIdAccounting.resetHighest(), oldFileLen)); this.totalLogSize.addAndGet(oldFileLen); LOG.info("Rolled WAL {} with entries={}, filesize={}; new WAL {}", CommonFSUtils.getPath(oldPath), oldNumEntries, StringUtils.byteDesc(oldFileLen), newPathString); } else { LOG.info("New WAL {}", newPathString); } }
flushing = flattenToLowestSequenceId(this.flushingSequenceIds); unflushed = flattenToLowestSequenceId(this.lowestUnflushedSequenceIds);
@Test public void testAreAllLower() { SequenceIdAccounting sida = new SequenceIdAccounting(); sida.getOrCreateLowestSequenceIds(ENCODED_REGION_NAME); Map<byte[], Long> m = new HashMap<>(); m.put(ENCODED_REGION_NAME, HConstants.NO_SEQNUM); assertTrue(sida.areAllLower(m)); long sequenceid = 1; sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid, true); sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid++, true); sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid++, true); assertTrue(sida.areAllLower(m)); m.put(ENCODED_REGION_NAME, sequenceid); assertFalse(sida.areAllLower(m)); long lowest = sida.getLowestSequenceId(ENCODED_REGION_NAME); assertEquals("Lowest should be first sequence id inserted", 1, lowest); m.put(ENCODED_REGION_NAME, lowest); assertFalse(sida.areAllLower(m)); sida.startCacheFlush(ENCODED_REGION_NAME, FAMILIES); assertFalse(sida.areAllLower(m)); m.put(ENCODED_REGION_NAME, HConstants.NO_SEQNUM); assertTrue(sida.areAllLower(m)); sida.completeCacheFlush(ENCODED_REGION_NAME); m.put(ENCODED_REGION_NAME, sequenceid); assertTrue(sida.areAllLower(m)); sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid++, true); sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid++, true);
@Test public void testStartCacheFlush() { SequenceIdAccounting sida = new SequenceIdAccounting(); sida.getOrCreateLowestSequenceIds(ENCODED_REGION_NAME); Map<byte[], Long> m = new HashMap<>(); m.put(ENCODED_REGION_NAME, HConstants.NO_SEQNUM); assertEquals(HConstants.NO_SEQNUM, (long)sida.startCacheFlush(ENCODED_REGION_NAME, FAMILIES)); sida.completeCacheFlush(ENCODED_REGION_NAME); long sequenceid = 1; sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid, true); // Only one family so should return NO_SEQNUM still. assertEquals(HConstants.NO_SEQNUM, (long)sida.startCacheFlush(ENCODED_REGION_NAME, FAMILIES)); sida.completeCacheFlush(ENCODED_REGION_NAME); long currentSequenceId = sequenceid; sida.update(ENCODED_REGION_NAME, FAMILIES, sequenceid, true); final Set<byte[]> otherFamily = new HashSet<>(1); otherFamily.add(Bytes.toBytes("otherCf")); sida.update(ENCODED_REGION_NAME, FAMILIES, ++sequenceid, true); // Should return oldest sequence id in the region. assertEquals(currentSequenceId, (long)sida.startCacheFlush(ENCODED_REGION_NAME, otherFamily)); sida.completeCacheFlush(ENCODED_REGION_NAME); }
/** * Returns the lowest unflushed sequence id for the region. * @param encodedRegionName * @return Lowest outstanding unflushed sequenceid for <code>encodedRegionName</code>. Will * return {@link HConstants#NO_SEQNUM} when none. */ long getLowestSequenceId(final byte[] encodedRegionName) { synchronized (this.tieLock) { Map<?, Long> m = this.flushingSequenceIds.get(encodedRegionName); long flushingLowest = m != null ? getLowestSequenceId(m) : Long.MAX_VALUE; m = this.lowestUnflushedSequenceIds.get(encodedRegionName); long unflushedLowest = m != null ? getLowestSequenceId(m) : HConstants.NO_SEQNUM; return Math.min(flushingLowest, unflushedLowest); } }
ConcurrentMap<ImmutableByteArray, Long> m = getOrCreateLowestSequenceIds(encodedRegionName); boolean replaced = false; while (!replaced) {
/** * If the number of un-archived WAL files is greater than maximum allowed, check the first * (oldest) WAL file, and returns those regions which should be flushed so that it can * be archived. * @return regions (encodedRegionNames) to flush in order to archive oldest WAL file. * @throws IOException */ byte[][] findRegionsToForceFlush() throws IOException { byte [][] regions = null; int logCount = getNumRolledLogFiles(); if (logCount > this.maxLogs && logCount > 0) { Map.Entry<Path, Map<byte[], Long>> firstWALEntry = this.byWalRegionSequenceIds.firstEntry(); regions = this.sequenceIdAccounting.findLower(firstWALEntry.getValue()); } if (regions != null) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < regions.length; i++) { if (i > 0) sb.append(", "); sb.append(Bytes.toStringBinary(regions[i])); } LOG.info("Too many WALs; count=" + logCount + ", max=" + this.maxLogs + "; forcing flush of " + regions.length + " regions(s): " + sb.toString()); } return regions; }
assert highestUnsyncedSequence < entry.getSequence(); highestUnsyncedSequence = entry.getSequence(); sequenceIdAccounting.update(encodedRegionName, entry.getFamilyNames(), regionSequenceId, entry.isInMemstore()); coprocessorHost.postWALWrite(entry.getHRegionInfo(), entry.getKey(), entry.getEdit());
/** * Archive old logs. A WAL is eligible for archiving if all its WALEdits have been flushed. * @throws IOException */ private void cleanOldLogs() throws IOException { List<Path> logsToArchive = null; // For each log file, look at its Map of regions to highest sequence id; if all sequence ids // are older than what is currently in memory, the WAL can be GC'd. for (Map.Entry<Path, Map<byte[], Long>> e : this.byWalRegionSequenceIds.entrySet()) { Path log = e.getKey(); Map<byte[], Long> sequenceNums = e.getValue(); if (this.sequenceIdAccounting.areAllLower(sequenceNums)) { if (logsToArchive == null) logsToArchive = new ArrayList<Path>(); logsToArchive.add(log); if (LOG.isTraceEnabled()) LOG.trace("WAL file ready for archiving " + log); } } if (logsToArchive != null) { for (Path p : logsToArchive) { this.totalLogSize.addAndGet(-this.fs.getFileStatus(p).getLen()); archiveLogFile(p); this.byWalRegionSequenceIds.remove(p); } } }