Process Instance Attribute Update

Introduction

When you need to change a Process Instance attribute in all running Processes, simply looping over all Process Instances would acquire Process Instance write locks for each and every Process Instance — and therefore the loop would not finish and/or block everything else in the system.

This article provides three possible ways of achieving your aim for a small number of Process Instances, as well as for a larger number.

WARNING: The use of com.nm.cluster.utils.ClusterUtils.commit($piId);is safe to use for all the solutions outlined below. However, make sure you never call commit($piId) in the middle of handling a regular HTTP request. As well as releasing the write lock, it also releases your reference to the Process Instance itself. Any changes to the Process Instance after the commit ($piId) will be lost.

Simple Loop

This method can be used when you have a small number of Process Instances. Here is how you can loop:

Copy
//
  // loop over all process instances (no write lock is acquired here)
  Integer $count := 0;
  Indexed WorkflowInstance $pisRO := ToIndexed(CONTEXT().getWorkflowInstanceService().getWorkflowInstancesRO(), 'WorkflowInstance');
  ForEach WorkflowInstance $piRO In $pisRO Do
     String $piId := $piRO.getId();
     $count := $count + 1;
     //
     // re-acquire process instance with a write lock
     WorkflowInstance $pi := CONTEXT().getWorkflowInstanceService().getWorkflowInstance($piId);
     //
     // set attribute
     $pi.setAttribute('eyeColor', 'golden');
     //
     // commit change and release write lock
     com.nm.cluster.utils.ClusterUtils.commit($piId);
  End 
  PRINTLN($count & ' process instances updated');

When tested using two nodes, 2000 process instances took 5.6 seconds.

The solution above, however, has one limitation: There could be issues when using it on larger numbers of Process Instances.

Loop for a Large Number of Process Instances

Using the implementation above on a system where you have more than 50,000 Process Instances might cause a 'write-behind queue limit reached' issue, meaning that more than 50,000 write commands are still scheduled to be executed and therefore the write-behind queue has reached its limit. One possibility is increasing the write-behind queue size. A better approach, however, is introducing a slight delay, e.g. waiting 1 second after every 1000 Process Instances.

Here is an example:

Copy
//
  // loop over all process instances (no write lock is acquired here)
  Integer $count := 0;
  Indexed WorkflowInstance $pisRO := ToIndexed(CONTEXT().getWorkflowInstanceService().getWorkflowInstancesRO(), 'WorkflowInstance');
  ForEach WorkflowInstance $piRO In $pisRO Do
     String $piId := $piRO.getId();
     $count := $count + 1;
     //
     // re-acquire process instance with a write lock
     WorkflowInstance $pi := CONTEXT().getWorkflowInstanceService().getWorkflowInstance($piId);
     //
     // set attribute
     $pi.setAttribute('eyeColor', 'golden2');
     //
     // commit change and release write lock
     com.nm.cluster.utils.ClusterUtils.commit($piId);
     //
     // delay execution by 1 second every 1000 process instances
     If $count % 1000 == 0 Then
        PAUSE(1000);
     End
  End 
  PRINTLN($count & ' process instances updated');

When tested using two nodes, 2000 process instances took 8.3 seconds.

This implementation should work on any size of installation. However, it could be improved by making use of two nodes, thus splitting the work for each node, as explained in the section below.

Loop for a Large Number of Process Instances (Two Nodes)

  1. Create a Script Function as follows:

    Copy
    Function UpdateAttributeAllLocalPIs() : String Begin
         //
         // loop over all *local* process instances (no write lock is acquired here)
         Integer $count := 0;
         Indexed String $piIds := ToIndexed(CONTEXT().getWorkflowInstanceService().getLocalWorkflowInstanceIds(), 'String');
         ForEach String $piId In $piIds Do
            $count := $count + 1;
            //
            // acquire process instance with a write lock
            WorkflowInstance $pi := CONTEXT().getWorkflowInstanceService().getWorkflowInstance($piId);
            //
            // set attribute
            $pi.setAttribute('eyeColor', 'golden3');
            //
            // commit change and release write lock
            com.nm.cluster.utils.ClusterUtils.commit($piId);
            //
            // delay execution by 1 second every 1000 process instances
            If $count % 1000 == 0 Then
               PAUSE(1000);
            End
         End 
         Return $count & ' process instances updated';
      End

  2. On any node, call the following Script in the Interactive Script Editor:

    Copy
    Indexed Any $results := ExecuteDistributed(UpdateAttributeAllLocalPIs());
      ForEach Any $result In $results Do
         PRINTLN($result);
      End

    This now executes in 3.7 seconds.

    To make this absolutely bullet-proof, you should add a test after acquiring the Process Instance to test if the Process Instance still exists. This allows you to execute this script while the system is under load.

    Copy
    Function UpdateAttributeAllLocalPIs() : String Begin
         //
         // loop over all *local* process instances (no write lock is acquired here)
         Integer $count := 0;
         Indexed String $piIds := ToIndexed(CONTEXT().getWorkflowInstanceService().getLocalWorkflowInstanceIds(), 'String');
         ForEach String $piId In $piIds Do
            $count := $count + 1;
            //
            // acquire process instance with a write lock (ignore no longer existing process instances)
            WorkflowInstance $pi := CONTEXT().getWorkflowInstanceService().getWorkflowInstance($piId);
            If $pi == null Then
               Continue;
            End
            //
            // set attribute
            $pi.setAttribute('eyeColor', 'golden3');
            //
            // commit change and release write lock
            com.nm.cluster.utils.ClusterUtils.commit($piId);
            //
            // delay execution by 1 second every 1000 process instances
            If $count % 1000 == 0 Then
               PAUSE(1000);
            End
         End 
         Return $count & ' process instances updated';
      End