Non-functional requirements are important for your analysis and development in order to really gage how a certain function should be handled – especially bulk operations. With bulk operations I simply mean: an amount of actions you call upon a dynamic range of items. For instance, we have a functionality that shows the user a paged list.
Within this list, the first column of each row is a checkbox, making them selectable. At the bottom of the table, you’ll find the button ‘confirm registration’. We can thus select a number of records from the table and confirm the registration in bulk.
The problem, here, is that this is not a scalable solution. During my tests, this worked fine – since I used a limited test dataset on my local machine. After getting feedback from the acceptation tests, however, I came to notice clients actually attempting to confirm thousands of registrations at once. Several things tended to happen:
- They selected such a load that we reached our method timeout on the server.
- Our application monitoring tools did not give a lot of useful information on this either, as everything was entirely clogged into one path.
- Additionally, the users had to wait a really long time before getting any feedback whatsoever; Sometimes clicking away, because it seemed ‘stuck’
- Whenever a validation on one of the validations within the service failed, however, they nicely got an human error message back. Except, they had to start all over again, as the transaction was rolled back.
Obviously, this was not the solution. In a real world scenario, this did not quite work out as intended. So, in a follow-up fix round, we addressed this.
We worked out a different way of handling this, that handles each of these concerns. There is no need for an over-designed additional piece of architecture here, simply a different way of developing this functionality is enough. In fact, this can provide additional benefits.
Wait, why are we doing this? It’s simple, every time you go back to the client, you are provided with an opportunity to show him the change in real-time. By doing this, you will take a slight overall speed hit, but you will instead create a fully scalable tool that can handle any load. As such, this speed hit is only a technical consideration. A user, on the other hand, will notice the application responding a lot quicker, providing him with more feedback. In their eyes, this will actually improve the feeling of performance of your tool.
Do not call all separate AJAX calls at once, as this will create a possible location for your application to drain a lot of resources at once. Call these in batches, a few requests at a time. Your batch size can be a few calls big. When the action is requested, launch the amount items according to your batch size. In the callback functionality of each AJAX request, you can then proceed with an additional call. This will limit the amount of calls for this user to your batch size, but will provide him with a greater speed than a sequential series of calls. Additionally, you keep the batch size under your control – keeping it scalable.
If the order of your calls is important, try making use of an ‘echo’ field in your feedback. The echo field will simply remember the order it was sent in and repeat it back once it returns. This will allow you to determine in the AJAX callbacks what the order of the returned item is.
Using this method, it is also perfectly possible to determine how much the bulk action has progressed. You have your total size, and can easily see which items have already retrieved. Depending on your use case, it might be interesting to show this visually in the table itself (remove rows, change colors) or provide the user with a progress bar (5 out of 126 items handled). Take note that this method still requires the user to wait, so provide him with an AJAX loader icon or a humane message asking him to wait. If they do move away, the bulk action will have progressed up to the part where they moved away, making its behaviour very visual and predictable.
It is also possible to build this as an server-side process, but keep in mind this will make it harder for users to visually see anything happening. Their behaviour might then change from a patiently-waiting behaviour to a constantly-refreshing-the-browser behaviour.