Skip to content

Commit

Permalink
More fully support LRU caching for EntityGroups and EntityRelations.
Browse files Browse the repository at this point in the history
This provides a middle ground for applications with large relations and
entity groups so they don't need to be fully cached (using all that
heap space) but can still benefit from LRU caching and otherwise be used
similar to when cached and get CacheMessageManager updates appropriately.

Note that for both LruCacheGroup and LruSqlEntityRelation, client code will
want to continue to avoid operations that result in reading the full table
into memory. These changes enable an application to treat these closer to
their fully-cached counterparts, but client code must still beware of the
performance consequences of an LruCacheGroup.list() or
LruSqlEntityRelation.replaceAll() or removeAll().

- SqlEntityRelation
  - Builder needed its members to be protected so they could be used by the
    new subclass in LruSqlEntityRelation.
  - leftValueList() and leftValueSet() had the same guts as leftIDs(), so
    change to simply use leftIDs() instead. Same for rightValueList() and
    rightValueSet() using rightIDs().
  - Add toString so registered relations are cleanly listed during startup.
- CachingEntityRelation
  - Create new interface to represent functionality shared between
    CachedRelation and the new LruSqlEntityRelation.
- CachedRelation
  - Implement CachingEntityRelation by changing some existing methods to
    @OverRide and moving their comments to the new interface.
  - Fix two bugs in CachedRelation where the wrong listener method was
    called in removeAll() and replaceAll().
- LruSqlEntityRelation
  - Create new class to provide an LRU cache of relations, similar to what
    LruCacheGroup does for entities.
- EntityStore
  - Change references of CachedRelation to CachingEntityRelation so
    LruSqlEntityRelation can be treated the same as CachedRelation.
- LruCacheGroup
  - Add map() for querying by a set of IDs instead of relying on a separate
    query per desired object.
  - Add querySingle() for querying by arbitrary criteria. Useful for login
    when User is an LruCacheGroup.
  - Default to distribute=true so LruCacheGroups send/receive cache
    messages.
- CacheMessageManager
  - Appropriately handle messages for an LruCacheGroup to keep them
    updated just like a CacheGroup is. This enables easier transitions from
    CacheGroup to LruCacheGroup.
  - Change references of CachedRelation to CachingEntityRelation so
    LruSqlEntityRelation can be kept updated just the same as
    CachedRelation.
  • Loading branch information
Keith R. Gustafson committed Mar 9, 2023
1 parent e5d408f commit 6a504e9
Show file tree
Hide file tree
Showing 7 changed files with 683 additions and 245 deletions.
155 changes: 16 additions & 139 deletions gemini/src/main/java/com/techempower/cache/CachedRelation.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
* @see com.techempower.collection.relation.LongRelation
*/
public class CachedRelation<L extends Identifiable, R extends Identifiable>
implements EntityRelation<L, R>, Identifiable
implements EntityRelation<L, R>, CachingEntityRelation<L, R>
{
// TODO: Consider whether database-level uniqueness constraints and "INSERT
// IGNORE" behavior should be supported. This shouldn't matter for
Expand Down Expand Up @@ -224,17 +224,7 @@ public boolean add(long leftID, long rightID)
return add(leftID, rightID, true, true, true);
}

/**
* Adds the specified pair to the relation.
*
* @param leftID the ID of the left value of the pair to be added
* @param rightID the ID of the right value of the pair to be added
* @param updateDatabase whether to update the database
* @param notifyListeners whether to notify the listeners of the change
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
* @return <tt>true</tt> if the relation changed as a result of the call
*/
@Override
public boolean add(long leftID, long rightID, boolean updateDatabase, boolean notifyListeners, boolean notifyDistributionListeners)
{
if (!this.loaded)
Expand Down Expand Up @@ -319,16 +309,7 @@ public boolean addAll(LongRelation relationToAdd)
return addAll(relationToAdd, true, true, true);
}

/**
* Adds the given pairs to the relation and updates the database.
*
* @param relationToAdd the pairs to be added
* @param updateDatabase whether to update the database
* @param notifyListeners whether to notify the listeners of the change
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
* @return <tt>true</tt> if the relation changed as a result of the call
*/
@Override
public boolean addAll(LongRelation relationToAdd, boolean updateDatabase,
boolean notifyListeners, boolean notifyDistributionListeners)
{
Expand Down Expand Up @@ -474,11 +455,7 @@ public boolean addAll(LongRelation relationToAdd, boolean updateDatabase,
}
}

/**
* Adds the given listener to the relation.
*
* @param listener the listener to be added
*/
@Override
public void addListener(CachedRelationListener listener)
{
this.listeners.add(listener);
Expand All @@ -490,14 +467,7 @@ public void clear()
clear(true, true, true);
}

/**
* Clears the relation of all pairs.
*
* @param updateDatabase whether to update the database
* @param notifyListeners whether to notify the listeners of the change
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
*/
@Override
public void clear(boolean updateDatabase, boolean notifyListeners, boolean notifyDistributionListeners)
{
this.lock.writeLock().lock();
Expand Down Expand Up @@ -790,11 +760,7 @@ public Set<L> leftValueSet(R right)
: leftValueSet(right.getId());
}

/**
* Returns a copy of the list of listeners to this relation.
*
* @return A copy of the list of listeners to this relation.
*/
@Override
public List<CachedRelationListener> listeners()
{
return new ArrayList<>(this.listeners);
Expand Down Expand Up @@ -894,17 +860,7 @@ public <T extends Identifiable> boolean removeEntity(Class<T> type, long idToRem
return changed;
}

/**
* Removes the specified pair of values from this relation.
*
* @param leftID the id of the left value of the pair to be removed
* @param rightID the id of the right value of the pair to be removed
* @param updateDatabase whether to update the database
* @param notifyListeners whether to notify the listeners of the change
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
* @return <tt>true</tt> if the relation was modified as a result of the call
*/
@Override
public boolean remove(long leftID, long rightID, boolean updateDatabase, boolean notifyListeners, boolean notifyDistributionListeners)
{
if (!this.loaded)
Expand Down Expand Up @@ -990,16 +946,7 @@ public boolean removeAll(LongRelation relationToRemove)
return removeAll(relationToRemove, true, true, true);
}

/**
* Removes the given pairs from the relation and updates the database.
*
* @param relationToRemove the pairs to be removed
* @param updateDatabase whether to update the database
* @param notifyListeners whether to notify the listeners of the change
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
* @return <tt>true</tt> if the relation changed as a result of the call
*/
@Override
public boolean removeAll(LongRelation relationToRemove, boolean updateDatabase,
boolean notifyListeners, boolean notifyDistributionListeners)
{
Expand Down Expand Up @@ -1119,7 +1066,7 @@ public boolean removeAll(LongRelation relationToRemove, boolean updateDatabase,
if (!(listener instanceof DistributionListener)
|| notifyDistributionListeners)
{
listener.addAll(this.id, relationToRemove);
listener.removeAll(this.id, relationToRemove);
}
}
}
Expand Down Expand Up @@ -1151,16 +1098,7 @@ public boolean removeLeftValue(long leftID)
return removeLeftValue(leftID, true, true, true);
}

/**
* Removes the specified left value from this relation.
*
* @param leftID the id of the left value to be removed from this relation
* @param updateDatabase whether to update the database
* @param notifyListeners whether to notify the listeners of the change
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
* @return <tt>true</tt> if the relation was modified as a result of the call
*/
@Override
public boolean removeLeftValue(long leftID, boolean updateDatabase, boolean notifyListeners, boolean notifyDistributionListeners)
{
if (!this.loaded)
Expand Down Expand Up @@ -1228,17 +1166,7 @@ public boolean removeRightValue(long rightID)
return removeRightValue(rightID, true, true, true);
}

/**
* Removes the specified right value from this relation.
*
* @param rightID the id of the right value to be removed from this
* relation
* @param updateDatabase whether to update the database
* @param notifyListeners whether to notify the listeners of the change
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
* @return <tt>true</tt> if the relation was modified as a result of the call
*/
@Override
public boolean removeRightValue(long rightID, boolean updateDatabase, boolean notifyListeners, boolean notifyDistributionListeners)
{
if (!this.loaded)
Expand Down Expand Up @@ -1313,41 +1241,7 @@ public boolean replaceAll(LongRelation relationToReplace)
return replaceAll(relationToReplace, true, true, true);
}

/**
* <p>Clears the existing relation, then sets the relation to the passed in
* relation.</p>
*
* <p>Note that this is generally preferable to doing the following:</p>
*
* <pre>
* {@code
* // foo is a CachedRelation.
* foo.clear();
* foo.addAll( .. );
* }
* </pre>
*
* <p>The above will cause foo to be empty for a certain window of time.
* Using replaceAll( .. ) will achieve the same end goal of clearing the
* current relation and then adding the passed in relation, but will never
* result in a call to this object seeing an empty relation.</p>
*
* <p>This call will only block reads very briefly while switching to the
* new cached relation and notifying listeners. If deferDatabaseUpdates is
* false, then this blocking extends until the database writes have
* completed.</p>
*
* <p>If called with a relation that is equivalent to the current relation,
* this function will immediately return with a value of false and will
* not hit the db or notify listeners.</p>
*
* @param relationToReplace the pairs to be added
* @param updateDatabase whether to update the database
* @param notifyListeners whether to notify the listeners of the change
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
* @return <tt>true</tt> if the relation changed as a result of the call
*/
@Override
public boolean replaceAll(LongRelation relationToReplace, boolean updateDatabase,
boolean notifyListeners, boolean notifyDistributionListeners)
{
Expand Down Expand Up @@ -1484,7 +1378,7 @@ public boolean replaceAll(LongRelation relationToReplace, boolean updateDatabase
if (!(listener instanceof DistributionListener)
|| notifyDistributionListeners)
{
listener.addAll(this.id, relationToReplace);
listener.replaceAll(this.id, relationToReplace);
}
}
}
Expand Down Expand Up @@ -1519,15 +1413,7 @@ public void reset()
reset(true, true);
}

/**
* Internally marks this relation as "not loaded", which is understood to
* mean that it should be reloaded from the database before being used
* again.
*
* @param notifyListeners whether to notify the listeners of the reset
* @param notifyDistributionListeners Whether to notify any
* DistributionListeners; only used When notifyListeners is true.
*/
@Override
public void reset(boolean notifyListeners, boolean notifyDistributionListeners)
{
this.lock.writeLock().lock();
Expand All @@ -1553,22 +1439,13 @@ public void reset(boolean notifyListeners, boolean notifyDistributionListeners)
}
}

/**
* Resets this relation if it maps objects of the specified type.
*
* @param type The type of the cache group being reset.
*/
@Override
public <T extends Identifiable> void reset(Class<T> type)
{
reset(type, true, true);
}

/**
* Resets this relation if it maps objects of the specified type.
*
* @param type The type of the cache group being reset.
* @param notifyListeners whether to notify the listeners of the reset
*/
@Override
public <T extends Identifiable> void reset(Class<T> type, boolean notifyListeners, boolean notifyDistributionListeners)
{
if (type.equals(this.leftType)
Expand Down
Loading

0 comments on commit 6a504e9

Please sign in to comment.