Skip to content

Commit 1fb4d5a

Browse files
authored
Merge pull request #14333 from guardian/jlk/new-ab-testing-definitions
Fastly Dictionary AB testing configuration and associated scripts
2 parents 33c0a91 + f77215c commit 1fb4d5a

37 files changed

+3471
-18
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Checks
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_call:
8+
secrets:
9+
FASTLY_AB_TESTING_CONFIG:
10+
required: true
11+
FASTLY_API_TOKEN:
12+
required: true
13+
14+
jobs:
15+
checks:
16+
runs-on: ubuntu-latest
17+
defaults:
18+
run:
19+
working-directory: ab-testing
20+
env:
21+
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_AB_TESTING_CONFIG }}
22+
FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
# https://github.com/denoland/setup-deno#latest-stable-for-a-major
27+
- uses: denoland/setup-deno@v1
28+
with:
29+
deno-version: v2.3
30+
31+
- name: Test
32+
run: deno test
33+
34+
- name: Validate
35+
run: deno run scripts/validation/index.ts
36+
37+
- name: Build
38+
run: deno task build
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: 🧪 AB testing
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
paths:
9+
- 'ab-testing/**'
10+
11+
jobs:
12+
checks:
13+
uses: ./.github/workflows/ab-testing-checks.yml
14+
secrets:
15+
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_CODE_AB_TESTING_CONFIG }}
16+
FASTLY_API_TOKEN: ${{ secrets.FASTLY_CODE_API_TOKEN }}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: 🧪 Deploy CODE AB Testing Config
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_dispatch:
8+
9+
jobs:
10+
ci:
11+
uses: ./.github/workflows/ab-testing-checks.yml
12+
secrets:
13+
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_CODE_AB_TESTING_CONFIG }}
14+
FASTLY_API_TOKEN: ${{ secrets.FASTLY_CODE_API_TOKEN }}
15+
16+
deploy:
17+
name: Deploy CODE
18+
needs: ci
19+
runs-on: ubuntu-latest
20+
defaults:
21+
run:
22+
working-directory: ab-testing
23+
steps:
24+
- name: Deploy
25+
uses: ./.github/workflows/ab-testing-deploy.yml
26+
with:
27+
stage: code
28+
env:
29+
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_CODE_AB_TESTING_CONFIG }}
30+
FASTLY_API_TOKEN: ${{ secrets.FASTLY_CODE_API_TOKEN }}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: 🧪 Deploy PROD AB Testing Config
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_dispatch:
8+
# push:
9+
# branches:
10+
# - main
11+
# paths:
12+
# - 'ab-testing/**'
13+
14+
jobs:
15+
ci:
16+
uses: ./.github/workflows/ab-testing-checks.yml
17+
secrets:
18+
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_PROD_AB_TESTING_CONFIG }}
19+
FASTLY_API_TOKEN: ${{ secrets.FASTLY_PROD_API_TOKEN }}
20+
deploy:
21+
name: Deploy PROD
22+
needs: ci
23+
runs-on: ubuntu-latest
24+
defaults:
25+
run:
26+
working-directory: ab-testing
27+
steps:
28+
- name: Deploy
29+
uses: ./.github/workflows/ab-testing-deploy.yml
30+
with:
31+
stage: prod
32+
env:
33+
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_PROD_AB_TESTING_CONFIG }}
34+
FASTLY_API_TOKEN: ${{ secrets.FASTLY_PROD_API_TOKEN }}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: 🧪 Deploy AB Testing Config
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
stage:
10+
description: 'Stage to deploy to (e.g., code, prod)'
11+
required: true
12+
type: string
13+
secrets:
14+
FASTLY_AB_TESTING_CONFIG:
15+
required: true
16+
FASTLY_API_TOKEN:
17+
required: true
18+
19+
jobs:
20+
deploy:
21+
name: Deploy
22+
runs-on: ubuntu-latest
23+
defaults:
24+
run:
25+
working-directory: ab-testing
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
# https://github.com/denoland/setup-deno#latest-stable-for-a-major
30+
- uses: denoland/setup-deno@v1
31+
with:
32+
deno-version: v2.3
33+
34+
- name: Deploy
35+
run: deno run deploy

.vscode/settings.json.required

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Copy these to .vscode/settings.json to apply them to your editor – but don't commit it!
33
// https://github.com/guardian/recommendations/blob/main/VSCode.md#do-not-commit-vscode
44
{
5-
"deno.enablePaths": ["scripts/deno"],
5+
"deno.enablePaths": ["scripts/deno", "ab-testing/scripts"],
66
"deno.lint": true,
77
"deno.unstable": false,
88
"typescript.tsdk": "node_modules/typescript/lib",

ab-testing/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# AB Testing
2+
3+
This directory contains the code and configuration for the AB testing framework used on theguardian.com.
4+
5+
The [`abTest.ts`](./abTest.ts) module is where you should define your AB tests.
6+
7+
Add your AB tests to the `abTests` array in the `abTest.ts` file. Each test should have a unique name.
8+
9+
```ts
10+
{
11+
name: 'webex-example-test',
12+
description:
13+
'Test something interesting on the site',
14+
owners: ['[email protected]'],
15+
status: 'ON',
16+
expirationDate: '2050-12-30',
17+
type: 'client',
18+
audienceSize: 10 / 100,
19+
audienceSpace: 'A',
20+
groups: ['control', 'variant'],
21+
}
22+
```
23+
24+
When you create a PR that modifies the `abTest.ts` file, a git hook and CI will run checks to ensure that your AB test is valid (not expired, enough space for the test etc.).
25+
26+
When your PR is merged, the AB test will be automatically deployed to Fastly and be available at the same time as your changes.
27+
28+
## Guidelines for AB Tests
29+
30+
### Naming Conventions
31+
32+
AB tests should be prefixed with the team associated with the test, for example `webex-example-test`. This helps to identify the team responsible for the test and is enforce by typescript validation.
33+
34+
### Test Size and Groups
35+
36+
The `audienceSize` is the size of the whole test and is divided between the test groups that you specify. The "resolution" of sizing is down to 0.1%, so groups will be rounded to the nearest 0.1%.
37+
38+
Convention is to have groups named control and variant, but you can name them as you wish.
39+
40+
A single group is also possible, for example if you're rolling out a new feature and don't need a control.
41+
42+
### Client vs Server Side Tests
43+
44+
All requests are processed by Fastly at the edge, however, ab testing of server-side logic in Frontend or DCR will need to be cached separately. Client side tests do not need to be cached separately, as they are applied in the browser after the response is delivered.
45+
46+
Ensure that the `type` field is set to either `client` or `server` to indicate the type of test so that server side tests can be cached correctly, and client side tests are not splitting the cache unnecessarily.
47+
48+
There's a limit of the number of concurrent server-side tests that can be run, enforce by the validation script, so it's important to use client-side tests where possible.
49+
50+
### Test Expiration
51+
52+
AB tests should have an expiration date set in the future. This is to ensure that tests do not run indefinitely.
53+
54+
Expired tests will cause the ab testing validation to fail, and will not be deployed.
55+
56+
Tests that expire while they are are in-flight will not be served by fastly, and should be removed from the `abTest.ts` file as soon as possible.
57+
58+
### Audience Spaces
59+
60+
Ideally AB tests would never overlap (users being in multiple tests), but sometimes this is unavoidable, for example when running a very large 50+% test without interrupting existing tests.
61+
62+
To add a test where there is not enough space in the default audience space (`A`), you can specify a different `audienceSpace` in the test definition.
63+
64+
For example if there are already 3 25% tests in space `A` totalling 75%, and you want to run a 50% test, you can set the `audienceSpace` to `B` to allow this test to overlap with the existing tests.
65+
66+
## How it works
67+
68+
The AB testing framework uses Deno to run scripts that validate and deploy the tests. The `deno.json` file contains the tasks that can be run, such as `validate`, `deploy`, and `build`.
69+
70+
Which tests using which mvt ids is computed by the `build` task, which generates the `dist/mvts.json` file. This file contains the mapping of AB tests to MVT IDs.
71+
72+
The algorithm allocates tests available MVT IDs based on the audience size and space. Allocation is deterministic, so the same test (with the same name) will always be assigned the same MVT ID as long as the test hasn't been removed, allowing for consistent user experience when tests are added/updated/removed. This also means that new/update tests will not use contiguous MVT IDs, but will instead use whichever MVT IDs are available.
73+
74+
However, the allocation is completely separate for each audience space, so if you have a test in space `A` and move it to space `B`, it will be allocated different MVT IDs.
75+
76+
The state of the AB tests is stored in Fastly dictionaries, which are updated when the `deploy` task is run. Logic in fastly VCL will then use these dictionaries to determine which users are in which test groups and set appropriate headers and/or cookies.

ab-testing/abTest.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,4 @@ export const ABTests: ABTest[] = [
4545
groups: ['control', 'variant'],
4646
shouldForceMetricsCollection: true,
4747
},
48-
{
49-
name: 'commercial-large-overlap-test',
50-
description:
51-
'Allows viewing the beta version of the Europe network front',
52-
owners: [
53-
54-
55-
],
56-
status: 'ON',
57-
expirationDate: '2050-12-30',
58-
type: 'server',
59-
audienceSize: 50 / 100,
60-
groups: ['control', 'variant'],
61-
audienceSpace: 'B',
62-
shouldForceMetricsCollection: true,
63-
},
6448
];

ab-testing/deno.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"tasks": {
3+
"validate": "deno run scripts/validation/index.ts",
4+
"deploy": "deno run --allow-read=dist --allow-env=FASTLY_AB_TESTING_CONFIG --allow-net=api.fastly.com:443 scripts/deploy/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json",
5+
"build": "deno run --allow-write=dist --allow-env=FASTLY_AB_TESTING_CONFIG,FASTLY_API_TOKEN --allow-net=api.fastly.com:443 scripts/build/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json"
6+
},
7+
"imports": {
8+
"@std/assert": "jsr:@std/assert@^1.0.13",
9+
"@superstruct/core": "jsr:@superstruct/[email protected]"
10+
}
11+
}

0 commit comments

Comments
 (0)