High Frequency Updates allow large numbers of updates against the grid without having a drastic hit on performance.
When a transaction is applied to the grid, it results in the grid re-rendering its rows to cater for the new values. In addition to this, if the transactions are getting applied in different JavaScript VM turns (which is often the case when data updates are streamed), each VM turn will result in a browser redraw. If you are receiving tens of updates a second, this will probably kill your application's performance, hence the need for Async Transactions.
Grid grid caters for High Frequency Updates via Async Transactions.
When a Transaction is applied to the grid using Async Transactions, the transaction is not applied immediately. Rather the grid waits for a period for more transactions to be applied and then applies them all together in one go, resulting in just one redraw for all the Transactions.
The amount of time which the grid waits before applying the transaction is set via the grid property asyncTransactionWaitMillis and defaults to 50ms. Transactions are also applied after any rows are loaded.
The transaction interfaces ServerSideTransaction and ServerSideTransactionResult used in SSRM Transactions are used again for Async Transactions.
// Standard Sync for regular updates
public applyServerSideTransaction(
transaction: ServerSideTransaction
) : ServerSideTransactionResult;
// Async apply for High Frequency Updates
public applyServerSideTransactionAsync(
transaction: ServerSideTransaction,
callback?: (res: ServerSideTransactionResult) => void
): void;Below shows a simple example using Async Transactions. Note the following:
asyncTransactionWaitMillis = 4000. This makes the grid wait 4 seconds before applying Async Transactions.When Async Transactions are applied, the asyncTransactionsFlushed event is fired. The event contains all of the ServerSideTransactionResult objects of all attempted transactions.
Properties available on the AsyncTransactionsFlushed<TData = any> interface.
resultsServerSideTransactionResult. For Client-Side Row Model it's a list of RowNodeTransaction. results: (RowNodeTransaction<TData> | ServerSideTransactionResult)[];
interface RowNodeTransaction<TData = any> {
// Row nodes added
add: RowNode<TData>[];
// Row nodes removed
remove: RowNode<TData>[];
// Row nodes updated
update: RowNode<TData>[];
}
interface ServerSideTransactionResult {
// The status of applying the transaction.
status: ServerSideTransactionResultStatus;
// If rows were added, the newly created Row Nodes for those rows.
add?: RowNode[];
// If rows were removed, the deleted Row Nodes.
remove?: RowNode[];
// If rows were updated, the updated Row Nodes.
update?: RowNode[];
}
enum ServerSideTransactionResultStatus {
// Transaction was successfully applied
Applied = 'Applied'
// Store was not found, transaction not applied.
// Either invalid route, or the parent row has not yet been expanded.
StoreNotFound = 'StoreNotFound'
// Store is loading, transaction not applied.
StoreLoading = 'StoreLoading'
// Store is loading (as max loads exceeded), transaction not applied.
StoreWaitingToLoad = 'StoreWaitingToLoad'
// Store load attempt failed, transaction not applied.
StoreLoadingFailed = 'StoreLoadingFailed'
// Store is type Partial, which doesn't accept transactions
StoreWrongType = 'StoreWrongType'
// Transaction was cancelled, due to grid.
// Callback isApplyServerSideTransaction() returning false
Cancelled = 'Cancelled'
}apicolumnApitypeThe example below listens for this event and prints a summary of the result objects to the console.
The Retrying Transactions feature guards against Lost Updates. Lost updates refers to new data created in the server's data store that due to the order of data getting read and the transaction applied, the record ends up missing in the grid.
Lost updates occur when data read from the server is missing a record (as it's read to early), but the transaction for the new record is attempted before the grid finishes loading (causing transaction to be discarded).
When the grid is loading a particular level and a transaction is applied asynchronously to that level, the grid will wait for the load and then apply the transaction.
The transactions will get applied to the grid's level in the order they were provided to the grid. The order will not get mixed up due to retrying. However transactions applied to other levels (e.g. grouping is active and other levels have loaded) will go ahead as normal. Loading only delays transactions for the loading level.
The example is configured to demonstrate this. Note the following:
This only applies to loading levels. If the grid is limiting the number of concurrent loads (property maxConcurrentDatasourceRequests is set) then it's possible levels are waiting to load. If they are waiting to load, transactions for updates will all be discarded as no race condition (see below) is possible.
The Cancelling Transactions feature guards against Duplicate Records. Duplicate records is the inverse of Lost Update. It results in records duplicating.
Duplicate records occur when data read from the server includes a new record (it's just in time), but the transaction for the new record is attempted after the level finishes loading (transaction is applied). This results in the record appearing twice.
Before a transaction is applied, the grid calls the isApplyServerSideTransaction(params) callback to give the application one last chance to cancel the transaction.
| Allows cancelling transactions. |
If the callback returns true, the transaction is applied as normal and the Transaction Status Applied is returned. If the callback returns false, the transaction is discarded and the Transaction Status Cancelled is returned.
The suggested mechanism is to use versioned (or timestamped) data. When row data is loaded, the application could provide a data version as Group Level Info.
The example is configured to demonstrate this. Note the following:
Race conditions occur because of the asynchronous nature of loading data and applying transactions. Race conditions result in lost updates and duplicated records.
Lost updates are catered for by the grid by retrying transactions when loading completes explained in Retry Transactions above.
Duplicate records need to be catered for by your application using the Cancelling Transactions feature explained above.
The above explains all the finer details of using Async Transactions. Below presents an example bringing it all together with a larger dataset, grouping and streaming updates from the server.
The example presents a simplified trading hierarchy, typically found inside a financial institution. The example data initially has 28 products, with each product containing 5 portfolios each, each portfolio containing 5 books and each book containing 5 trades. So the data has 28 products, 150 portfolios, 700 books and 3,500 trades. This data size is small comparable to what the grid can handle, or what's typical for large financial institutions, however it's kept to moderate size as the server is mocked in the example.
As far as the grid is concerned, it is lazy loading data based on what groups the user has expanded, so it doesn't matter from the grids perspective how big the dataset is on the server.
In theory there is no limit to the number of groupings or data size allowed. It's common for financial institutions to use the grid to show trading hierarchies with 10 or more levels in the hierarchy with 60,000 to 100,000 books.
In the example, note the following:
asyncTransactionWaitMillis = 500, which means all Async Transactions will get applied after 500ms. In applications, a lower number would typically be used to give more instant feedback to the user. However this example slows it down to save clutter in the dev console and make the example easier to follow.isApplyServerSideTransaction(params) is implemented to discard old transactions.Continue to the next section to learn how to do Load Retry.