(or - Stop users button mashing while you generate their reports)

Imagine you have a webapp that dynamically generates reports, maybe they're PDFs (mine often are). Sometimes these reports take a while to produce, because they're retrieving and processing a load of data from some huge database.

The last thing we want while this generation work is going on is for the user to either not realise that their request is being handled, or just become impatient and retry the same request. If the user starts button mashing, we have the following problems:

  1. the user is getting frustrated/annoyed/confused by the lack of response and lack of their report
  2. the user's browser is jammed up with multiple long running requests (so the rest of their browsing starts to crawl)
  3. the web server end of those connections get soaked up (so it can't serve other users)
  4. the app server and database get clobbered both in CPU usage generating multiple copies of the same report simultaneously and in memory usage to hold the data

This is clearly a bad thing all round. So what can you do about it?

1. Run faster

What you really want is the report generation and download to be so fast that the user doesn't get time to be frustrated and start button mashing.

Unfortunately this can be difficult to achieve and you'll still get users double-clicking or mashing away for various reasons, so while we want to do all that good optimisation work, we also want to stop the user accidentally (or at least unwittingly) slowing the server down with multiple identical requests.

I don't have any general advice for this - it's a big topic and varies widely depending on your specific situation. Sorry!

2. Client-side blocking

The simplest solution is to hide/disable that troublesome "generate report" button once the user clicks it the first time. Then put it back again once the report has downloaded.

This could easily frustrate our user even more, because we're removing the ability to do one thing we know they want. So if we're going to do this (which is a good idea), some kind of nice "please wait" message is absolutely required.

These days it's usual to accompany the message with some kind of animation (swirly, throbber, Knight Rider swoosh, kitty chasing its tail, whatever). Ideally this would be a real progress bar, but they're notoriously difficult to make accurate (just ask whoever is responsible for Microsoft's file copy dialog box), especially when we're doing something purely on the server-side with no intermediate response steps. HTML5 gives a few more architectural options in that area, but that's another topic.

Once the PDF has been downloaded we need to remove the "please wait" message and animation. We could say "please close this message when the download is complete" or something similar as part of the "please wait" message, but that's pretty clunky and amateurish - surely we can tell when the PDF is there and stop telling the user to wait?

Yes, we can do a bit of nifty cookie based shenanigans - as I describe in Showing and hiding a "please wait" message for slow webapp actions.

3. Server-side blocking

If it's important that the user never triggers more than one generation process at once, then on the server-side we should set a flag when the first generation request comes in. On all subsequent requests, the flag should be checked first, if it's set then a response is sent immediately saying that there is already a report pending (if you don't have anything fancier in mind, try a HTTP 429).

Once the initial request has completed (either successfully or with an error/exception) the flag should be cleared, ready for the user to make another request for the report later on. It's probably also a good idea to have some kind of time limit after which the flag gets cleared automatically and an alert gets sent - to handle any situations where you've cocked up the flag clearing logic.

If you need this kind of solution, combine it with the client-side blocking plus an extra bit of handling for any "report pending" responses. Otherwise we're still not giving the user any feedback that something slow is going on.

The implementation approach for this depends on what kind of web/app server architecture you're using. I may write a separate article about that at some point. If I do, it'll probably be called something like Throttling access to long running webapp actions

4. Do it beforehand

Arguably an even better solution than "Run faster" is to run at whatever speed you like, but finish before the user even asks for the report. We're talking about batch processing here - doing all that slow report generation at some quiet time, at a controlled pace, maybe on some other server(s). The resulting reports pile up where the webapp can find them. When the user request comes in the webapp picks up the right report and dumps it back down the response pipe; nice and fast with very low server overhead.

So if this is an option (and it often is), definitely go for it. It's still a good idea to do that "Run faster" optimisation work on the report generation, but at least with this solution the users aren't trying to kill your servers at the same time. One downside of this is that your hefty batch processing engine may generate a whole bunch of reports which the users don't even have the decency to download. So you'll be needing some way to keep your pile of pre-generated reports tidy and work out when you can throw away the unwanted ones. But that's part of any reasonable batch processing system.

Depending on the size of the reports being thrown around, the client-side blocking could still be useful, because networks can be slow and users can be quick to start their mashing.

As with "Run faster" the way you set up your batch processing depends largely on your specific needs, so again I don't have any code-heavy article to link to, sorry!

Previous Post Next Post

© Me. Best viewed with a sense of humour and a beer in hand.