package com.sleepycat.je.incomp; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.cleaner.TrackedFileSummary; import com.sleepycat.je.cleaner.UtilizationTracker; import com.sleepycat.je.config.EnvironmentParams; import com.sleepycat.je.dbi.DatabaseImpl; import com.sleepycat.je.dbi.DbTree; import com.sleepycat.je.dbi.EnvironmentImpl; import com.sleepycat.je.tree.BIN; import com.sleepycat.je.tree.BINReference; import com.sleepycat.je.tree.CursorsExistException; import com.sleepycat.je.tree.DBIN; import com.sleepycat.je.tree.DIN; import com.sleepycat.je.tree.IN; import com.sleepycat.je.tree.Key; import com.sleepycat.je.tree.Node; import com.sleepycat.je.tree.NodeNotEmptyException; import com.sleepycat.je.tree.Tree; import com.sleepycat.je.tree.Tree.SearchType; import com.sleepycat.je.utilint.DaemonThread; import com.sleepycat.je.utilint.PropUtil; import com.sleepycat.je.utilint.Tracer; import de.ovgu.cide.jakutil.*; /** * The IN Compressor. JE compression consist of removing delete entries from * BINs, and pruning empty IN/BINs from the tree. Compression is carried out by * either a daemon thread or lazily by operations (namely checkpointing and * eviction) that are writing INS. */ public class INCompressor extends DaemonThread { private static final String TRACE_COMPRESS="INCompress:"; private static final boolean DEBUG=false; private EnvironmentImpl env; private long lockTimeout; private Map binRefQueue; private Object binRefQueueSync; public INCompressor( EnvironmentImpl env, long waitTime, String name) throws DatabaseException { super(waitTime,name,env); this.env=env; lockTimeout=PropUtil.microsToMillis(env.getConfigManager().getLong(EnvironmentParams.COMPRESSOR_LOCK_TIMEOUT)); binRefQueue=new HashMap(); binRefQueueSync=new Object(); } public String toString(){ StringBuffer sb=new StringBuffer(); sb.append("<INCompressor name=\"").append(name).append("\"/>"); return sb.toString(); } synchronized public void clearEnv(){ env=null; } /** * The default daemon work queue is not used because we need a map, not a * set. */ public void addToQueue( Object o) throws DatabaseException { throw new DatabaseException("INCompressor.addToQueue should never be called."); } public int getBinRefQueueSize() throws DatabaseException { int size=0; synchronized (binRefQueueSync) { size=binRefQueue.size(); } return size; } /** * Adds the BIN and deleted Key to the queue if the BIN is not already in * the queue, or adds the deleted key to an existing entry if one exists. */ public void addBinKeyToQueue( BIN bin, Key deletedKey, boolean doWakeup) throws DatabaseException { synchronized (binRefQueueSync) { addBinKeyToQueueAlreadyLatched(bin,deletedKey); } if (doWakeup) { wakeup(); } } /** * Adds the BINReference to the queue if the BIN is not already in the * queue, or adds the deleted keys to an existing entry if one exists. */ public void addBinRefToQueue( BINReference binRef, boolean doWakeup) throws DatabaseException { synchronized (binRefQueueSync) { addBinRefToQueueAlreadyLatched(binRef); } if (doWakeup) { wakeup(); } } /** * Adds an entire collection of BINReferences to the queue at once. Use * this to avoid latching for each add. */ public void addMultipleBinRefsToQueue( Collection binRefs, boolean doWakeup) throws DatabaseException { synchronized (binRefQueueSync) { Iterator it=binRefs.iterator(); while (it.hasNext()) { BINReference binRef=(BINReference)it.next(); addBinRefToQueueAlreadyLatched(binRef); } } if (doWakeup) { wakeup(); } } /** * Adds the BINReference with the latch held. */ private void addBinRefToQueueAlreadyLatched( BINReference binRef){ Long node=new Long(binRef.getNodeId()); BINReference existingRef=(BINReference)binRefQueue.get(node); if (existingRef != null) { existingRef.addDeletedKeys(binRef); } else { binRefQueue.put(node,binRef); } } /** * Adds the BIN and deleted Key with the latch held. */ private void addBinKeyToQueueAlreadyLatched( BIN bin, Key deletedKey){ Long node=new Long(bin.getNodeId()); BINReference existingRef=(BINReference)binRefQueue.get(node); if (existingRef != null) { if (deletedKey != null) { existingRef.addDeletedKey(deletedKey); } } else { BINReference binRef=bin.createReference(); if (deletedKey != null) { binRef.addDeletedKey(deletedKey); } binRefQueue.put(node,binRef); } } public boolean exists( long nodeId){ Long node=new Long(nodeId); synchronized (binRefQueueSync) { return (binRefQueue.get(node) != null); } } private BINReference removeCompressibleBinReference( long nodeId){ Long node=new Long(nodeId); BINReference foundRef=null; synchronized (binRefQueueSync) { BINReference target=(BINReference)binRefQueue.remove(node); if (target != null) { if (target.deletedKeysExist()) { foundRef=target; } else { binRefQueue.put(node,target); } } } return foundRef; } /** * Return the number of retries when a deadlock exception occurs. */ protected int nDeadlockRetries() throws DatabaseException { return env.getConfigManager().getInt(EnvironmentParams.COMPRESSOR_RETRY); } public synchronized void onWakeup() throws DatabaseException { if (env.isClosed()) { return; } this.hook403(); doCompress(); } /** * The real work to doing a compress. This may be called by the compressor * thread or programatically. */ public synchronized void doCompress() throws DatabaseException { if (!isRunnable()) { return; } Map queueSnapshot=null; int binQueueSize=0; synchronized (binRefQueueSync) { binQueueSize=binRefQueue.size(); if (binQueueSize > 0) { queueSnapshot=binRefQueue; binRefQueue=new HashMap(); } } if (binQueueSize > 0) { this.hook404(); this.hook392(binQueueSize); this.hook393(); UtilizationTracker tracker=new UtilizationTracker(env); Map dbCache=new HashMap(); DbTree dbTree=env.getDbMapTree(); BINSearch binSearch=new BINSearch(); try { Iterator it=queueSnapshot.values().iterator(); while (it.hasNext()) { if (env.isClosed()) { return; } BINReference binRef=(BINReference)it.next(); if (!findDBAndBIN(binSearch,binRef,dbTree,dbCache)) { continue; } if (binRef.deletedKeysExist()) { boolean requeued=compressBin(binSearch.db,binSearch.bin,binRef,tracker); if (!requeued) { checkForRelocatedSlots(binSearch.db,binRef,tracker); } } else { BIN foundBin=binSearch.bin; byte[] idKey=foundBin.getIdentifierKey(); boolean isDBIN=foundBin.containsDuplicates(); byte[] dupKey=null; if (isDBIN) { dupKey=((DBIN)foundBin).getDupKey(); } this.hook394(foundBin); pruneBIN(binSearch.db,binRef,idKey,isDBIN,dupKey,tracker); } } TrackedFileSummary[] summaries=tracker.getTrackedFiles(); if (summaries.length > 0) { env.getUtilizationProfile().countAndLogSummaries(summaries); } } finally { this.hook395(); this.hook405(); } } } /** * Compresses a single BIN and then deletes the BIN if it is empty. * @param bin is latched when this method is called, and unlatched when it * returns. * @return true if the BINReference was requeued by this method. */ private boolean compressBin( DatabaseImpl db, BIN bin, BINReference binRef, UtilizationTracker tracker) throws DatabaseException { boolean empty=false; boolean requeued=false; byte[] idKey=bin.getIdentifierKey(); byte[] dupKey=null; boolean isDBIN=bin.containsDuplicates(); this.hook396(bin,binRef,empty,requeued,dupKey,isDBIN); if (empty) { requeued=pruneBIN(db,binRef,idKey,isDBIN,dupKey,tracker); } return requeued; } /** * If the target BIN is empty, attempt to remove the empty branch of the * tree. * @return true if the pruning was unable to proceed and the BINReference * was requeued. */ private boolean pruneBIN( DatabaseImpl dbImpl, BINReference binRef, byte[] idKey, boolean containsDups, byte[] dupKey, UtilizationTracker tracker) throws DatabaseException { boolean requeued=false; try { Tree tree=dbImpl.getTree(); if (containsDups) { tree.deleteDup(idKey,dupKey,tracker); } else { tree.delete(idKey,tracker); } this.hook406(); } catch ( NodeNotEmptyException NNEE) { this.hook407(); } catch ( CursorsExistException e) { addBinRefToQueue(binRef,false); this.hook408(); requeued=true; } return requeued; } private void checkForRelocatedSlots( DatabaseImpl db, BINReference binRef, UtilizationTracker tracker) throws DatabaseException { Iterator iter=binRef.getDeletedKeyIterator(); if (iter != null) { byte[] mainKey=binRef.getKey(); boolean isDup=(binRef.getData() != null); while (iter.hasNext()) { Key key=(Key)iter.next(); BIN splitBin=isDup ? searchForBIN(db,mainKey,key.getKey()) : searchForBIN(db,key.getKey(),null); if (splitBin != null) { BINReference splitBinRef=splitBin.createReference(); splitBinRef.addDeletedKey(key); compressBin(db,splitBin,splitBinRef,tracker); } } } } private boolean isRunnable() throws DatabaseException { return true; } /** * Search the tree for the BIN or DBIN that corresponds to this * BINReference. * @param binRef the BINReference that indicates the bin we want. * @return the BIN or DBIN that corresponds to this BINReference. The * node is latched upon return. Returns null if the BIN can't be found. */ public BIN searchForBIN( DatabaseImpl db, BINReference binRef) throws DatabaseException { return searchForBIN(db,binRef.getKey(),binRef.getData()); } private BIN searchForBIN( DatabaseImpl db, byte[] mainKey, byte[] dupKey) throws DatabaseException { try { Tree tree=db.getTree(); IN in=tree.search(mainKey,SearchType.NORMAL,-1,null,false); if (in == null) { return null; } if (dupKey == null) { return (BIN)in; } DIN duplicateRoot=null; DBIN duplicateBin=null; BIN bin=(BIN)in; this.hook397(mainKey,dupKey,tree,duplicateRoot,duplicateBin,bin); throw ReturnHack.returnObject; } catch ( ReturnObject r) { return (BIN)r.value; } } /** * Lazily compress a single BIN. Do not do any pruning. The target IN * should be latched when we enter, and it will be remain latched. */ public void lazyCompress( IN in) throws DatabaseException { if (!in.isCompressible()) { return; } this.hook398(in); BIN bin=(BIN)in; int nCursors=bin.nCursors(); if (nCursors > 0) { return; } else { BINReference binRef=removeCompressibleBinReference(bin.getNodeId()); if ((binRef == null) || (!binRef.deletedKeysExist())) { return; } else { boolean requeued=bin.compress(binRef,false); this.hook409(); if (!requeued && binRef.deletedKeysExist()) { addBinRefToQueue(binRef,false); this.hook410(); } else { if (bin.getNEntries() == 0) { addBinRefToQueue(binRef,false); this.hook411(); } } } } } private boolean findDBAndBIN( BINSearch binSearch, BINReference binRef, DbTree dbTree, Map dbCache) throws DatabaseException { binSearch.db=dbTree.getDb(binRef.getDatabaseId(),lockTimeout,dbCache); boolean close=binSearch.db == null; close=this.hook415(binSearch,close); if (close) { this.hook412(); return false; } this.hook391(); binSearch.bin=searchForBIN(binSearch.db,binRef); if ((binSearch.bin == null) || binSearch.bin.getNodeId() != binRef.getNodeId()) { this.hook399(binSearch); this.hook413(); return false; } return true; } private static class BINSearch { public DatabaseImpl db; public BIN bin; } protected void hook391() throws DatabaseException { } protected void hook392( int binQueueSize) throws DatabaseException { } protected void hook393() throws DatabaseException { } protected void hook394( BIN foundBin) throws DatabaseException { } protected void hook395() throws DatabaseException { } protected void hook396( BIN bin, BINReference binRef, boolean empty, boolean requeued, byte[] dupKey, boolean isDBIN) throws DatabaseException { int nCursors=bin.nCursors(); if (nCursors > 0) { addBinRefToQueue(binRef,false); requeued=true; this.hook414(); } else { requeued=bin.compress(binRef,true); if (!requeued) { empty=(bin.getNEntries() == 0); if (empty) { if (isDBIN) { dupKey=((DBIN)bin).getDupKey(); } } } } } protected void hook397( byte[] mainKey, byte[] dupKey, Tree tree, DIN duplicateRoot, DBIN duplicateBin, BIN bin) throws DatabaseException { int index=bin.findEntry(mainKey,false,true); if (index >= 0) { Node node=null; if (!bin.isEntryKnownDeleted(index)) { node=bin.fetchTarget(index); } if (node == null) { this.hook400(bin); throw new ReturnObject(null); } if (node.containsDuplicates()) { duplicateRoot=(DIN)node; this.hook401(duplicateRoot,bin); duplicateBin=(DBIN)tree.searchSubTree(duplicateRoot,dupKey,SearchType.NORMAL,-1,null,false); throw new ReturnObject(duplicateBin); } else { throw new ReturnObject(bin); } } else { this.hook402(bin); throw new ReturnObject(null); } } protected void hook398( IN in) throws DatabaseException { } protected void hook399( BINSearch binSearch) throws DatabaseException { } protected void hook400( BIN bin) throws DatabaseException { } protected void hook401( DIN duplicateRoot, BIN bin) throws DatabaseException { } protected void hook402( BIN bin) throws DatabaseException { } protected void hook403() throws DatabaseException { } protected void hook404() throws DatabaseException { } protected void hook405() throws DatabaseException { } protected void hook406() throws DatabaseException, NodeNotEmptyException, CursorsExistException { } protected void hook407() throws DatabaseException { } protected void hook408() throws DatabaseException { } protected void hook409() throws DatabaseException { } protected void hook410() throws DatabaseException { } protected void hook411() throws DatabaseException { } protected void hook412() throws DatabaseException { } protected void hook413() throws DatabaseException { } protected void hook414() throws DatabaseException { } protected boolean hook415( BINSearch binSearch, boolean close) throws DatabaseException { return close; } }