You are viewing the documentation for Blueriq 14. Documentation for other versions is available in our documentation directory.
Introduction
When running on multiple nodes without concurrency control it is possible for the session to become desynchronized during a failover event. Consider the following scenario, where the user (browser) is on Page 1 of an application:
Step | Actor | Description |
---|---|---|
1. | User / Browser | The User clicks a button on Page 1. This should cause the application to flow to Page 2. |
2. | Load Balancer | The Load Balancer sends the request to Runtime 1. |
3. | Load Balancer | Because Runtime 1 takes a long time to respond, the load balancer decides the failover to Runtime 2 |
4. | Runtime 2 | Runtime 2 successfully handles the request, flows to Page 2 and stores the session state in the key-value store (Redis in this example) |
5. | Runtime 2 | Runtime 2 sends the success response to the Load Balancer |
6. | Load Balancer | The Load Balancer sends the success response to the User / Browser. The User now sees Page 2. |
7. | User / Browser | The User clicks a button on Page 2. This should cause the application to flow to Page 3. |
8. | Load Balancer | The Load Balancer sends the request to Runtime 2. |
9. | Runtime 2 | Runtime 2 successfully handles the request, flows to Page 3 and stores the session state in the key-value store |
10. | Runtime 2 | Runtime 2 sends the success response to the Load Balancer |
11. | Load Balancer | The Load Balancer sends the success response to the Browser / User. The User now sees Page 3. |
12. | Runtime 1 | Runtime 1 finishes handling the request received at step 2, flows to Page 2 and writes the session state in the key-value store. |
After step 12. there is a discrepancy between what the user sees (Page 3) and the state of the session in the key-value store (Page 2). Additionally, all data entered by the user at step 7. is lost, as the profile written in the key-value store at step 9. is overwritten at step 12. The problem is further compounded if the flow from Page 2 to Page 3 called an external web service.
Another symptom of the desynchronized session is that the Runtime ignores all further events from the user. The user is clicking buttons on Page 3, but the session is actually on Page 2. In order to re-synchronize the state of the session with the front-end, the user needs to refresh the page.
Clearly, this scenario presents a problem. Blueriq Runtime addresses this multi-node concurrency problem by using keyspace notifications and interruptible sessions.
Keyspace Notifications
Keyspace notifications are a feature of the key-value store. The key-value store clients (the Runtime nodes in this case) subscribe for notifications for a key or key pattern. The key-value store then notifies its clients when a change occurs for a subscribed key.
When using Redis, keyspace notifications may be enabled using the following command:
config set notify-keyspace-events g$xKE
In order to enable keyspace notifications across restarts of the Redis instance, the setting should be made in the Redis configuration file, or the current configuration must be saved using the "config rewrite" command.
For possible values for the notify-keyspace-events configuration option and for more detailed information about Redis keyspace notifications, please see Redis Keyspace Notifications.
The values for this configuration option are used for the following purposes:
Option | Used By | Used For |
---|---|---|
g | Spring Session | Spring Session uses this setting, among other things, to detect when an HTTP session is manually deleted. |
$ | Blueriq | Blueriq uses this for concurrency control on multiple runtimes. |
x | Spring Session | Spring Session uses this setting to detect when an HTTP session expires. |
K | Blueriq | Blueriq uses keyspace notifications. That is, it can detect operations on a specific key or set of keys which match a pattern. This is used for concurrency control on multiple runtimes. |
E | Spring Session | Spring Session uses keyevent notifications. That is, it can detect keys affected by a specific operation. |
Interruptible Sessions and Flows
Interruptible sessions and flows are a new feature in Blueriq 11. The IAquimaSession and IPortalSession interfaces now extend the Interruptible interface. Each session has an interrupted flag which is not set at the beginning of each request. During the handling of the request, it is possible to interrupt the session, which sets the interrupted flag. The Flow Engine checks the interrupted flag before and after each service call or function call node in the flow. If the session is interrupted, the Flow Engine will not continue the flow and exit with an exception.
Please note that interrupting the flow does not cause a service call or function call to take the exception exit. Interrupting a session is intended to indicate an abnormal situation in that session instance. All further processing should stop and any state modifications should not be persisted. For this reason, the session manager also checks the interrupted flag and will not save interrupted sessions in the key-value store.
Concurrency Control using Keyspace Notifications and Interruptible Sessions
Blueriq Runtime uses keyspace notifications in combination with interruptible sessions in order to prevent a slow Runtime from overwriting the session state. Each request which may modify the session registers with a monitor component within the Runtime. The monitor writes a "guard" key in the key-value store. The guard identifies each session via the session ID and each request through a randomly generated GUID. The monitor also subscribes for keyspace notifications using a key pattern which matches all guards, therefore it is notified when a guard is written.
When the monitor receives a notification that a new guard was written, it can identify the session and request which caused that guard to be written. It then interrupts all registered sessions which did not register on that request. The following diagram illustrates this process:
Step | Actor | Description |
---|---|---|
1. | User / Browser | The user clicks a button on Page 1 of the application. |
2. | Load Balancer | The Load Balancer sends the request to Runtime 1. |
3. | Runtime 1 | Before the event handling begins, the session and request are registered with the monitor component. The monitor component generates a GUID to represent the request, registers the session/request pair for the duration of the request and writes a guard key in the key-value store (which is composed from a namespace, session id and the request GUID). |
4. | Redis | Redis notifies Runtime 1 that a guard has been written. Runtime 1 has a single session and request pair registered, and the request GUID of the request matches the request GUID of the guard. Therefore, this does not represent a concurrent modification and no action is taken. |
5. | Redis | Redis notifies Runtime 2 that a guard has been written. Runtime 2 has no session/request pairs registered at this time, so no action is taken. |
6. | Load Balancer | Since Runtime 1 takes too long to respond, the Load Balancer decides to failover to Runtime 2 and resends the request to Runtime 2. |
7. | Runtime 2 | Before the event handling begins, the session and request are registered with the monitor component. The monitor component generates a GUID to represent the request, registers the session/request pair for the duration of the request and writes a guard key in the key-value store. |
8. | Redis | Redis notifies Runtime 1 that a guard has been written. |
9. | Redis | Redis notifies Runtime 2 that a guard has been written. The session ID and request GUID from the guard match those of the registered session/request pair, so no action is taken. |
10. | Runtime 1 | Runtime 1 has a single session and request pair registered. The session ID from the guard matches the session ID of the registered session/request pair, but the request GUID does not match. This indicates that while the request started at step 2 is still executing, another request was started for the same session (potentially on another node, but it may be the same node as well). The monitor component on Runtime 1 interrupts the registered session. This sets the interrupted flag |
11 | Runtime 1 | As soon as the Flow Engine exits the current service call or function call node (or as soon as entering one), it detects that the interrupted flag is set and exits with an exception. An error response is sent to the Load Balancer. The Load Balancer does not forward this response back to the user. |
12. | Runtime 2 | Runtime 2 finishes handling the event, flows to Page 2 and saves the session state in the key-value store. |
13. | Runtime 2 | Runtime 2 sends a success response to the Load Balancer. |
14. | Load Balancer | The Load Balancer sends the success reponse to the User / Browser. |
How a request which may modify the session is identified
Any request of type POST, PUT, PATCH or DELETE which is targeted at a controller with a session ID parameter annotated with @AquimaSessionId is considered as a request that can potentially modify the session state. Plugin developers who wish to benefit from concurrency control for their custom controllers should use only POST, PUT, PATCH or DELETE request methods for requests which modify the session state.
How to use concurrency control with other key-value stores
When using a key-value store other than Redis, the IKeyValueStore interface must be implemented and exposed as a Spring managed bean. In order to provide concurrency control an additional interface, IKeyspaceMonitor, must also be implemented and exposed as a Spring managed bean. The concurrency control component within the Runtime will use the provided implementations for IKeyValueStore and IKeyspaceMonitor to register for keyspace notifications and write guards in the key-value store.
Thread Pools and Configuration Options
The default implementation in the Blueriq Redis Key-Value Store Component uses 2 thread pools:
- the subscription thread pool is used when subscribing for notifications for a specific key or key pattern
- the task thread pool is used for dispatching notifications
See 3. Key-value store API and default component for details on how to configure these thread pools . The default configuration used is:
- Subscriptions use a CACHED thread pool. The Runtime subscribes for a key pattern only once, at startup. If the keyspace notifications API is used more often, the thread pool type may be changed to FIXED.
- Notifications use a FIXED thread pool with 4 threads. Each session modification request causes a guard to be written, which in turn triggers a notification on all Runtime nodes. The amount of work done for each notification is low. Depending on load, the default pool size and type may be adjusted as needed.