Skip to content

[AB testing PoC] calculate ab test MVTs without manual offset#14286

Merged
Jakeii merged 11 commits intocommercial/ab-testing-pocfrom
jlk/calculate-test-offsets
Jul 30, 2025
Merged

[AB testing PoC] calculate ab test MVTs without manual offset#14286
Jakeii merged 11 commits intocommercial/ab-testing-pocfrom
jlk/calculate-test-offsets

Conversation

@Jakeii
Copy link
Member

@Jakeii Jakeii commented Jul 25, 2025

What does this change?

This does away with manual test offset, and now looks at the existing state of the tests in the mvt dictionary when building the key values for the current ab-tests.ts, ensuring any existing tests "stay where they are" within the test space.

It does the following

  1. Work out which tests are using which MVT ids, and which MVT ids are available.
  2. Free up space by removing any removed tests, and shrinking any down-sized tests - adding the freed mvt ids to the pool of available mvts.
  3. Attempt to increase any up-sized tests using available mvts.
  4. Attempt to create any new tests using available mvts.

This does means that tests will not use contiguous ranges of MVT IDs anymore, but MVTs are random so this doesn't affect the behaviour of test allocation.

Why?

At the moment tests need to specify their audienceOffset, which is where in the mvt space to position the test, this meant that developers wanting to add tests have to check all existing tests and work out what value they need to use for any new tests.

The reason for it's existence was to avoid tests "moving around" to different mvts, changing the users who are in the test while it's in progress.

@github-actions
Copy link

github-actions bot commented Jul 28, 2025

@github-actions
Copy link

github-actions bot commented Jul 28, 2025

@Jakeii Jakeii force-pushed the jlk/calculate-test-offsets branch from b4ad08f to 46a5cf9 Compare July 28, 2025 10:10
"validate": "deno run scripts/validation/index.ts",
"upload": "deno run --allow-read=dist --allow-env=FASTLY_AB_TESTING_API_TOKEN,FASTLY_AB_TESTING_SERVICE_ID,FASTLY_AB_TESTING_SERVICE_NAME,FASTLY_MVTS_DICTIONARY_ID,FASTLY_MVTS_DICTIONARY_NAME,FASTLY_AB_TESTS_DICTIONARY_ID,FASTLY_AB_TESTS_DICTIONARY_NAME --allow-net=api.fastly.com:443 scripts/deploy/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json",
"build": "deno run --allow-write scripts/build/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json"
"build": "deno run --allow-env=FASTLY_AB_TESTING_API_TOKEN,FASTLY_AB_TESTING_SERVICE_ID,FASTLY_AB_TESTING_SERVICE_NAME,FASTLY_MVTS_DICTIONARY_ID,FASTLY_MVTS_DICTIONARY_NAME,FASTLY_AB_TESTS_DICTIONARY_ID,FASTLY_AB_TESTS_DICTIONARY_NAME --allow-net=api.fastly.com:443 --allow-write scripts/build/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build step now needs to read the AB testing state from fastly

@Jakeii Jakeii marked this pull request as ready for review July 28, 2025 10:44
@github-actions
Copy link

Hello 👋! When you're ready to run Chromatic, please apply the run_chromatic label to this PR.

You will need to reapply the label each time you want to run Chromatic.

Click here to see the Chromatic project.

@GHaberis GHaberis requested a review from Copilot July 29, 2025 15:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR removes the manual audienceOffset requirement from A/B tests and implements an automatic MVT (multivariate test) allocation system that preserves existing test positions while optimizing space usage.

  • Removes audienceOffset field from ABTest type and replaces manual offset management with automatic allocation
  • Introduces a new TestGroupMVTManager class to handle MVT allocation, resizing, and deallocation
  • Refactors the MVT dictionary building process to use existing Fastly dictionary state as the source of truth

Reviewed Changes

Copilot reviewed 25 out of 28 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
ab-testing/types.ts Removes audienceOffset field from ABTest interface
ab-testing/scripts/validation/variantOverlap.ts Removes entire overlap validation module (no longer needed)
ab-testing/scripts/validation/validSizeOffset.ts Removes size/offset validation module
ab-testing/scripts/build/test-group-mvt-manager.ts New class for managing MVT allocation and test group lifecycle
ab-testing/scripts/build/calculate-mvt-updates.ts New module for calculating MVT updates across audience spaces
ab-testing/scripts/lib/ New shared utilities for constants, types, and Fastly subfield parsing
ab-testing/scripts/build/index.ts Updates build process to fetch existing MVT state and use new allocation system
ab-testing/abTest.ts Removes audienceOffset and highImpact fields from example tests

string,
{ name: string; type: string; exp: number }
> = new Map(
mvtGroups.entries().map(([key, value]) => [key, value[i]]),
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map.entries() returns a MapIterator, not an array. This should be Array.from(mvtGroups.entries()).map(...) to properly convert to an array before calling map().

Suggested change
mvtGroups.entries().map(([key, value]) => [key, value[i]]),
Array.from(mvtGroups.entries()).map(([key, value]) => [key, value[i]]),

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@GHaberis GHaberis Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI is flagging that the result of mvtGroups.entries() cannot be iterated over using a .map, I asked it to generate a TypeScript playground snippet to demonstrate how to iterate over the result of .entries() which we can see here. So this snippet has a compiler error and fails to run, but this isn't an issue we've come across via our tests or with the compiler. 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot must think we're targeting an older ES version? If you change the target to ESNext the error goes away

Copy link
Contributor

@GHaberis GHaberis Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok interesting, we should record this and provide feedback, all the issues it's flagged look to be the same kind of issue.

Copy link
Contributor

@cemms1 cemms1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got halfway through reviewing this but then stopped and forgot to continue. I might suggest this PR is slightly too large to give a good PR review here and in an ideal world, this would have been split into a couple of PRs to make it a bit easier for reviewing.

I'll finish my review soon but leaving the comments before I forget!

Jakeii and others added 2 commits July 30, 2025 10:20
@Jakeii
Copy link
Member Author

Jakeii commented Jul 30, 2025

I got halfway through reviewing this but then stopped and forgot to continue. I might suggest this PR is slightly too large to give a good PR review here and in an ideal world, this would have been split into a couple of PRs to make it a bit easier for reviewing.

I'll finish my review soon but leaving the comments before I forget!

If you have a suggestion of how I could break it up, happy to do so! There were a couple of small things like removing the highImpact option that snuck in but I can't think how to break it down further.

Comment on lines +33 to +41
const stringifyMVTValue = (array: FastlyTestParams[]): string => {
const subfield: Record<string, string> = {};
array.forEach((item, index) => {
subfield[`group:${index}`] = item.name;
subfield[`group:${index}:type`] = item.type;
subfield[`group:${index}:exp`] = String(item.exp);
});
return stringifyFastlySubfield(subfield);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test would help to better illustrate what is happening here

Copy link
Member Author

@Jakeii Jakeii Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes good point!

@Jakeii Jakeii merged commit 2953150 into commercial/ab-testing-poc Jul 30, 2025
25 checks passed
@Jakeii Jakeii deleted the jlk/calculate-test-offsets branch July 30, 2025 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants