Skip to content

Instantly share code, notes, and snippets.

@brucebolt
Last active September 16, 2025 15:00
Show Gist options
  • Select an option

  • Save brucebolt/54e6050dacb950155fc7fbb0b9ba8219 to your computer and use it in GitHub Desktop.

Select an option

Save brucebolt/54e6050dacb950155fc7fbb0b9ba8219 to your computer and use it in GitHub Desktop.
GraphQL load testing

GraphQL load testing using k6

This repo contains the scripts that were used to run a load test against GOV.UK pages that were converted to use GraphQL.

Files

  • data.json: contains a list of pages to test with their URL and a list of VU (virtual user) numbers to test with.
  • k6.js: the k6 script that runs the actual load test.
  • runner.rb: a wrapper around k6.js which sequentially runs the load test against each example defined in data.json.

Running a load test

  1. Install k6.

    brew install k6
  2. Run the runner script.

    ruby runner.rb
  3. The output will be in ./summaries/data_amalgam.csv.

{
"pages": [
{ "name": "government_response", "basePath": "/government/news/new-child-car-seat-rules-no-change-for-existing-booster-seats" },
{ "name": "ministers_index", "basePath": "/government/ministers" },
{ "name": "news_story", "basePath": "/government/news/government-financial-boost-for-small-and-medium-housebuilders" },
{ "name": "press_release", "basePath": "/government/news/a-very-merry-fishmas-for-south-east-anglers" },
{ "name": "role_1", "basePath": "/government/ministers/prime-minister" },
{ "name": "role_2", "basePath": "/government/ministers/secretary-of-state-for-culture-media-and-sport--3" },
{ "name": "role_3", "basePath": "/government/ministers/captain-of-the-kings-bodyguard-of-the-yeoman-of-the-guard-lords-deputy-chief-whip" },
{ "name": "world_index", "basePath": "/world" },
{ "name": "world_news_story", "basePath": "/government/news/british-high-commission-marks-his-majesty-king-charles-iiis-birthday-with-brilliantly-british-celebrations"}
],
"vus": [10, 25, 50, 100, 200, 400, 800],
"sleepPeriod": 30,
"websiteRoot": "https://www-origin.staging.publishing.service.gov.uk"
}
import http from "k6/http";
import { sleep } from "k6";
const data = JSON.parse(open("./data.json"));
const scenarios = {};
const baseScenarioOptions = {
executor: "per-vu-iterations",
maxDuration: "60s",
iterations: 1,
exec: "govuk_test",
};
for (const page of data.pages) {
for (const vus of data.vus) {
scenarios[`${page.name}_${vus}`] = {
...baseScenarioOptions,
vus,
env: {
GRAPHQL: "false",
BASE_PATH: page.basePath,
WEBSITE_ROOT: data.websiteRoot,
},
};
scenarios[`${page.name}_${vus}_graphql`] = {
...baseScenarioOptions,
vus,
env: {
GRAPHQL: "true",
BASE_PATH: page.basePath,
WEBSITE_ROOT: data.websiteRoot,
},
};
}
}
const { SCENARIO } = __ENV;
const options = {
scenarios: SCENARIO ? { [SCENARIO]: scenarios[SCENARIO] } : scenarios,
};
function govuk_test() {
let url = `${__ENV.WEBSITE_ROOT}${__ENV.BASE_PATH}`;
if (__ENV.GRAPHQL === "true") url += `?graphql=${__ENV.GRAPHQL}`
const res = http.get(url);
sleep(1);
}
export { govuk_test, options };
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'csv'
require 'json'
require 'fileutils'
data = JSON.parse(File.read('data.json'))
page_names = data['pages'].map { |x| x['name'] }
vuss = data['vus']
scenarios = []
page_names.each do |page_name|
vuss.each do |vus|
scenarios << "#{page_name}_#{vus}"
scenarios << "#{page_name}_#{vus}_graphql"
end
end
FileUtils.mkdir_p('summaries')
scenarios.each_with_index do |scenario, index|
puts "\n#{Time.now} Starting scenario: #{scenario}"
`SCENARIO=#{scenario} k6 run k6.js --summary-export summaries/#{scenario}_summary.json`
puts "#{Time.now} Finished scenario: #{scenario}"
sleep(data['sleepPeriod']) unless index == scenarios.size - 1
end
OUTPUT_LOOKUP = {
'request_count' => %w[metrics http_reqs count],
'request_duration_avg' => %w[metrics http_req_duration avg],
'request_duration_p90' => ['metrics', 'http_req_duration', 'p(90)'],
'request_duration_p95' => ['metrics', 'http_req_duration', 'p(95)'],
'failed_request_count' => %w[metrics http_req_failed passes] # this is correct because k6 loves the weird double negative of a failure pass
}.freeze
CSV.open('summaries/data_amalgam.csv', 'w') do |csv|
csv << ['page_name', 'vus', 'graphql?'] + OUTPUT_LOOKUP.keys
page_names.each do |page_name|
vuss.each do |vus|
[false, true].each do |graphql|
filename = if graphql
"summaries/#{page_name}_#{vus}_graphql_summary.json"
else
"summaries/#{page_name}_#{vus}_summary.json"
end
summary_data = JSON.parse(File.read(filename))
csv << [page_name, vus, graphql] +
OUTPUT_LOOKUP.map { |_, dig_path| summary_data.dig(*dig_path) }
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment