HHH-4577 - 2L query cache: Low performance of flush and commit due many unnecessary (pre)invalidate calls on UpdateTimestampsCache
This commit is contained in:
parent
7faae967ca
commit
31ad26731c
|
@ -109,14 +109,15 @@ public class ActionQueue {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
|
||||||
unresolvedInsertions = new UnresolvedEntityInsertActions();
|
unresolvedInsertions = new UnresolvedEntityInsertActions();
|
||||||
insertions = new ExecutableList<AbstractEntityInsertAction>( ExecutableList.INIT_QUEUE_LIST_SIZE, new InsertActionSorter() );
|
|
||||||
deletions = new ExecutableList<EntityDeleteAction>( ExecutableList.INIT_QUEUE_LIST_SIZE );
|
|
||||||
updates = new ExecutableList<EntityUpdateAction>( ExecutableList.INIT_QUEUE_LIST_SIZE );
|
|
||||||
|
|
||||||
collectionCreations = new ExecutableList<CollectionRecreateAction>( ExecutableList.INIT_QUEUE_LIST_SIZE );
|
insertions = new ExecutableList<AbstractEntityInsertAction>( new InsertActionSorter() );
|
||||||
collectionRemovals = new ExecutableList<CollectionRemoveAction>( ExecutableList.INIT_QUEUE_LIST_SIZE );
|
deletions = new ExecutableList<EntityDeleteAction>();
|
||||||
collectionUpdates = new ExecutableList<CollectionUpdateAction>( ExecutableList.INIT_QUEUE_LIST_SIZE );
|
updates = new ExecutableList<EntityUpdateAction>();
|
||||||
collectionQueuedOps = new ExecutableList<QueuedOperationCollectionAction>( ExecutableList.INIT_QUEUE_LIST_SIZE );
|
|
||||||
|
collectionCreations = new ExecutableList<CollectionRecreateAction>();
|
||||||
|
collectionRemovals = new ExecutableList<CollectionRemoveAction>();
|
||||||
|
collectionUpdates = new ExecutableList<CollectionUpdateAction>();
|
||||||
|
collectionQueuedOps = new ExecutableList<QueuedOperationCollectionAction>();
|
||||||
|
|
||||||
// Important: these lists are in execution order
|
// Important: these lists are in execution order
|
||||||
List<ExecutableList<?>> tmp = new ArrayList<ExecutableList<?>>( 7 );
|
List<ExecutableList<?>> tmp = new ArrayList<ExecutableList<?>>( 7 );
|
||||||
|
@ -715,6 +716,10 @@ public class ActionQueue {
|
||||||
* @author Jay Erb
|
* @author Jay Erb
|
||||||
*/
|
*/
|
||||||
private static class InsertActionSorter implements ExecutableList.Sorter<AbstractEntityInsertAction> {
|
private static class InsertActionSorter implements ExecutableList.Sorter<AbstractEntityInsertAction> {
|
||||||
|
/**
|
||||||
|
* Singleton access
|
||||||
|
*/
|
||||||
|
public static final InsertActionSorter INSTANCE = new InsertActionSorter();
|
||||||
|
|
||||||
// the mapping of entity names to their latest batch numbers.
|
// the mapping of entity names to their latest batch numbers.
|
||||||
private Map<String, Integer> latestBatches;
|
private Map<String, Integer> latestBatches;
|
||||||
|
|
|
@ -36,13 +36,14 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.hibernate.action.spi.Executable;
|
import org.hibernate.action.spi.Executable;
|
||||||
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specialized encapsulating of the state pertaining to each Executable list.
|
* Specialized encapsulating of the state pertaining to each Executable list.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Lazily sorts the list and caches the sorted state.
|
* Manages sorting the executables (lazily)
|
||||||
* <p/>
|
* <p/>
|
||||||
* Lazily calculates the querySpaces affected by the actions in the list, and caches this too.
|
* Manages the querySpaces affected by the executables in the list, and caches this too.
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
* @author Anton Marsden
|
* @author Anton Marsden
|
||||||
|
@ -72,48 +73,70 @@ public class ExecutableList<E extends Executable & Comparable & Serializable> im
|
||||||
private final Sorter<E> sorter;
|
private final Sorter<E> sorter;
|
||||||
private boolean sorted;
|
private boolean sorted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to hold the query spaces (table names, roughly) that all the Executable instances contained
|
||||||
|
* in this list define. This information is ultimately used to invalidate cache regions as it is
|
||||||
|
* exposed from {@link #getQuerySpaces}. This value being {@code null} indicates that the
|
||||||
|
* query spaces should be calculated.
|
||||||
|
*/
|
||||||
private transient Set<Serializable> querySpaces;
|
private transient Set<Serializable> querySpaces;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new ExecutableList.
|
* Creates a new ExecutableList with the default settings.
|
||||||
*/
|
*/
|
||||||
public ExecutableList() {
|
public ExecutableList() {
|
||||||
this( null );
|
this( INIT_QUEUE_LIST_SIZE );
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ExecutableList using the specified Sorter.
|
|
||||||
*
|
|
||||||
* @param sorter The Sorter to use; may be {@code null}
|
|
||||||
*/
|
|
||||||
public ExecutableList(ExecutableList.Sorter<E> sorter) {
|
|
||||||
this( INIT_QUEUE_LIST_SIZE, sorter );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new ExecutableList with the specified initialCapacity.
|
* Creates a new ExecutableList with the specified initialCapacity.
|
||||||
*
|
*
|
||||||
* @param initialCapacity The initial capacity for instantiating the internal List
|
* @param initialCapacity The initial capacity for instantiating the internal List
|
||||||
*/
|
*/
|
||||||
ExecutableList(int initialCapacity) {
|
public ExecutableList(int initialCapacity) {
|
||||||
this( initialCapacity, null );
|
this( initialCapacity, null );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ExecutableList using the specified Sorter.
|
||||||
|
*
|
||||||
|
* @param sorter The Sorter to use; may be {@code null}
|
||||||
|
*/
|
||||||
|
public ExecutableList(ExecutableList.Sorter<E> sorter) {
|
||||||
|
this( INIT_QUEUE_LIST_SIZE, sorter );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new ExecutableList with the specified initialCapacity and Sorter.
|
* Creates a new ExecutableList with the specified initialCapacity and Sorter.
|
||||||
*
|
*
|
||||||
* @param initialCapacity The initial capacity for instantiating the internal List
|
* @param initialCapacity The initial capacity for instantiating the internal List
|
||||||
* @param sorter The Sorter to use; may be {@code null}
|
* @param sorter The Sorter to use; may be {@code null}
|
||||||
*/
|
*/
|
||||||
ExecutableList(int initialCapacity, ExecutableList.Sorter<E> sorter) {
|
public ExecutableList(int initialCapacity, ExecutableList.Sorter<E> sorter) {
|
||||||
this.sorter = sorter;
|
this.sorter = sorter;
|
||||||
this.executables = new ArrayList<E>( initialCapacity );
|
this.executables = new ArrayList<E>( initialCapacity );
|
||||||
// a non-null querySpaces value would add to the querySpaces as the list is added to,
|
this.querySpaces = new HashSet<Serializable>();
|
||||||
// but we would like this data to be lazily initialized.
|
|
||||||
this.querySpaces = null;
|
|
||||||
this.sorted = true;
|
this.sorted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily constructs the querySpaces affected by the actions in the list.
|
||||||
|
*
|
||||||
|
* @return the querySpaces affected by the actions in this list
|
||||||
|
*/
|
||||||
|
public Set<Serializable> getQuerySpaces() {
|
||||||
|
if ( querySpaces == null ) {
|
||||||
|
querySpaces = new HashSet<Serializable>();
|
||||||
|
for ( E e : executables ) {
|
||||||
|
Serializable[] propertySpaces = e.getPropertySpaces();
|
||||||
|
if ( querySpaces != null && propertySpaces != null ) {
|
||||||
|
Collections.addAll( querySpaces, propertySpaces );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return querySpaces;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the list is empty.
|
* @return true if the list is empty.
|
||||||
*/
|
*/
|
||||||
|
@ -129,13 +152,16 @@ public class ExecutableList<E extends Executable & Comparable & Serializable> im
|
||||||
* @return the entry that was removed
|
* @return the entry that was removed
|
||||||
*/
|
*/
|
||||||
public E remove(int index) {
|
public E remove(int index) {
|
||||||
if ( index < executables.size() - 1 ) {
|
// removals are generally safe in regards to sorting...
|
||||||
sorted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final E e = executables.remove( index );
|
final E e = executables.remove( index );
|
||||||
|
|
||||||
// clear the querySpaces cache if the removed Executable had property querySpaces
|
// If the executable being removed defined query spaces we need to recalculate the overall query spaces for
|
||||||
|
// this list. The problem is that we don't know how many other executable instances in the list also
|
||||||
|
// contributed those query spaces as well.
|
||||||
|
//
|
||||||
|
// An alternative here is to use a "multiset" which is a specialized set that keeps a reference count
|
||||||
|
// associated to each entry. But that is likely overkill here.
|
||||||
if ( e.getPropertySpaces() != null && e.getPropertySpaces().length > 0 ) {
|
if ( e.getPropertySpaces() != null && e.getPropertySpaces().length > 0 ) {
|
||||||
querySpaces = null;
|
querySpaces = null;
|
||||||
}
|
}
|
||||||
|
@ -170,41 +196,39 @@ public class ExecutableList<E extends Executable & Comparable & Serializable> im
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazily constructs the querySpaces affected by the actions in the list.
|
|
||||||
*
|
|
||||||
* @return the querySpaces affected by the actions in this list
|
|
||||||
*/
|
|
||||||
public Set<Serializable> getQuerySpaces() {
|
|
||||||
if ( querySpaces == null ) {
|
|
||||||
querySpaces = new HashSet<Serializable>();
|
|
||||||
for ( E e : executables ) {
|
|
||||||
Serializable[] propertySpaces = e.getPropertySpaces();
|
|
||||||
if ( querySpaces != null && propertySpaces != null ) {
|
|
||||||
Collections.addAll( querySpaces, propertySpaces );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return querySpaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an Executable to this list.
|
* Add an Executable to this list.
|
||||||
*
|
*
|
||||||
* @param o the executable to add to the list
|
* @param executable the executable to add to the list
|
||||||
|
*
|
||||||
* @return true if the object was added to the list
|
* @return true if the object was added to the list
|
||||||
*/
|
*/
|
||||||
public boolean add(E o) {
|
public boolean add(E executable) {
|
||||||
boolean added = executables.add( o );
|
final E previousLast = sorter != null || executables.isEmpty() ? null : executables.get( executables.size() - 1 );
|
||||||
if ( added ) {
|
boolean added = executables.add( executable );
|
||||||
// no longer sorted
|
|
||||||
|
if ( !added ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if the addition invalidated the sorting
|
||||||
|
if ( sorter != null ) {
|
||||||
|
// we don't have intrinsic insight into the sorter's algorithm, so invalidate sorting
|
||||||
sorted = false;
|
sorted = false;
|
||||||
Serializable[] propertySpaces = o.getPropertySpaces();
|
}
|
||||||
// we can cheaply keep querySpaces in sync once they are cached
|
else {
|
||||||
if ( querySpaces != null && propertySpaces != null ) {
|
// otherwise, we added to the end of the list. So check the comparison between the incoming
|
||||||
Collections.addAll( querySpaces, propertySpaces );
|
// executable and the one previously at the end of the list using the Comparable contract
|
||||||
|
if ( previousLast != null && previousLast.compareTo( executable ) > 0 ) {
|
||||||
|
sorted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Serializable[] querySpaces = executable.getPropertySpaces();
|
||||||
|
if ( this.querySpaces != null && querySpaces != null ) {
|
||||||
|
Collections.addAll( this.querySpaces, querySpaces );
|
||||||
|
}
|
||||||
|
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,42 +277,67 @@ public class ExecutableList<E extends Executable & Comparable & Serializable> im
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the list out to oos.
|
* Write this list out to the given stream as part of serialization
|
||||||
*
|
*
|
||||||
* @param oos The stream to which to serialize our state
|
* @param oos The stream to which to serialize our state
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void writeExternal(ObjectOutput oos) throws IOException {
|
public void writeExternal(ObjectOutput oos) throws IOException {
|
||||||
|
oos.writeBoolean( sorted );
|
||||||
|
|
||||||
oos.writeInt( executables.size() );
|
oos.writeInt( executables.size() );
|
||||||
for ( E e : executables ) {
|
for ( E e : executables ) {
|
||||||
oos.writeObject( e );
|
oos.writeObject( e );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the spaces are initialized, write them out for usage after deserialization
|
||||||
|
if ( querySpaces == null ) {
|
||||||
|
oos.writeInt( -1 );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
oos.writeInt( querySpaces.size() );
|
||||||
|
// these are always String, why we treat them as Serializable instead is beyond me...
|
||||||
|
for ( Serializable querySpace : querySpaces ) {
|
||||||
|
oos.writeUTF( querySpace.toString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* De-serializes the list into this object from in.
|
* Read this object state back in from the given stream as part of de-serialization
|
||||||
*
|
*
|
||||||
* @param in The stream from which to read our serial state
|
* @param in The stream from which to read our serial state
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||||
sorted = false;
|
sorted = in.readBoolean();
|
||||||
querySpaces = null;
|
|
||||||
int size = in.readInt();
|
final int numberOfExecutables = in.readInt();
|
||||||
executables.ensureCapacity( size );
|
executables.ensureCapacity( numberOfExecutables );
|
||||||
if ( size > 0 ) {
|
if ( numberOfExecutables > 0 ) {
|
||||||
for ( int i = 0; i < size; i++ ) {
|
for ( int i = 0; i < numberOfExecutables; i++ ) {
|
||||||
E e = (E) in.readObject();
|
E e = (E) in.readObject();
|
||||||
executables.add( e );
|
executables.add( e );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int numberOfQuerySpaces = in.readInt();
|
||||||
|
if ( numberOfQuerySpaces < 0 ) {
|
||||||
|
this.querySpaces = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
querySpaces = new HashSet<Serializable>( CollectionHelper.determineProperSizing( numberOfQuerySpaces ) );
|
||||||
|
for ( int i = 0; i < numberOfQuerySpaces; i++ ) {
|
||||||
|
querySpaces.add( in.readUTF() );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-attaches the Executable elements to the session after deserialization.
|
* Allow the Executables to re-associate themselves with the Session after deserialization.
|
||||||
*
|
*
|
||||||
* @param session The session to which to attach the Executable elements
|
* @param session The session to which to associate the Executables
|
||||||
*/
|
*/
|
||||||
public void afterDeserialize(SessionImplementor session) {
|
public void afterDeserialize(SessionImplementor session) {
|
||||||
for ( E e : executables ) {
|
for ( E e : executables ) {
|
||||||
|
|
Loading…
Reference in New Issue