/** * Flush a specified ledger * * @param ledger Ledger Id * @throws IOException */ private void flushSpecificLedger(long ledger) throws IOException { LinkedList<Long> firstEntryList = pageMapAndList.getFirstEntryListToBeFlushed(ledger); // flush ledger index file header if necessary indexPersistenceManager.flushLedgerHeader(ledger); if (null == firstEntryList || firstEntryList.size() == 0) { LOG.debug("Nothing to flush for ledger {}.", ledger); // nothing to do return; } // Now flush all the pages of a ledger List<LedgerEntryPage> entries = new ArrayList<LedgerEntryPage>(firstEntryList.size()); try { for (Long firstEntry : firstEntryList) { LedgerEntryPage lep = getLedgerEntryPage(ledger, firstEntry, true); if (lep != null) { entries.add(lep); } } indexPersistenceManager.flushLedgerEntries(ledger, entries); } finally { for (LedgerEntryPage lep : entries) { lep.releasePage(); } } }
/** * Grab ledger entry page whose first entry is <code>pageEntry</code>. * * <p>If the page doesn't existed before, we allocate a memory page. Otherwise, we grab a clean * page and read it from disk. * * @param ledger Ledger Id * @param pageEntry Start entry of this entry page. */ private LedgerEntryPage grabLedgerEntryPage(long ledger, long pageEntry) throws IOException { LedgerEntryPage lep = grabCleanPage(ledger, pageEntry); try { // should get the up to date page from the persistence manager // before we put it into table otherwise we would put // an empty page in it indexPersistenceManager.updatePage(lep); LedgerEntryPage oldLep; if (lep != (oldLep = pageMapAndList.putPage(lep))) { lep.releasePage(); // Decrement the page count because we couldn't put this lep in the page cache. pageCount.decrementAndGet(); // Increment the use count of the old lep because this is unexpected oldLep.usePage(); lep = oldLep; } } catch (IOException ie) { // if we grab a clean page, but failed to update the page // we are exhausting the count of ledger entry pages. // since this page will be never used, so we need to decrement // page count of ledger cache. lep.releasePage(); pageCount.decrementAndGet(); throw ie; } return lep; }
private LedgerEntryPage grabCleanPage(long ledger, long entry) throws IOException { if (entry % entriesPerPage != 0) { throw new IllegalArgumentException(entry + " is not a multiple of " + entriesPerPage); } while (true) { boolean canAllocate = false; if (pageCount.incrementAndGet() <= pageLimit) { canAllocate = true; } else { pageCount.decrementAndGet(); } if (canAllocate) { LedgerEntryPage lep = new LedgerEntryPage(pageSize, entriesPerPage, pageMapAndList); lep.setLedgerAndFirstEntry(ledger, entry); lep.usePage(); return lep; } LedgerEntryPage lep = pageMapAndList.grabCleanPage(ledger, entry); if (null != lep) { return lep; } LOG.info( "Could not grab a clean page for ledger {}, entry {}, force flushing dirty ledgers.", ledger, entry); flushOneOrMoreLedgers(false); } }
/** * Add the LedgerEntryPage to the clean page LRU map * * @param lep Ledger Entry Page object */ private void addToCleanPagesList(LedgerEntryPage lep) { synchronized (lruCleanPageMap) { if (lep.isClean() && !lep.inUse()) { lruCleanPageMap.put(lep.getEntryKey(), lep); } } }
/** * Remove the LedgerEntryPage from the clean page LRU map * * @param lep Ledger Entry Page object */ private void removeFromCleanPageList(LedgerEntryPage lep) { synchronized (lruCleanPageMap) { if (!lep.isClean() || lep.inUse()) { lruCleanPageMap.remove(lep.getEntryKey()); } } }
LedgerEntryPage getLedgerEntryPage(Long ledger, Long firstEntry, boolean onlyDirty) { LedgerEntryPage lep = pageMapAndList.getPage(ledger, firstEntry); if (onlyDirty && null != lep && lep.isClean()) { return null; } if (null != lep) { lep.usePage(); } return lep; }
void flushLedgerEntries(long l, List<LedgerEntryPage> entries) throws IOException { FileInfo fi = null; try { Collections.sort( entries, new Comparator<LedgerEntryPage>() { @Override public int compare(LedgerEntryPage o1, LedgerEntryPage o2) { return (int) (o1.getFirstEntry() - o2.getFirstEntry()); } }); int[] versions = new int[entries.size()]; try { fi = getFileInfo(l, null); } catch (Bookie.NoLedgerException nle) { // ledger has been deleted LOG.info("No ledger {} found when flushing entries.", l); return; } // flush the header if necessary relocateIndexFileAndFlushHeader(l, fi); int start = 0; long lastOffset = -1; for (int i = 0; i < entries.size(); i++) { versions[i] = entries.get(i).getVersion(); if (lastOffset != -1 && (entries.get(i).getFirstEntry() - lastOffset) != entriesPerPage) { // send up a sequential list int count = i - start; if (count == 0) { LOG.warn("Count cannot possibly be zero!"); } writeBuffers(l, entries, fi, start, count); start = i; } lastOffset = entries.get(i).getFirstEntry(); } if (entries.size() - start == 0 && entries.size() != 0) { LOG.warn("Nothing to write, but there were entries!"); } writeBuffers(l, entries, fi, start, entries.size() - start); for (int i = 0; i < entries.size(); i++) { LedgerEntryPage lep = entries.get(i); lep.setClean(versions[i]); } if (LOG.isDebugEnabled()) { LOG.debug("Flushed ledger {} with {} pages.", l, entries.size()); } } finally { if (fi != null) { fi.release(); } } }
void putEntryOffset(long ledger, long entry, long offset) throws IOException { int offsetInPage = (int) (entry % entriesPerPage); // find the id of the first entry of the page that has the entry // we are looking for long pageEntry = entry - offsetInPage; LedgerEntryPage lep = getLedgerEntryPage(ledger, pageEntry, false); if (lep == null) { lep = grabLedgerEntryPage(ledger, pageEntry); } assert lep != null; lep.setOffset(offset, offsetInPage * LedgerEntryPage.getIndexEntrySize()); lep.releasePage(); }
long getEntryOffset(long ledger, long entry) throws IOException { int offsetInPage = (int) (entry % entriesPerPage); // find the id of the first entry of the page that has the entry // we are looking for long pageEntry = entry - offsetInPage; LedgerEntryPage lep = getLedgerEntryPage(ledger, pageEntry, false); try { if (lep == null) { lep = grabLedgerEntryPage(ledger, pageEntry); } return lep.getOffset(offsetInPage * LedgerEntryPage.getIndexEntrySize()); } finally { if (lep != null) { lep.releasePage(); } } }
/** * Get a clean page and provision it for the specified ledger and firstEntry within the ledger * * @param ledgerId Ledger id * @param firstEntry Id of the first entry in the page * @returns LedgerEntryPage if present */ LedgerEntryPage grabCleanPage(long ledgerId, long firstEntry) { LedgerEntryPage lep = null; while (lruCleanPageMap.size() > 0) { lep = null; synchronized (lruCleanPageMap) { Iterator<Map.Entry<EntryKey, LedgerEntryPage>> iterator = lruCleanPageMap.entrySet().iterator(); Map.Entry<EntryKey, LedgerEntryPage> entry = null; while (iterator.hasNext()) { entry = iterator.next(); iterator.remove(); if (entry.getValue().isClean() && !entry.getValue().inUse()) { lep = entry.getValue(); break; } } if (null == lep) { LOG.debug("Did not find eligible page in the first pass"); return null; } } // We found a candidate page, lets see if we can reclaim it before its re-used ConcurrentMap<Long, LedgerEntryPage> pageMap = pages.get(lep.getLedger()); // Remove from map only if nothing has changed since we checked this lep. // Its possible for the ledger to have been deleted or the page to have already // been reclaimed. The page map is the definitive source of information, if anything // has changed we should leave this page along and continue iterating to find // another suitable page. if ((null != pageMap) && (pageMap.remove(lep.getFirstEntry(), lep))) { if (!lep.isClean()) { // Someone wrote to this page while we were reclaiming it. pageMap.put(lep.getFirstEntry(), lep); lep = null; } else { // Do some bookkeeping on the page table pages.remove(lep.getLedger(), EMPTY_PAGE_MAP); // We can now safely reset this lep and return it. lep.usePage(); lep.zeroPage(); lep.setLedgerAndFirstEntry(ledgerId, firstEntry); return lep; } } else { lep = null; } } return lep; }
/** * Traverse the pages for a given ledger in memory and find the highest entry amongst these * pages * * @param ledgerId Ledger id * @returns last entry in the in memory pages */ private long getLastEntryInMem(long ledgerId) { long lastEntry = 0; // Find the last entry in the cache ConcurrentMap<Long, LedgerEntryPage> map = pages.get(ledgerId); if (map != null) { for (LedgerEntryPage lep : map.values()) { if (lep.getMaxPossibleEntry() < lastEntry) { continue; } lep.usePage(); long highest = lep.getLastEntry(); if (highest > lastEntry) { lastEntry = highest; } lep.releasePage(); } } return lastEntry; }
void updatePage(LedgerEntryPage lep) throws IOException { if (!lep.isClean()) { throw new IOException("Trying to update a dirty page"); } FileInfo fi = null; try { fi = getFileInfo(lep.getLedger(), null); long pos = lep.getFirstEntryPosition(); if (pos >= fi.size()) { lep.zeroPage(); } else { lep.readPage(fi); } } finally { if (fi != null) { fi.release(); } } }
/** * Add a LedgerEntryPage to the page map * * @param lep Ledger Entry Page object */ private LedgerEntryPage putPage(LedgerEntryPage lep) { // Do a get here to avoid too many new ConcurrentHashMaps() as putIntoTable is called // frequently. ConcurrentMap<Long, LedgerEntryPage> map = pages.get(lep.getLedger()); if (null == map) { ConcurrentMap<Long, LedgerEntryPage> mapToPut = new ConcurrentHashMap<Long, LedgerEntryPage>(); map = pages.putIfAbsent(lep.getLedger(), mapToPut); if (null == map) { map = mapToPut; } } LedgerEntryPage oldPage = map.putIfAbsent(lep.getFirstEntry(), lep); if (null == oldPage) { oldPage = lep; // Also include this in the clean page map if it qualifies. // Note: This is done for symmetry and correctness, however it should never // get exercised since we shouldn't attempt a put without the page being in use addToCleanPagesList(lep); } return oldPage; }
/** * Gets the list of pages in memory that have been changed and hence need to be written as a * part of the flush operation that is being issued * * @param ledgerId Ledger id * @returns last entry in the in memory pages. */ private LinkedList<Long> getFirstEntryListToBeFlushed(long ledgerId) { ConcurrentMap<Long, LedgerEntryPage> pageMap = pages.get(ledgerId); if (pageMap == null || pageMap.isEmpty()) { return null; } LinkedList<Long> firstEntryList = new LinkedList<Long>(); for (ConcurrentMap.Entry<Long, LedgerEntryPage> entry : pageMap.entrySet()) { LedgerEntryPage lep = entry.getValue(); if (lep.isClean()) { if (!lep.inUse()) { addToCleanPagesList(lep); } if (LOG.isTraceEnabled()) { LOG.trace("Page is clean " + lep); } } else { firstEntryList.add(lep.getFirstEntry()); } } return firstEntryList; }
long getPersistEntryBeyondInMem(long ledgerId, long lastEntryInMem) throws IOException { FileInfo fi = null; long lastEntry = lastEntryInMem; try { fi = getFileInfo(ledgerId, null); long size = fi.size(); // make sure the file size is aligned with index entry size // otherwise we may read incorret data if (0 != size % LedgerEntryPage.getIndexEntrySize()) { LOG.warn("Index file of ledger {} is not aligned with index entry size.", ledgerId); size = size - size % LedgerEntryPage.getIndexEntrySize(); } // we may not have the last entry in the cache if (size > lastEntry * LedgerEntryPage.getIndexEntrySize()) { ByteBuffer bb = ByteBuffer.allocate(pageSize); long position = size - pageSize; if (position < 0) { position = 0; } fi.read(bb, position); bb.flip(); long startingEntryId = position / LedgerEntryPage.getIndexEntrySize(); for (int i = entriesPerPage - 1; i >= 0; i--) { if (bb.getLong(i * LedgerEntryPage.getIndexEntrySize()) != 0) { if (lastEntry < startingEntryId + i) { lastEntry = startingEntryId + i; } break; } } } } finally { if (fi != null) { fi.release(); } } return lastEntry; }