Changed Key object management and database fetching strategy. Parallel fetching is now allowed, but at cache insertion strict checks are performed.
This commit is contained in:
parent
8451bfddd9
commit
e20aaec618
3 changed files with 72 additions and 64 deletions
|
@ -4,7 +4,7 @@
|
||||||
package helma.objectmodel;
|
package helma.objectmodel;
|
||||||
|
|
||||||
|
|
||||||
import Acme.LruHashtable;
|
import helma.util.CacheMap;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,32 +13,20 @@ import java.io.Serializable;
|
||||||
* key of the node and unique within each HOP application. Currently only
|
* key of the node and unique within each HOP application. Currently only
|
||||||
* single keys are supported.
|
* single keys are supported.
|
||||||
*/
|
*/
|
||||||
public class Key implements Serializable {
|
public final class Key implements Serializable {
|
||||||
|
|
||||||
protected String type;
|
protected String type;
|
||||||
protected String id;
|
protected String id;
|
||||||
private int hash;
|
private int hash;
|
||||||
private static LruHashtable keycache;
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized static Key makeKey (DbMapping dbmap, String id) {
|
public Key (DbMapping dbmap, String id) {
|
||||||
String _type = dbmap == null ? "" : dbmap.typename;
|
this.type = dbmap == null ? "" : dbmap.typename;
|
||||||
String _id = id.trim (); // removed .toLowerCase() - hw
|
this.id = id;
|
||||||
return makeKey (_type, _id);
|
hash = this.id.hashCode ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized static Key makeKey (String _type, String _id) {
|
public Key (String type, String id) {
|
||||||
if (keycache == null)
|
|
||||||
keycache = new LruHashtable (1000, 0.9f);
|
|
||||||
Key k = (Key) keycache.get (_type+"#"+_id);
|
|
||||||
if (k == null) {
|
|
||||||
k = new Key (_type, _id);
|
|
||||||
keycache.put (_type+"#"+_id, k);
|
|
||||||
}
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Key (String type, String id) {
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
hash = this.id.hashCode ();
|
hash = this.id.hashCode ();
|
||||||
|
@ -66,8 +54,7 @@ public class Key implements Serializable {
|
||||||
* a key that can't be mistaken for a relational db key.
|
* a key that can't be mistaken for a relational db key.
|
||||||
*/
|
*/
|
||||||
public Key getVirtualKey (String sid) {
|
public Key getVirtualKey (String sid) {
|
||||||
Key virtkey = makeKey ("", getVirtualID (type, id, sid));
|
return new Key ("", getVirtualID (type, id, sid));
|
||||||
return virtkey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getVirtualID (DbMapping pmap, String pid, String sid) {
|
public static String getVirtualID (DbMapping pmap, String pid, String sid) {
|
||||||
|
|
|
@ -96,6 +96,7 @@ public class Node implements INode, Serializable {
|
||||||
transient INode cacheNode;
|
transient INode cacheNode;
|
||||||
transient WrappedNodeManager nmgr;
|
transient WrappedNodeManager nmgr;
|
||||||
transient DbMapping dbmap;
|
transient DbMapping dbmap;
|
||||||
|
transient Key primaryKey = null;
|
||||||
transient DbMapping parentmap;
|
transient DbMapping parentmap;
|
||||||
transient String subnodeRelation = null;
|
transient String subnodeRelation = null;
|
||||||
transient long lastSubnodeFetch = 0;
|
transient long lastSubnodeFetch = 0;
|
||||||
|
@ -489,6 +490,7 @@ public class Node implements INode, Serializable {
|
||||||
public void setDbMapping (DbMapping dbmap) {
|
public void setDbMapping (DbMapping dbmap) {
|
||||||
if (this.dbmap != dbmap) {
|
if (this.dbmap != dbmap) {
|
||||||
this.dbmap = dbmap;
|
this.dbmap = dbmap;
|
||||||
|
primaryKey = null;
|
||||||
((Transactor) Thread.currentThread()).visitCleanNode (this);
|
((Transactor) Thread.currentThread()).visitCleanNode (this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -498,7 +500,9 @@ public class Node implements INode, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Key getKey () {
|
public Key getKey () {
|
||||||
return Key.makeKey (dbmap, id);
|
if (primaryKey == null)
|
||||||
|
primaryKey = new Key (dbmap, id);
|
||||||
|
return primaryKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSubnodeRelation (String rel) {
|
public void setSubnodeRelation (String rel) {
|
||||||
|
@ -1249,7 +1253,7 @@ public class Node implements INode, Serializable {
|
||||||
parent.unset (oldvalue);
|
parent.unset (oldvalue);
|
||||||
parent.addNode (this);
|
parent.addNode (this);
|
||||||
// let the node cache know this key's not for this node anymore.
|
// let the node cache know this key's not for this node anymore.
|
||||||
nmgr.evictKey (Key.makeKey (prel.other, prel.getKeyID (parent, oldvalue)));
|
nmgr.evictKey (new Key (prel.other, prel.getKeyID (parent, oldvalue)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parent.setNode (value, this);
|
parent.setNode (value, this);
|
||||||
|
@ -1434,10 +1438,10 @@ public class Node implements INode, Serializable {
|
||||||
propMap.put (p2, prop);
|
propMap.put (p2, prop);
|
||||||
}
|
}
|
||||||
String nID = n.getID();
|
String nID = n.getID();
|
||||||
((Transactor) Thread.currentThread ()).visitCleanNode (Key.makeKey (nmap, nID), n);
|
((Transactor) Thread.currentThread ()).visitCleanNode (new Key (nmap, nID), n);
|
||||||
// if the field is not the primary key of the property, also register it
|
// if the field is not the primary key of the property, also register it
|
||||||
if (rel != null && !rel.getKeyID(this, p2).equals (nID))
|
if (rel != null && !rel.getKeyID(this, p2).equals (nID))
|
||||||
((Transactor) Thread.currentThread ()).visitCleanNode (Key.makeKey (rel.other, rel.getKeyID(this, p2)), n);
|
((Transactor) Thread.currentThread ()).visitCleanNode (new Key (rel.other, rel.getKeyID(this, p2)), n);
|
||||||
|
|
||||||
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_ADDED, n));
|
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_ADDED, n));
|
||||||
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
|
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.io.*;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import Acme.LruHashtable;
|
import Acme.LruHashtable;
|
||||||
|
import helma.util.CacheMap;
|
||||||
import helma.objectmodel.*;
|
import helma.objectmodel.*;
|
||||||
import helma.framework.core.Application;
|
import helma.framework.core.Application;
|
||||||
import com.sleepycat.db.*;
|
import com.sleepycat.db.*;
|
||||||
|
@ -25,7 +26,7 @@ public final class NodeManager {
|
||||||
|
|
||||||
private Application app;
|
private Application app;
|
||||||
|
|
||||||
private LruHashtable cache;
|
private CacheMap cache;
|
||||||
|
|
||||||
protected DbWrapper db;
|
protected DbWrapper db;
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ public final class NodeManager {
|
||||||
public NodeManager (Application app, String dbHome, Properties props) throws DbException {
|
public NodeManager (Application app, String dbHome, Properties props) throws DbException {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
int cacheSize = Integer.parseInt (props.getProperty ("cachesize", "1000"));
|
int cacheSize = Integer.parseInt (props.getProperty ("cachesize", "1000"));
|
||||||
cache = new LruHashtable (cacheSize, 0.9f);
|
cache = new CacheMap (cacheSize, 0.8f);
|
||||||
IServer.getLogger().log ("set up node cache ("+cacheSize+")");
|
IServer.getLogger().log ("set up node cache ("+cacheSize+")");
|
||||||
|
|
||||||
safe = new WrappedNodeManager (this);
|
safe = new WrappedNodeManager (this);
|
||||||
|
@ -112,6 +113,10 @@ public final class NodeManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* private synchronized Node storeNodeInCache (Node n, Key k, Key pk) {
|
||||||
|
|
||||||
|
} */
|
||||||
|
|
||||||
public void shutdown () throws DbException {
|
public void shutdown () throws DbException {
|
||||||
db.shutdown ();
|
db.shutdown ();
|
||||||
this.cache = null;
|
this.cache = null;
|
||||||
|
@ -134,7 +139,7 @@ public final class NodeManager {
|
||||||
if (kstr == null)
|
if (kstr == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Key key = Key.makeKey (dbmap, kstr);
|
Key key = new Key (dbmap, kstr);
|
||||||
|
|
||||||
Transactor tx = (Transactor) Thread.currentThread ();
|
Transactor tx = (Transactor) Thread.currentThread ();
|
||||||
// tx.timer.beginEvent ("getNode "+kstr);
|
// tx.timer.beginEvent ("getNode "+kstr);
|
||||||
|
@ -153,20 +158,18 @@ public final class NodeManager {
|
||||||
|
|
||||||
// The requested node isn't in the shared cache. Synchronize with key to make sure only one
|
// The requested node isn't in the shared cache. Synchronize with key to make sure only one
|
||||||
// version is fetched from the database.
|
// version is fetched from the database.
|
||||||
synchronized (key) {
|
|
||||||
|
|
||||||
// check again because only in the synchronized section can we be sure that
|
|
||||||
// another thread hasn't fetched the node in the meantime.
|
|
||||||
node = (Node) cache.get (key);
|
|
||||||
|
|
||||||
if (node == null || node.getState() == Node.INVALID) {
|
|
||||||
node = getNodeByKey (db, tx.txn, kstr, dbmap);
|
node = getNodeByKey (db, tx.txn, kstr, dbmap);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
cache.put (node.getKey (), node);
|
synchronized (cache) {
|
||||||
}
|
Node oldnode = (Node) cache.put (node.getKey (), node);
|
||||||
|
if (oldnode != null && oldnode.getState () != Node.INVALID) {
|
||||||
|
cache.put (node.getKey (), oldnode);
|
||||||
|
node = oldnode;
|
||||||
}
|
}
|
||||||
} // synchronized
|
} // synchronized
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (node != null)
|
if (node != null)
|
||||||
tx.visitCleanNode (node.getKey (), node);
|
tx.visitCleanNode (node.getKey (), node);
|
||||||
|
@ -186,7 +189,7 @@ public final class NodeManager {
|
||||||
key = home.getKey ().getVirtualKey (kstr);
|
key = home.getKey ().getVirtualKey (kstr);
|
||||||
// if a key for a node from within the DB
|
// if a key for a node from within the DB
|
||||||
else
|
else
|
||||||
key = Key.makeKey (rel.other, rel.getKeyID (home, kstr));
|
key = new Key (rel.other, rel.getKeyID (home, kstr));
|
||||||
|
|
||||||
Transactor tx = (Transactor) Thread.currentThread ();
|
Transactor tx = (Transactor) Thread.currentThread ();
|
||||||
// tx.timer.beginEvent ("getNode "+kstr);
|
// tx.timer.beginEvent ("getNode "+kstr);
|
||||||
|
@ -198,41 +201,52 @@ public final class NodeManager {
|
||||||
// tx.timer.endEvent ("getNode "+kstr);
|
// tx.timer.endEvent ("getNode "+kstr);
|
||||||
// if we didn't fetch the node via its primary key, refresh the primary key in the cache.
|
// if we didn't fetch the node via its primary key, refresh the primary key in the cache.
|
||||||
// otherwise we risk cache corroption (duplicate node creation) if the node is fetched by its primary key
|
// otherwise we risk cache corroption (duplicate node creation) if the node is fetched by its primary key
|
||||||
if (!rel.usesPrimaryKey ())
|
if (!rel.usesPrimaryKey ()) {
|
||||||
cache.put (node.getKey (), node);
|
synchronized (cache) {
|
||||||
|
Node oldnode = (Node) cache.put (node.getKey (), node);
|
||||||
|
if (oldnode != null && oldnode.getState () != Node.INVALID) {
|
||||||
|
cache.put (node.getKey (), oldnode);
|
||||||
|
cache.put (key, oldnode);
|
||||||
|
node = oldnode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to get the node from the shared cache
|
// try to get the node from the shared cache
|
||||||
node = (Node) cache.get (key);
|
node = (Node) cache.get (key);
|
||||||
if (node == null || node.getState() == Node.INVALID) {
|
|
||||||
|
|
||||||
|
if (node == null || node.getState() == Node.INVALID) {
|
||||||
// The requested node isn't in the shared cache. Synchronize with key to make sure only one
|
// The requested node isn't in the shared cache. Synchronize with key to make sure only one
|
||||||
// version is fetched from the database.
|
// version is fetched from the database.
|
||||||
synchronized (key) {
|
|
||||||
|
|
||||||
// check again because only in the synchronized section can we be sure that
|
|
||||||
// another thread hasn't fetched the node in the meantime.
|
|
||||||
node = (Node) cache.get (key);
|
|
||||||
|
|
||||||
if (node == null || node.getState() == Node.INVALID) {
|
|
||||||
node = getNodeByRelation (db, tx.txn, home, kstr, rel);
|
node = getNodeByRelation (db, tx.txn, home, kstr, rel);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
Key primKey = node.getKey ();
|
Key primKey = node.getKey ();
|
||||||
cache.put (primKey, node);
|
synchronized (cache) {
|
||||||
if (!key.equals (primKey)) {
|
Node oldnode = (Node) cache.put (primKey, node);
|
||||||
// cache node with extra (non-primary but unique) key
|
if (oldnode != null && oldnode.getState () != Node.INVALID) {
|
||||||
cache.put (key, node);
|
cache.put (primKey, oldnode);
|
||||||
}
|
cache.put (key, oldnode);
|
||||||
}
|
node = oldnode;
|
||||||
}
|
}
|
||||||
} // synchronized
|
} // synchronized
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
// update primary key in cache, see above
|
// update primary key in cache, see above
|
||||||
if (!rel.usesPrimaryKey ())
|
if (!rel.usesPrimaryKey ()) {
|
||||||
cache.put (node.getKey (), node);
|
synchronized (cache) {
|
||||||
|
Node oldnode = (Node) cache.put (node.getKey (), node);
|
||||||
|
if (oldnode != null && oldnode.getState () != Node.INVALID) {
|
||||||
|
cache.put (node.getKey (), oldnode);
|
||||||
|
cache.put (key, oldnode);
|
||||||
|
node = oldnode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
tx.visitCleanNode (node.getKey (), node);
|
tx.visitCleanNode (node.getKey (), node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,9 +591,12 @@ public final class NodeManager {
|
||||||
retval.addElement (node.getID());
|
retval.addElement (node.getID());
|
||||||
Key primKey = node.getKey ();
|
Key primKey = node.getKey ();
|
||||||
// do we need to synchronize on primKey here?
|
// do we need to synchronize on primKey here?
|
||||||
Node oldnode = (Node) cache.get (primKey);
|
synchronized (cache) {
|
||||||
if (oldnode == null || oldnode.getState() == INode.INVALID)
|
Node oldnode = (Node) cache.put (primKey, node);
|
||||||
cache.put (primKey, node);
|
if (oldnode != null && oldnode.getState() != INode.INVALID) {
|
||||||
|
cache.put (primKey, oldnode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue