org.apache.jena.tdb.base.block.BlockMgrCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jena-tdb Show documentation
Show all versions of jena-tdb Show documentation
TDB is a storage subsystem for Jena and ARQ, it is a native triple store providing persistent storage of triples/quads.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.tdb.base.block;
import java.util.Iterator ;
import org.apache.jena.atlas.lib.Cache ;
import org.apache.jena.atlas.lib.CacheFactory ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
/** Caching block manager - this is an LRU cache */
public class BlockMgrCache extends BlockMgrSync
{
// Actually, this is two caches, one on the read blocks and one on the write blocks.
// The overridden public operations are sync'ed.
// As sync is on "this", it also covers all the other operations via BlockMgrSync
private static Logger log = LoggerFactory.getLogger(BlockMgrCache.class) ;
// Read cache : always present.
private final Cache readCache ;
// Delayed dirty writes. May be present, may not.
private final Cache writeCache ;
public static boolean globalLogging = false ; // Also enable the logging level.
private boolean logging = false ; // Also enable the logging level.
// ---- stats
long cacheReadHits = 0 ;
long cacheMisses = 0 ;
long cacheWriteHits = 0 ;
static BlockMgr create(int readSlots, int writeSlots, final BlockMgr blockMgr)
{
if ( readSlots < 0 && writeSlots < 0 )
return blockMgr ;
return new BlockMgrCache(readSlots, writeSlots, blockMgr) ;
}
private BlockMgrCache(int readSlots, int writeSlots, final BlockMgr blockMgr)
{
super(blockMgr) ;
// Caches are related so we can't use a Getter for cache management.
if ( readSlots < -1 )
readCache = CacheFactory.createNullCache() ;
else
readCache = CacheFactory.createCache(readSlots) ;
if ( writeSlots <= 0 )
writeCache = null ;
else
{
writeCache = CacheFactory.createCache(writeSlots) ;
writeCache.setDropHandler((id, block) -> {
// We're inside a synchronized operation at this point.
log("Cache spill: write block: %d", id) ;
if (block == null)
{
log.warn("Write cache: " + id + " dropping an entry that isn't there") ;
return ;
}
// Force the block to be writtern
// by sending it to the wrapped BlockMgr
BlockMgrCache.super.write(block) ;
}
) ;
}
}
// Pool?
// @Override
// public ByteBuffer allocateBuffer(int id)
// {
// super.allocateBuffer(id) ;
// }
@Override
synchronized
public Block getRead(long id)
{
// A Block may be in the read cache or the write cache.
// It can be just in the write cache because the read cache is finite.
Block blk = readCache.getIfPresent(id) ;
if ( blk != null )
{
cacheReadHits++ ;
log("Hit(r->r) : %d", id) ;
return blk ;
}
// A requested block may be in the other cache.
// Writable blocks are readable.
// readable blocks are not writeable (see below).
if ( writeCache != null )
// Might still be in the dirty blocks.
// Leave in write cache
blk = writeCache.getIfPresent(id) ;
if ( blk != null )
{
cacheWriteHits++ ;
log("Hit(r->w) : %d",id) ;
return blk ;
}
cacheMisses++ ;
log("Miss/r: %d", id) ;
blk = super.getRead(id) ;
readCache.put(id, blk) ;
return blk ;
}
@Override
synchronized
public Block getReadIterator(long id)
{
// And don't pass down "iterator" calls.
return getRead(id) ;
}
@Override
synchronized
public Block getWrite(long _id)
{
Long id = _id;
Block blk = null ;
if ( writeCache != null )
blk = writeCache.getIfPresent(id) ;
if ( blk != null )
{
cacheWriteHits++ ;
log("Hit(w->w) : %d", id) ;
return blk ;
}
// blk is null.
// A requested block may be in the other cache. Promote it.
if ( readCache.containsKey(id) )
{
blk = readCache.getIfPresent(id) ;
cacheReadHits++ ;
log("Hit(w->r) : %d", id) ;
blk = promote(blk) ;
return blk ;
}
// Did not find.
cacheMisses++ ;
log("Miss/w: %d", id) ;
// Pass operation to wrapper.
blk = super.getWrite(id);
if ( writeCache != null )
writeCache.put(id, blk) ;
return blk ;
}
@Override
synchronized
public Block promote(Block block)
{
Long id = block.getId() ;
readCache.remove(id) ;
Block block2 = super.promote(block) ;
if ( writeCache != null )
writeCache.put(id, block2) ;
return block ;
}
@Override
synchronized
public void write(Block block)
{
writeCache(block) ;
super.write(block) ;
}
@Override
synchronized
public void overwrite(Block block)
{
Long id = block.getId() ;
// It can be a read block (by the transaction), now being written for real (enacting a transaction).
super.overwrite(block) ;
// Keep read cache up-to-date.
// Must at least expel the read block (which is not the overwrite block).
readCache.put(id, block) ;
}
private void writeCache(Block block)
{
Long id = block.getId() ;
log("WriteCache : %d", id) ;
// Should not be in the read cache due to a getWrite earlier.
if ( readCache.containsKey(id) )
log.warn("write: Block in the read cache") ;
if ( writeCache != null )
{
writeCache.put(id, block) ;
return ;
}
}
@Override
synchronized
public void free(Block block)
{
Long id = block.getId() ;
log("Free : %d", id) ;
if ( readCache.containsKey(id) )
{
log.warn("Freeing block from read cache") ;
readCache.remove(id) ;
}
if ( writeCache != null )
writeCache.remove(id) ;
super.free(block) ;
}
@Override
synchronized
public void sync()
{
_sync(false) ;
}
@Override
synchronized
public void syncForce()
{
_sync(true) ;
}
@Override
synchronized
public void close()
{
if ( writeCache != null )
log("close ("+writeCache.size()+" blocks)") ;
syncFlush() ;
super.close() ;
}
@Override
public String toString()
{
return "Cache:"+super.blockMgr.toString() ;
}
private void log(String fmt, Object... args)
{
if ( ! logging && ! globalLogging ) return ;
String msg = String.format(fmt, args) ;
if ( getLabel() != null )
msg = getLabel()+" : "+msg ;
log.debug(msg) ;
}
private void _sync(boolean force)
{
if ( true )
{
String x = "" ;
if ( getLabel() != null )
x = getLabel()+" : ";
log("%sH=%d, M=%d, W=%d", x, cacheReadHits, cacheMisses, cacheWriteHits) ;
}
if ( writeCache != null )
log("sync (%d blocks)", writeCache.size()) ;
else
log("sync") ;
boolean somethingWritten = syncFlush() ;
if ( force )
{
log("syncForce underlying BlockMgr") ;
super.syncForce() ;
}
else if ( somethingWritten )
{
log("sync underlying BlockMgr") ;
super.sync() ;
}
else
log("Empty sync") ;
}
private boolean syncFlush()
{
if ( writeCache == null ) return false ;
boolean didSync = false ;
log("Flush (write cache)") ;
long N = writeCache.size() ;
Long[] ids = new Long[(int)N] ;
// Single writer (sync is a write operation MRSW)
// Iterating is safe.
Iterator iter = writeCache.keys() ;
if ( iter.hasNext() )
didSync = true ;
// Need to get all then delete else concurrent modification exception.
for ( int i = 0 ; iter.hasNext() ; i++ )
ids[i] = iter.next() ;
for ( int i = 0 ; i < N ; i++ )
{
Long id = ids[i] ;
expelEntry(id) ;
}
if ( didSync )
super.sync() ;
return didSync ;
}
// Write out when flushed.
// Do not call from drop handler.
private void expelEntry(Long id)
{
Block block = writeCache.getIfPresent(id) ;
if ( block == null )
{
log.warn("Write cache: "+id+" expelling entry that isn't there") ;
return ;
}
log("Expel (write cache): %d", id) ;
// This pushes the block to the BlockMgr being cached.
super.write(block) ;
writeCache.remove(id) ;
// Move it into the readCache because it's often read after writing
// and the read cache is often larger.
readCache.put(id, block) ;
}
}