JADE  Environment Development Ideas

Collection Concurrency Enhancements

Overview

The aim of the proposed feature is to remove accidental complexity so that developers can focus on delivering value to their business.

Objective

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.

Feature Summary

Precursor functionality

    • Conditional collection operations

    • A new form of external method invocation

New Deferred Collection Methods

New collection methods to allow updates to persistent collections to be deferred

Deferred Inverse Maintenance

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

Feature Description

Conditional collection methods

New tryAdd and tryRemove collection methods: abstract methods defined on Collection, implemented by the Array, Set, DynaDictionary, ExtKeyDictionary and MemberKeyDictionary classes.

tryAdd method

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 method

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.

Conditional dictionary methods

New tryPutAtKey, tryRemoveKey and tryRemoveKeyEntry dictionary methods: abstract methods defined on Dictionary, implemented by the DynaDictionary, ExtKeyDictionary and MemberKeyDictionary classes.

tryPutAtKey method

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 method

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 method

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.

Common exception handling

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.

Deferred conditional methods

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 method

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 method

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 method

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 method

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 method

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

Exception handling

During the processing phase

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.

During the commit phase

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.

Deferred Inverse Maintenance

Schema defined property Scope

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

New Process Methods

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 method

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 method

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 method

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.

Extended functionality - under consideration for 2020 release

Cache warming for deferred operations

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.

Proposed enableDeferredExecutionFetchAhead method

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.

FAQ

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

  • Hugh McColl
  • Jul 24 2019
  • Customer Feedback Requested
  • Attach files
  • Admin
    Ashley Bass commented
    July 24, 2019 05:31

    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.

  • Kevin Saul commented
    July 29, 2019 03:16

    Is this the idea Hugh proposed at the TOI?  Could this please be updated with further details of the idea/proposal?

  • Admin
    Hugh McColl commented
    August 02, 2019 04:57

    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.

  • Kevin Saul commented
    August 07, 2019 01:46

    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:

    • Setting key values.
    • Setting properties used in conditions (which may also be a key value or another reference with inverse collection).

    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?

  • Admin
    Hugh McColl commented
    August 07, 2019 09:32
    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?

    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.

  • Kevin Saul commented
    August 08, 2019 01:49

    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?

  • Admin
    Hugh McColl commented
    August 08, 2019 02:21
    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.

    Thanks again for the input Kevin. Description updated to make it clear that scenario is in scope.

  • BeeJay commented
    August 08, 2019 03:45

    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:

     

    if someProposedValue <> self.somePropertyUsedAsMKDKey then
        self.somePropertyUsedAsMKDKey := someProposedValue;
    endif;

     

    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.

  • BeeJay commented
    August 08, 2019 04:57

    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! 

  • Martin Jagers commented
    August 14, 2019 04:17

    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.

  • Admin
    Hugh McColl commented
    August 14, 2019 05:09

    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.

  • John Beaufoy commented
    August 15, 2019 05:50

    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.

  • Admin
    Hugh McColl commented
    August 15, 2019 09:42

    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

    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.

    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.

    And If so, would an exception also be raised from within your critical pre-commit state when the deferred methods are used?

    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.

    Wondering what this might look like from a triage perspective.

    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?

  • John Beaufoy commented
    August 16, 2019 04:08

    Thumbs Up on Apple iOS 12.2

  • Admin
    Hugh McColl commented
    November 11, 2019 01:34

    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.

    • The proposed extensions such as the cache warming capability.
    • Do you envisage any naming clashes with the proposed try<xxx> collection methods?
    • Is this a feature you would consider using?
      • If not why not? Is there some aspect we could add / change to make it more useful or easier to use?