Cluster Aware Functions

Introduction

Some Functions take advantage of FNZ Studio's clustering capabilities, but can require locks which degrade performance. This article explains how this mechanism works and how this issue can be avoided.

Locking

Clustering enables FNZ Studio to scale horizontally for high availability. In this setup, FNZ Studio distributes its state and processing across all nodes, with data accessed via a distributed map. This map is shared across all nodes in a cluster.

FNZ Studio therefore implements locking on requested Objects to ensure the integrity of data. Only one end user at a time has write-access to an Object, such as a Process Instance, in FNZ Studio Runtime. That end user is granted a lock on the Object. Only the end user holding the lock can modify the Object.

Once an item is locked, all other users are required to use the read-only view of that specific data. The item is unlocked after a transaction, that is, when the Screen is progressed.

Let's assume a Relationship Manager starts a Process Instance, and the first Screen is a form with fields to complete. After filling out the form, the Relationship Manager clicks Next. Clicking Next is considered a transaction, where the data keyed in is sent to the server, the server processes the data, and moves the token to the next step in the Process.

In the case of a lock not being released automatically, FNZ Studio releases the lock after a specified configured time. This is configured via nm.cluster.lockpool.processinstances.timeout, and has a default value of 120 seconds.

Locking is an important feature of the FNZ Studio platform, and important when doing certain tasks such as modifying a Process Instance. However, locking should be minimized for trivial tasks — when checking e.g. the last modified time of a Process Instance.

Cluster-Aware Functions

To fully support and take advantage of clustering, FNZ Studio introduced cluster-aware functions around these locking mechanisms. Using the cluster-aware functions properly can help improve the performance of your application:

For example, instead of CONTEXT().getWorkflowInstanceService().getWorkflowInstance, consider using getWorkflowInstanceRO if no modification to the Process Instance is required. The getWorkflowInstance Function locks the fetched Process Instance, whereas getWorkflowInstanceRO() is a read-only Function that does not lock the entities involved.

Example: Multiple Locks and Queues

Why is the read-only function described above useful? Let's take a look at an example.

Consider the following scenario:

  • User A clicks on a Screen that uses getWorkflowInstance to modify data for another Process Instance (ID — 12345). This Process Instance is therefore locked by User A.
  • User B clicks on a screen to open Process Instance ID 12345.
  • User B must wait as the Process Instance is locked by User A.

In the scenario above, User B must wait until the lock is released by User A, or released by FNZ Studio after the lock expires. This alone degrades performance, as User B has to wait for the lock to be released. While performance degradation is not overly noticeable in a simple two-person scenario such as this, the impact increases exponentially as more and more users are involved.

Moreover, as the number of users increases in this scenario, so does the number of locks being waited upon. The queue for the lock manager now has to manage the following:

  1. Unlocking a Process Instance when the user releases it (by moving to the next screen, or if the thread executing the Instance finishes).
  2. Force-unlocking a Process Instance when the expiration time is reached (after a user opens a screen which locks a process instance, and just closes the browser window for example).
  3. Assigning locks to waiting users.

The queues which result from multiple locks and multiple users can build up, and lead to locks being mismanaged, In certain circumstances, this can also lead to deadlocks.

Deadlocks can be prevented by ensuring that a Process Instance only works on itself, thereby avoiding locking of other Process Instances that might be used by other users.