The JADE 2022 release meets tomorrow’s business demands.
The aim of the proposed feature is to remove accidental complexity so that developers can focus on delivering value to their business.
To make it easier for developers to write code that maintains persistent Collections either directly or as a side-effect of inverse maintenance in a manner that minimises contention and avoids deadlocks.
Conditional collection operations
A new form of external method invocation
New collection methods to allow updates to persistent collections to be deferred
A new deferred execution strategy for automatically maintained multi-valued inverses with two ways to control its scope
Schema defined per property scope
Runtime execution per process scope - overrides property scope for the process
The deferred execution strategy will be used to action all state changes that currently trigger collection inverse maintenance, including:
Assigning or changing the manual single value property
Setting or changing key values for an auto inverse member key dictionary
Changing the values of properties used in a condition specified as a constraint on the multi-valued inverse
New tryAdd and tryRemove collection methods: abstract methods defined on Collection, implemented by the Array, Set, DynaDictionary, ExtKeyDictionary and MemberKeyDictionary classes.
tryAdd(value: Membertype): Boolean, abstract, updating;
Attempts to add the value specified by the value parameter to the collection. Returns true if the value was successfully added; returns false if the collection already contains the value.
Exception handling
Member key dictionaries with a no-duplicates constraint will raise a duplicated_key (1310) exception when the collection already contains the member key(s) with a different value. Reason: this is not a case of adding the same object again; it is an attempt to add a different object that conflicts with an existing entry.
tryRemove(value: Membertype): Boolean, abstract, updating;
Attempts to remove the specified value from the collection. Returns true if the value was successfully removed; returns false if the collection does not contain the value.
New tryPutAtKey, tryRemoveKey and tryRemoveKeyEntry dictionary methods: abstract methods defined on Dictionary, implemented by the DynaDictionary, ExtKeyDictionary and MemberKeyDictionary classes.
tryPutAtKey(keys: KeyType; value: MemberType): Boolean, abstract,
lockReceiver, updating;
Attempts to add the specified (key, value) pair to the dictionary. Returns true if the (key, value) pair was successfully added; returns false if the dictionary already contains the (key, value) pair.
Exception handling
Dictionaries with a no-duplicates constraint will raise a duplicated_key (1310) exception when the collection already contains the member key(s) with a different value.
tryRemoveKey(keys: KeyType): MemberType, abstract, lockReceiver, updating;
Attempts to remove a single (key, value) pair with the specified key(s) from the dictionary. Returns the member value if a single (key, value) pair was successfully removed; returns a null if the dictionary does not contain the specified key (note: no subclass of RootSchema::Dictionary allows the insertion of a null object reference).
tryRemoveKeyEntry(keys: KeyType; value: MemberType): Boolean, abstract,
lockReceiver, updating;
Attempts to remove the specified (key, value) pair from the dictionary. Returns true if the (key, value) pair was successfully removed; returns false if the dictionary does not contain the specified (key, value) pair.
The conditional collection and dictionary methods will do all the precondition checks on the parameters as their existing non-conditional counterparts do. When a precondition check fails, an exception will be raised. The methods will only return false in the contains-on-add or not contains-on-remove cases.
New deferred conditional collection methods declared abstract at the Collection or Dictionary level with specific implementations for different collection types. These methods will be supported for collection types that can contain objects (they will not be supported for primitive arrays) and for all collection instance lifetimes. Deferred execution behaviour will only be observed for persistent collections.
The following summarises the proposed deferred conditional methods and their expected behaviour.
tryAddDeferred(value: Membertype): Boolean, abstract,
receiverByReference, updating;
If the receiver has a persistent lifetime:
The receiver is not fetched or locked
A request to execute a tryAdd is queued and the method returns true
The queued tryAdd operation is executed when the transaction successfully commits
If the receiver has a non-persistent lifetime:
The method is executed directly by calling tryAdd
tryRemoveDeferred(value: Membertype): Boolean, abstract,
receiverByReference, updating;
If the receiver has a persistent lifetime:
The receiver is not fetched or locked
A request to execute a tryRemove is queued and the method returns true
The queued tryRemove operation is executed when the transaction successfully commits
If the receiver has a non-persistent lifetime:
The method is executed directly by calling tryRemove
tryPutAtKeyDeferred(keys: KeyType; value: Membertype): Boolean, abstract,
receiverByReference, updating;
If the receiver has a persistent lifetime:
The receiver is not fetched or locked
A request to execute a tryPutAtKey is queued and the method returns true
The queued tryPutAtKey operation is executed when the transaction successfully commits
If the receiver has a non-persistent lifetime:
The method is executed directly by calling tryPutAtKey
tryRemoveKeyDeferred(keys: KeyType): MemberType, abstract,
receiverByReference, updating;
If the receiver has a persistent lifetime:
The receiver is not fetched or locked
A request to execute a tryRemoveKey is queued and the method returns true
The queued tryRemoveKey operation is executed when the transaction successfully commits
If the receiver has a non-persistent lifetime:
The method is executed directly by calling tryRemoveKey
tryRemoveKeyEntryDeferred(keys: KeyType; value: MemberType): Boolean, abstract,
receiverByReference, updating;
If the receiver has a persistent lifetime:
The receiver is not fetched or locked
A request to execute a tryRemoveKeyEntry is queued and the method returns true
The queued tryRemoveKeyEntry operation is executed when the transaction successfully commits
If the receiver has a non-persistent lifetime:
The method is executed directly by calling tryRemoveKeyEntry
When invoked in the processing phase of a transaction, the deferred conditional collection and dictionary methods will do all the precondition checks on the parameters as their existing non-deferred counterparts do. When a precondition check fails, an exception will be raised.
Precondition Checks: Precondition checks will be repeated when deferred operations are applied in the commit phase. The likelihood of the precondition checks failing in commit should be low. For example: with member key dictionaries, it won't be possible to encounter any parameter precondition check failure because the object being added is locked (current behaviour) and in the remove case, we will not require that the object being removed exists.
Unlike update locks: there is no window where an acquired share lock is dropped and a new exclusive lock is acquired, which means deferred updates are not susceptible to intervening update (1146) exceptions.
Like update locks: deferred operations must acquire an exclusive lock on the target collection prior to committing, which means Object Locked and Deadlock exceptions can still occur. How these are handled by the application will be pretty much the same as update lock scenarios.
This covers the ability to specify that a collection inverse is maintained using a deferred execution strategy at an individual property level. Deferred execution is applicable to the existing automatic and manualAutomatic update modes resulting in two new update modes:
automaticDeferred
manualAutomaticDeferred
Process instance methods will be provided to support enabling or disabling deferred execution for all automatic or manualAutomatic inverse collection properties for the current process.
useDeferredInverseMaintenanceStrategy(enable: Boolean): Boolean,
updating;
When called with enable set to true: enables the use of a deferred execution strategy for all automatically maintained collection properties for the current process, overriding the per property execution strategy. When called with enable set to false: restores the schema defined per property behaviour. Returns the value of the prior enabled state.
Use case
Evaluating, testing, benchmarking the impact of using a deferred execution strategy before permanently applying its use in the schema.
overrideDeferredInverseMaintenanceStrategy(disable: Boolean): Boolean, updating;
When the called with disable set to true: disables the use of a deferred execution strategy for all automatically maintained collection properties for the current process, overriding the per property execution strategy. When called with disable set to false: restores the schema defined per property behaviour. Returns the value of the prior disabled state.
Use case
Deferred execution has been specified at the property level in the schema because this was deemed to be appropriate for standard online processing. However, there is also a need to avoid the impact (particularly memory consumption) from a batch processing or bulk data load workload that is known to generate a large number of collection updates. The overrideDeferredInverseMaintenanceStrategy method provides a means to disable the schema defined behaviour for the duration of the batch or bulk load execution window.
useDeferredStrategyForManualCollectionOperations(enable: Boolean): Boolean, updating;
When called with enable set to true: enables the use of a deferred execution strategy for all manually maintained collection operations for the current process. When called with enable set to false: disables deferred execution strategy. Returns the value of the prior enabled state.
Use case
Evaluating, testing, benchmarking the impact of using a deferred execution strategy before applying it permanently in code.
A mechanism that does a synchronous fetch ahead of collection blocks without locking the collection. Fetch ahead will be triggered when tryXXXDeferred methods are called in the processing phase of a transaction. The goal is to cause the process to wait on any necessary network / disk I/Os during the processing phase i.e. before locks are acquired in the commit phase in order to remove I/O wait time from the critical region after locks are acquired. Waiting for I/Os before locks are acquired serves to reduce the contention window hence improving concurrency.
enableDeferredExecutionFetchAhead(enable: Boolean): Boolean,
updating;
When called with enable set to true: enables a cache warming strategy that fetches the collection blocks that will be accessed by the deferred update operation into client cache without locking the collection.
Q: What are the main considerations in deciding between use of Update Locks and the new deferred execution methods?
A: The deferred execution model is a good choice when applied to collections that are updated but not read within the transaction. Here are some pros and cons to consider:
Pros
The proposed methods can be called at any point within the transaction and since a deferred execution does not lock the collection at all, it means multiple processes can execute the deferred operations concurrently. Whereas only one process at a time can hold an update lock on a given collection
If application logic does not read the collection in the updating transaction, then a shared to exclusive lock upgrade does not happen
Con
The deferred add and remove operations are not visible to the calling process until after the enclosing transaction has committed
A presentation on collection concurrency improvements coming 2020 and benchmark results available here.
Development of this feature is progressing well. For example, all the public RootSchema methods have been implemented. The description has been updated to reflect current development status; however, it is still not too late to change certain things.
We are interested in feedback on the following items.
Thanks for your comments John and great questions. Here are some answers based on the current design, some aspects covered in the Q&A remain subject to change:
On conditional method exception handling
Yes indeed. The conditional methods will do all the precondition checks on the value parameter as their existing non-conditional counterparts do. When a check fails, an exception is raised. The methods will only return false in the "contains on add" or "not contains on remove" cases. That means: an attempt to add an invalid object reference will raise an exception; however, there is no plan to check that the receiver is locked.
On deferred conditional method exception handling
When invoked in the processing phase of a transaction, the deferred methods will do all the same precondition checks up front. These checks are repeated when the deferred operations are applied in the commit phase; however, the likelihood of the precondition checks failing in commit should be fairly low. For example: with member key dictionaries, it won't be possible to encounter any parameter precondition check failure because the object being added is locked and in the remove case, we won't require that the object being removed exists.
We are also considering several approaches to either prevent the deletion or handle the deletion of the receiver between invocation and deferred execution. I won’t describe these yet, because the preferred approach hasn’t been decided.
While we are aiming to minimise both the kinds of exceptions and the likelihood of them occurring in the commit phase, some exceptions are still possible.
Unlike update locks there is no window where an acquired share lock is dropped and a new exclusive lock is acquired, which means deferred updates are not susceptible to an Intervening Update (1146) exception. However, as with update locks, deferred operations must acquire an exclusive lock on the target collection prior to committing, which means Object Locked and Deadlock exceptions could still occur. There is strategy to avoid deadlocks happening on collections that are only updated and locked by deferred operations, but we cannot prevent deadlocks that are a result of interplay with locks acquired by the application.
What do you think based on the answers above?
Is there anything covered in the above responses that you would like to see handled differently?
Any other concerns about error handling and potential edge cases?
Great idea Hugh, I’m actually quite surprised this doesn’t have a much higher vote count!
Kudos too for taking in feedback from the community on the implementation detail.
Will the conditional methods raise an exception rather than return false in the event of any other issue? Examples being if an invalid object was added or where the collection is manually locked by another process.
And If so, would an exception also be raised from within your critical pre-commit state when the deferred methods are used?
Wondering what this might look like from a triage perspective.
Thanks for your input Martyn. Specifying that automatic inverse maintenance is deferred is a key part of the feature. I have updated the description to promote that aspect.
Totally on board with Kevin's comments around automatic inverse maintenance.
Most of our updates to collections would be through automatic inverse maintenance where a new item would have its manual reference updated and associated collection(s) automatically updated. And similarly when an item is deleted then automatic inverse maintenance kicks in to delete the item from one or more collections.
Being able to 'defer' these and not having to lock collections would eliminate most deadlocks and reduce lock contentions and lock exceptions when our retry mechanism times out.
A little birdy just pointed out to me that this optimisation was already implemented in Jade 2018 via Parsys 63315. A whole lot of boiler plate code just became superfluous!
Hi Hugh,
One of the other common pieces of code we have to write is the following, to not set the value of properties used as keys unless the 'proposed' value is different to the current value to avoid remove/add operations on the automatic inverse collections:
Will the proposed deferred collection update changes also detect that there is actually no update required when the value hasn't actually changed, to avoid the need to write this type of protective boiler plate code around the Clayton's updates of properties used in MKDs?
Cheers,
BeeJay.
Thanks again for the input Kevin. Description updated to make it clear that scenario is in scope.
Would this also include objects being deleted? (which'll trigger automatic removals). This is another scenario we currently handle by deferring the deletion of the object in general.
Ideally, deferred inverse maintenance would also allow us to delete objects upfront (and any children) without locking contentious collections they need to be removed from. However, I'm wondering if this would be difficult to handle in a deferred manner as finding/removing an object may depend on the object still existing in order to get any member key values?
Excellent question Kevin.
I can confirm the intent is to defer the automatic maintenance and hence locking of the auto inverse collection for most scenarios that trigger inverse collection maintenance. Scenarios to be confirmed. I have updated the description to make that clear.
Hi Hugh,
We've a similar mechanism we use to manage critical updates, which are deferred to the end of the transaction (as best we can depending on the context).
One of the biggest advantages I see this providing is the ability to set the manual side of an inverse, where we currently have to defer setting that altogether (so have to be wary not to rely on it being set, etc).
In addition to deferring how references are set to avoid the automatic inverse maintenance (and co-ordinate a similar locking strategy), we also defer the following:
Can you confirm whether these scenarios would also be supported by the Proposed Additional Functionality (second point), in addition to setting/changing a single-value reference?
Hi Kevin, as you guessed this is the idea I presented in the TOI workshops.
I have updated the description with an overview and feature summary to help developers understand and evaluate the proposal.
We are very interested in comments, questions and suggestion related to this feature and would also like to know if there are any applications around that have Collection methods using the proposed tryXXXX naming convention.
Is this the idea Hugh proposed at the TOI? Could this please be updated with further details of the idea/proposal?
We are adding features to JEDI that have been originated through conversation with customers or internal initiatives but not been submitted as a JEDI idea. These are features that are currently under investigation and/or design for inclusion in the JADE 2020 roadmap.