Election 2029: Load Testing

I’ve now been through three approaches to load testing for the site, and I think I’ve settled on a solution for the medium term at least. This post explains what I’m trying to achieve, along with the pros and cons of what I’ve tried.

Requirements

For this particular site, I have four requirements:

  • If the site is successful, I want to be able to handle the load as we approach election day. I expect to monitor the traffic from the day the election is announced onwards, and some traffic growth will be reasonably organic – but it’s also possible that it’ll spike significantly. (For example, if it’s mentioned on a popular podcast, that could drive up traffic massively within 24 hours. I want all those new users to have a good experience.)
  • On election night, I want to be ready to handle quite significant load. If word spreads, traffic could really shoot up – and it’ll be on pages (results and live results) that won’t have been active until then.
  • I want to be able to play with the code and see performance impact.
  • I don’t want to spend more money than I absolutely have to in order to meet the previous requirements. Spending money on scaling out due to a lot of real traffic is fine – spending anything more than the price of a cup of coffee each month on load testing (for the next 3+ years) is not, particularly if in some of that time I’m not even working on the site.

To some extent, these are “normal” requirements. The election site having three distinct expected traffic patterns (before the election is called, during the election campaign, and during election night) is slightly unusual, but other sites will have similar requirements around seasonal sales for example. The last requirement is probably the one that separates my site from most sites that are meaningful enough to need load testing… while every company wants to avoid spending money it doesn’t have to, my tolerance for additional cost is very low. A subscription that would be trivial to most companies (e.g. £10 per month) is offputting for me.

Solutions attempted

Home-grown “full request” tooling

Initially, I used a small console app which just cycled through a list of URLs and made HTTP requests, using a given number of tasks in parallel. There was no coordination between multiple hosts etc, and very little in the way of analysis.

This was crude, but did the job to some extent. While running against my laptop hosting the site, I could achieve about 20K qps when running the test code locally… and running the test code on another laptop on the same network, I was able to saturate the local network while my serving laptop stayed mostly idle. I was able to test the staging server (https://staging.election2029.uk) as well, but the limits of my home broadband made it hard to do this effectively it all felt a little too home-grown.

In-site testing

Almost as a detour along the way, I figured that when deploy on Cloud Run, it was tricky to really push the system with real network requests. I thought it might be interesting to have a page you could go to (when enabled, which most of the time it wouldn’t be) that would run a brief (30s or so – which raises its own concerns, of course) load test against the server itself. That would be similar to the “same laptop” testing from the previous solution, but measuring the performance on Cloud Run.

One benefit of this solution was that all those purely-internal requests (on the same machine) don’t cost anything – there’s no network egress, and because they don’t hit the Cloud Run frontend that effectively proxies the requests to my code, they wouldn’t even count as Cloud Run requests.

Again, this worked “okay-ish”. It didn’t really tell me how the site would scale for real traffic though. It did help to convince me (along with existing logs) that the latency of my site is almost entirely in the network. Even under load, requests typically finish in a millisecond or two, even though the end-to-end latency is obviously rather more than that.

Hosting the testing in the site definitely felt like a novelty though… and I was very conscious that I was building testing infrastructure which must be reinventing the wheel. It’s not like I’m the first person ever to want to load test a site hosted in the cloud, right?

Grafana Cloud k6

Doing a bit of research, I came across this handy guide (which was also recent at the time of writing – October 31st 2025) to free load testing tools and services.

I knew I wanted something that was:

  • Free so long as I didn’t use it too much
  • Cloud-based (i.e. I wasn’t making HTTP connections from my home network)
  • Simple to manage
  • Allowed a range of URLs to hit (this knocked out https://loader.io, but I may revisit that)

I ended up using Grafana Cloud k6. After playing around with this for a while, I deleted the code for the first two solutions. It took hardly any time to run the first tests – but a bit longer to figure out how I really wanted to use it. In particular:

  • I wanted the control provided by using real scripts instead of the GUI test builder. This has the side benefit that the test scripts are in source control, too.
  • I wanted HTTP header customization (without duplication)
  • I wanted to be able to easily run the tests locally, against my local machine, as well running k6 cloud to run the tests in the cloud against my staging site

That took a bit longer to set up, but it’s all been pretty pleasant overall, and there’s plenty of scope for future enhancements.

From home, I just run ./test-staging.sh constituency-filtering.js or ./test-local.sh constituency-filtering.js and the right thing happens… it really is very simple.

Just as a note: this is not a sponsored post. As it happens, an engineer from Grafana has reached out personally, but I’m neither angling for nor would accept a discount/comp on a paid subscription. Without having tried a bunch of other options, I’m not explicitly endorsing Grafana k6 as superior to alternatives – but it works for me.

Configuration tweaking

I currently have two scripts. One simulates users visiting each tab of the site, sleeping for a second between each tab. The other performs a bunch of requests against the constituency search API (postcode and constituency name filtering) then sleeps before starting again. The latter test is more interesting to me in terms of running my code – because almost everything in the first test ends up hitting the ASP.NET core cache almost all the time. The constituency filtering test normally hits about 700qps with a reasonably steady 20ms average latency (and a P95 of 58ms). Just for a laugh, I’ve just rerun the test without the sleep at the end… that hit 1200qps, but with higher end-to-end latency, despite the request latency still being reasonably low. This makes me wonder where that latency comes from. I may need to investigate that at some point, possibly testing for longer and allowing Cloud Run to stand up more instances in staging. One of the nice things about Cloud Run is how it’s pretty trivial to change that sort of thing.

Earlier in testing, I found I got high end-to-end latency due to lots of requests being in a state of “pending”. I hadn’t tweaked my Cloud Run configuration, and it was limited to the default of 80 concurrent requests – which I’ve now increased to the maximum of 1000. (And in staging I have a maximum of two instances running.) That configuration change alone has made it worth putting the time into running these tests; without it, Cloud Run would have been potentially started up far more servers in production than are really required… assuming the site actually becomes popular, of course.

The less fun part about earlier testing was finding out that the default HTTP headers from Grafana k6 didn’t include an Accept-Encoding – which meant that various tests served far more data than they normally would in reality. That probably cost me the equivalent of a whole two cups of coffee – scandalous! But again, all of this is good experience, and it’s always useful to become more au fait with the Cloud Run observability graphs etc.

As well as enabling compression, seeing some API calls returning relatively large responses also made me look at exactly what JSON I was returning for a few calls. I’ve managed to reduce that by a factor of about 75% by a combination of avoiding redundant data, and simply rounding of values. (If I’ve got an interpolated line, no-one’s going to care whether a value is 23.14 or 23.148294827263192…)

Conclusion

By the time we get to 2029, I might actually buy a licence for whatever load testing system I’m using at that point (which may very well be Grafana Cloud k6, but I’m not going to assume it will be). I don’t mind paying to do some more intensive load testing – I just don’t want to pay for a subscription for the next three years, if I’m very rarely going to use it. Being able to run relatively small tests at the moment provide me confidence that I’m not doing anything awful at the moment.

Overall, I’m pretty confident that the site will hold up even if I get a sudden rush of visitors (which of course I hope I will). I’m sure that’s not going to happen for another three years, but it’s good to be thinking ahead – and this exercise has both helped the site and taught me more about both Cloud Run configuration and the testing options available. As I add new pages to the site, I expect to add new load tests, particularly for anything that feels like it could be expensive.

Leave a comment