[Opensymphony-oscache] Cache slows down with too many cache keys

Lance Java <[email protected]> Mon, 12 Oct 2009 15:27:16 +0100
Newsgroups gmane.comp.java.open-symphony.os-cache
Message-ID <[email protected]>
--00151744790efe02300475bdb965
Content-Type: text/plain; charset=ISO-8859-1

I have debugged through
com.opensymphony.oscache.plugins.diskpersistence.HashDiskPersistenceListener
and have found that the Set of cache keys for a group is stored to disk.
Every time that a get or put occurs, the entire set of keys for the cache
group is deserialized or serialized to a file. This is not scalable and
causes an increasing lag as the number of cache entries in a group
increases.

I have implemented a fix that stores the cache keys in a ConcurrentHashMap
and have a separate thread that periodically saves the group keys to disk. I
also use spring's lifecycle to save the group keys to disk on server
shutdown.

Here's my solution for anyone experiencing the same issue

Cheers,
Lance.


Config:
cache.persistence.class=foo.bar.MyHashDiskPersistenceListener

Java:
/**
 * An extension of the default disk persistence where the group meta data is
stored in
 * memory rather than to disk. The oscache implementation of this class is
constantly
 * reading and writing the group meta data to disk which slows the
application down
 * when the number of keys in a group increases.
 * @author semmlan
 */
public class MyHashDiskPersistenceListener extends
HashDiskPersistenceListener {
   private static final Logger log =
Logger.getLogger(MyHashDiskPersistenceListener.class);

   /**
    * Groups are stored in a static variable because oscache uses it's own
factory and the
    * persistence listener instance that is used by oscache can not be
referenced
    */
   private static ConcurrentHashMap<String, Set<String>> groups = new
ConcurrentHashMap<String, Set<String>>();

   /**
    * Verify if a group exists in the cache
    *
    * @param group The group name to check
    * @return True if it exists
    * @throws CachePersistenceException
    */
   @Override
   public boolean isGroupStored(String groupName) throws
CachePersistenceException {
      return groups.containsKey(groupName);
   }
   /**
    * Deletes an entire group from the cache.
    *
    * @param groupName The name of the group to delete
    * @throws CachePersistenceException
    */
   @Override
   public void removeGroup(String groupName) throws
CachePersistenceException {
      groups.remove(groupName);
   }
   /**
    * Retrieves a group from the cache, or <code>null</code> if the group
    * file could not be found.
    *
    * @param groupName The name of the group to retrieve.
    * @return A <code>Set</code> containing keys of all of the cache
    * entries that belong to this group.
    * @throws CachePersistenceException
    */
   @Override
   public Set retrieveGroup(String groupName) throws
CachePersistenceException {
      return groups.get(groupName);
   }
   /**
    * Stores a group in the persistent cache. This will overwrite any
existing
    * group with the same name
    */
   @Override
   public void storeGroup(String groupName, Set group) throws
CachePersistenceException {
      groups.put(groupName, group);
   }

   /**
    * This method should be called periodically by a thread.
    * It should also be called on server shutdown
    */
   public void backupGroups() {
      for (Map.Entry<String, Set<String>> entry : groups.entrySet()) {
         try {
            super.storeGroup(entry.getKey(), entry.getValue());
         } catch (CachePersistenceException e) {
            log.error("Error storing group meta data for " + entry.getKey(),
e);
         }
      }
   }
}

--00151744790efe02300475bdb965
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable

<div>I have debugged through <font size=3D"2">com.opensymphony.oscache.plug=
ins.diskpersistence.HashDiskPersistenceListener and have found that the Set=
 of cache keys for a group is stored to disk. Every time that a get or put =
occurs, the entire set of keys for the cache group is deserialized or seria=
lized to a file. This is not scalable and causes an increasing lag as the n=
umber of cache entries in a group increases.</font></div>

<div>=A0</div>
<div>I have implemented a fix that stores the cache keys in a ConcurrentHas=
hMap and have a separate thread that periodically saves the group keys to d=
isk. I also use spring&#39;s lifecycle to save the group keys to disk on se=
rver shutdown.</div>

<div>=A0</div>
<div>Here&#39;s my solution=A0for anyone experiencing the same issue</div>
<div>=A0</div>
<div>Cheers,</div>
<div>Lance.</div>
<div>=A0</div>
<div>=A0</div>
<div>Config:</div>
<div>cache.persistence.class=3Dfoo.bar.MyHashDiskPersistenceListener</div>
<div>=A0</div>
<div>Java:=A0</div>
<div>/**<br>=A0* An extension of the default disk persistence where the gro=
up meta data is stored in<br>=A0* memory rather than to disk. The oscache i=
mplementation of this class is constantly<br>=A0* reading and writing the g=
roup meta data to disk which slows the application down<br>
=A0* when the number of keys in a group increases.<br>=A0* @author semmlan<=
br>=A0*/<br>public class MyHashDiskPersistenceListener extends HashDiskPers=
istenceListener {<br>=A0=A0 private static final Logger log =3D Logger.getL=
ogger(MyHashDiskPersistenceListener.class);<br>
=A0=A0 <br>=A0=A0 /**<br>=A0=A0=A0 * Groups are stored in a static variable=
 because oscache uses it&#39;s own factory and the<br>=A0=A0=A0 * persisten=
ce listener instance that is used by oscache can not be referenced<br>=A0=
=A0=A0 */<br>=A0=A0 private static ConcurrentHashMap&lt;String, Set&lt;Stri=
ng&gt;&gt; groups =3D new ConcurrentHashMap&lt;String, Set&lt;String&gt;&gt=
;();=A0 <br>
=A0=A0 <br>=A0=A0 /**<br>=A0=A0=A0 * Verify if a group exists in the cache<=
br>=A0=A0=A0 *<br>=A0=A0=A0 * @param group The group name to check<br>=A0=
=A0=A0 * @return True if it exists<br>=A0=A0=A0 * @throws CachePersistenceE=
xception<br>=A0=A0=A0 */<br>=A0=A0 @Override<br>
=A0=A0 public boolean isGroupStored(String groupName) throws CachePersisten=
ceException {<br>=A0=A0=A0=A0=A0 return groups.containsKey(groupName);<br>=
=A0=A0 }</div>
<div>=A0=A0 /**<br>=A0=A0=A0 * Deletes an entire group from the cache.<br>=
=A0=A0=A0 *<br>=A0=A0=A0 * @param groupName The name of the group to delete=
<br>=A0=A0=A0 * @throws CachePersistenceException<br>=A0=A0=A0 */<br>=A0=A0=
 @Override<br>=A0=A0 public void removeGroup(String groupName) throws Cache=
PersistenceException {<br>
=A0=A0=A0=A0=A0 groups.remove(groupName);<br>=A0=A0 }</div>
<div>=A0=A0 /**<br>=A0=A0=A0 * Retrieves a group from the cache, or &lt;cod=
e&gt;null&lt;/code&gt; if the group<br>=A0=A0=A0 * file could not be found.=
<br>=A0=A0=A0 *<br>=A0=A0=A0 * @param groupName The name of the group to re=
trieve.<br>=A0=A0=A0 * @return A &lt;code&gt;Set&lt;/code&gt; containing ke=
ys of all of the cache<br>
=A0=A0=A0 * entries that belong to this group.<br>=A0=A0=A0 * @throws Cache=
PersistenceException<br>=A0=A0=A0 */<br>=A0=A0 @Override<br>=A0=A0 public S=
et retrieveGroup(String groupName) throws CachePersistenceException {<br>=
=A0=A0=A0=A0=A0 return groups.get(groupName);<br>
=A0=A0 }</div>
<div>=A0=A0 /**<br>=A0=A0=A0 * Stores a group in the persistent cache. This=
 will overwrite any existing<br>=A0=A0=A0 * group with the same name<br>=A0=
=A0=A0 */<br>=A0=A0 @Override<br>=A0=A0 public void storeGroup(String group=
Name, Set group) throws CachePersistenceException {<br>
=A0=A0=A0=A0=A0 groups.put(groupName, group);<br>=A0=A0 }<br>=A0=A0=A0<br>=
=A0=A0 /**</div>
<div>=A0=A0=A0 * This method should be called periodically by a thread.</di=
v>
<div>=A0=A0=A0 * It should also be called on server shutdown</div>
<div>=A0=A0=A0 */</div>
<div>=A0=A0 public void backupGroups() {<br>=A0=A0=A0=A0=A0 for (Map.Entry&=
lt;String, Set&lt;String&gt;&gt; entry : groups.entrySet()) {<br>=A0=A0=A0=
=A0=A0=A0=A0=A0 try {<br>=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 super.storeGroup=
(entry.getKey(), entry.getValue());<br>=A0=A0=A0=A0=A0=A0=A0=A0 } catch (Ca=
chePersistenceException e) {<br>
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 log.error(&quot;Error storing group meta =
data for &quot; + entry.getKey(), e);<br>=A0=A0=A0=A0=A0=A0=A0=A0 }<br>=A0=
=A0=A0=A0=A0 }<br>=A0=A0 }<br>}<br></div>

--00151744790efe02300475bdb965--