Test Kitchen/Architecture
This page provides a detailed overview of the Test Kitchen architecture and how its components work together to enable instrumentation and experimentation.
Test Kitchen is a distributed system that enables product teams to instrument features and run A/B tests on Wikipedia and other Wikimedia projects. The system is made of a large set of components that communicate by API or protocol in order to enable/disable experiments quickly, and collect and analyze experiment-related analytics events efficiently. This page will:
- Give an overview of the components owned and maintained by the Experiment Platform team and provide links to documentation for those components that aren't; and
- Step through the lifecycle of an experiment to show how those components interact with one another
Overview
Test Kitchen UI
Test Kitchen UI is the source of truth for experiment and instrument configuration for Varnish and MediaWiki. It combines information about instrument and experiment configurations from multiple sources into one API endpoint. Other components, like Varnish and the TestKitchen MediaWiki extension, fetch experiment and instruments configurations from it.
The two sources Test Kitchen UI combines information from are GrowthBook and its own local database. Instrument data is stored in the Test Kitchen local database. Experiment configuration settings are fetched from GrowthBook.
Instrument owners can configure and activate/deactivate their instruments in the UI, and the settings will be stored locally.
Experiment owners can configure and activate/deactive their experiments in GrowthBook. Previously, experiment configuration was also locally configured and stored in Test Kitchen UI. As of FY26/27 Q1 this is still supported, but GrowthBook is recommended. Local configuration of experiments will stop being supported in the upcoming quarter. Test Kitchen UI fetches and validates experiment configurations from GrowthBook and outputs them alongside the experiment and instrument configurations from Test Kitchen UI.
UI: https://test-kitchen.wikimedia.org
API:
- Instrument configuration: https://test-kitchen.wikimedia.org/api/v1/instruments
- Experiment configuration: https://test-kitchen.wikimedia.org/api/v1/experiments
- Experiment configuration (Varnish): https://test-kitchen.wikimedia.org/api/v1/experiments?format=config&authority=varnish
- Analytics configuration: https://test-kitchen.wikimedia.org/api/v1/experiments?format=analytics
Codebase: https://gitlab.wikimedia.org/repos/data-engineering/test-kitchen
GrowthBook
GrowthBook is an open source feature flagging and experimentation software integrated into Test Kitchen. Currently, we are only using GrowthBook for experimentation.
Experiment settings are configured in GrowthBook, and experiment results can be viewed in GrowthBook.
Our installation of GrowthBook is entirely on-premises – GrowthBook queries our Data Lake using our Presto cluster and then analyzes the data in a statistics engine running on our servers.
Varnish
Varnish is the experiment enrollment sampling authority for cache-splitting and non-cache-splitting everyone experiments.
Varnish fetches the configurations of active everyone experiments every minute. It assigns the user agent an Edge Unique ID , enrolls the user agent in those experiments, and sends the experiment enrollment information to downstream components.
Varnish differs from the TestKitchen MediaWiki extension in that it persists the experiment configurations to disk. This is done for simplicity.
Codebases:
- https://gitlab.wikimedia.org/repos/sre/libvmod-wmfuniq
- https://gerrit.wikimedia.org/g/operations/puppet/+/564ff387b639/modules/profile/files/cache/wmfuniq_experiment_fetcher.py
Dashboards:
- https://grafana.wikimedia.org/d/503b903b-ceff-4931-b435-e011cfcda0e8/edge-uniques
- https://grafana.wikimedia.org/d/eemh00uzqj6yoc/edge-uniques-drilldown
TestKitchen MediaWiki extension
The TestKitchen MediaWiki extension has multiple responsibilities:
- It acts as the experiment enrollment sampling authority for logged-in experiments
- It fetches configurations for all active experiments to power the SDKs every minute
- It fetches experiment enrollment information sent from Varnish and makes it available to the PHP SDK
- It hosts the PHP and JavaScript SDKs and make them available to other MediaWiki code
The TestKitchen MediaWiki extension caches the experiment and instrument configurations in two places:
- The WANObjectCache (10 minutes)
- The MainStash (indefinitely)
This caching strategy prioritizes site performance over freshness as experiment and instrument configurations are fetched on every request served by the MediaWiki application servers. In the event that both places don't have experiment or instrument configurations, then all experiments and instruments are considered disabled.
Codebase: https://gerrit.wikimedia.org/r/admin/repos/mediawiki/extensions/TestKitchen,general
EventBus MediaWiki extension
The TestKitchen PHP SDK (provided by the TestKitchen MediaWiki extension) uses the EventBus MediaWiki extension to send analytics events to EventGate.
EventGate
EventGate, the Event Platform event intake service, receives, validates, and routes analytics events from experiments and instruments to the Data Lake .
Superset
A general-purpose analysis platform where where experiment results for experiments configured in Test Kitchen UI can be visualized. Results for experiments configured in GrowthBook are viewed in GrowthBook.
The experiment configurations are fetched from the Test Kitchen UI API every day by
the
load_configurations_daily
Airflow job
. The experiment results are calculated every hour by
the
analyze_experiments_hourly
Airflow job
.
Entrypoints
UI: https://superset.wikimedia.org/superset/dashboard/experiment-analytics/
Airflow: https://airflow-analytics-product.wikimedia.org/home?tags=test_kitchen
Codebases:
- https://gitlab.wikimedia.org/repos/product-analytics/test-kitchen/experiment-analytics-configs
- https://gitlab.wikimedia.org/repos/product-analytics/test-kitchen/experiment-analytics-jobs
The lifecycle of an experiment
The Test Kitchen/Conduct an experiment guide details all of the phases in the lifecycle of an experiment. This section focuses on the Launch and Evaluate phases to clarify how all of the components outlined above interact with one another.
Launch
When an experiment is started in GrowthBook:
-
~1 minute after the experiment is started Test Kitchen UI fetches and validates the experiment configuration
-
If the experiment configuration is not valid, Test Kitchen UI:
-
Sets the experiment status in GrowthBook to
draft -
Posts a comment on the Phabricator task in the
phabricator_urlfield
-
Sets the experiment status in GrowthBook to
-
If the experiment configuration is valid, Test Kitchen UI:
- Persists the experiment configuration to the database
- Adds the experiment configuration to the Test Kitchen APIs
-
If the experiment configuration is not valid, Test Kitchen UI:
When a cache-splitting everyone experiment is activated in Test Kitchen UI :
- The experiment configuration is present in the Test Kitchen APIs
-
The experiment traffic allocation and groups are converted to ranges between 1 and 100,000 for Varnish
-
For example: An experiment with a 1% traffic allocation, and with
controlandtreatmentgroups, are converted tocontrol: [0,999)andtreatment: [50000, 50999), respectively
-
For example: An experiment with a 1% traffic allocation, and with
-
~1 minute after the experiment is activated:
- Varnish fetches the experiment configuration and persists it to disk
- MediaWiki fetches the experiment configuration and stashes it in the MainStash for a week
- The automated analysis pipeline fetches the experiment configuration and persists it to Hive
When Varnish processes a request:
- It checks that request has an Edge Unique cookie
-
If the request doesn't have an Edge Unique cookie, then it initializes one and sets it
-
Note:
Edge Unique cookies are set with the
secureandhttponlyattributes and so cannot be read by JavaScript
-
Note:
Edge Unique cookies are set with the
-
For each everyone experiment:
- It hashes the Edge Unique with the name of the experiment
-
It converts the hash to a number in the range
[1, 100,000) - It checks if the number falls in one of the ranges
- If the number falls in a range, then it stores the name of the experiment and group name associated with the range
-
It converts the set of experiment, group tuples to a backend request header,
X-Experiment-Enrollments-
For example:
X-Experiment-Enrollments: foo=bar;baz=qux;
-
For example:
-
It checks whether there is a response with a matching
X-Experiment-Enrollmentsheader set -
If there is no response with a matching
X-Experiment-Enrollmentsheader set, then it sends the request to the backend -
If the backend response is cacheable, it ensures that backend response is cached with the
X-Experiment-Enrollmentsheader set -
It sets a
Server-Timingheader to the value of theX-Experiment-Enrollmentsheader-
For example:
Server-Timing: WMF-Uniq;desc="foo=bar;baz=qux;"
-
For example:
- It sends the response to the user agent
When the MediaWiki app servers receive the request, the MediaWiki TestKitchen extension is loaded and the
the
BeforeInitialize
hook
fires:
-
It parses and validates the
X-Experiment-Enrollmentsheader into a set of experiment, group tuples - It checks if the user is logged in and has a central ID
-
It fetches all experiment configurations
- It fetches all experiment configurations from the WANObjectCache
-
If no experiment configurations were returned, then it fetches all experiment configurations from the MainStash
- If no experiment configurations were returned, then it stores the empty list in the WANObjectCache for a minute
- It experiment configurations were returned, then it stores the experiment configurations in the WANObjectCache for 10 minutes
-
If the user is logged and has a central ID, then for each logged-in experiment:
-
It hashes the Edge Unique with the name of the experiment and converts it to a number in the range
[0, 1) -
It converts the experiment traffic allocation to a number in the range
[0, 1) -
If the number in (1) is less than the number in (2), then:
- It uses the number in (1) to select a group name
- It stores the name of the experiment and group name
-
It hashes the Edge Unique with the name of the experiment and converts it to a number in the range
- It combines the set of experiment, group tuples in (1) and (4)
- It initializes the PHP and JavaScript SDKs with the results of (5) and (3)
-
It adds the
ext.testKitchenResourceLoader module to the response
When the
ext.testKitchen
ResourceLoader module loads:
-
It parses and validates the
Server-Timingheader sent by Varnish into a set of experiment, group tuples -
It parses and validates the
mpoquery parameter into a set of experiment, group tuples - It combines the set of experiment, group tuples sent by MediaWiki with (1) and (2)
-
It defines
the
mw.testKitchennamespace and methods
When the feature code requests details of an experiment via the
mw.testKitchen.getExperiment()
method:
- It looks up the group name and experiment configuration
-
It initializes an instance of
the
Experimentclass with the experiment name, group name, and experiment configuration
When the feature code sends an experiment-related analytics event using
the
Experiment#send()
method
:
- It constructs a valid Event Platform event
- It merges the given action and interaction data into the event
- It merges the given names of contextual attributes with the names of contextual attributes from the experiment configuration
- It sets the named contextual attributes
-
It initializes the
experimentobject with the experiment configuration and adds it to the event-
NOTE:
For everyone experiments, the value of
experiment.subject_idis always"awaiting"
-
NOTE:
For everyone experiments, the value of
- It queues the event for sending after 5 seconds, or when the user hides the page or navigates away from it
-
It sends the event to the global experiment-related analytics event intake URL,
/evt-103e/v2/events
When Varnish processes a request to send an event:
- It checks that request has an Edge Unique cookie that has been sent to the user agent and received by it at least once
-
For each everyone experiment:
- It hashes the Edge Unique with the name of the experiment
-
It converts the hash to a number in the range
[1, 100,000) - It checks if the number falls in one of the ranges
- If the number falls in one of the ranges, then it stores the name of the experiment, the hash, and group name associated with the range
- It checks that the set of experiments, hashes, and group names has at least one entry
-
It converts the set of experiment, group tuples to a backend request header,
X-Experiment-Enrollments-
For example:
X-Experiment-Enrollments: foo=bar/foo_hash;baz=qux/baz_hash;
-
For example:
- It sends the request to the backend
- It responds with a synthetic HTTP 204 No Content response
When EventGate receives an event:
-
It validates it against the
/analytics/product_metrics/web/base/2.0.0schema - It validates and parses the X-Experiment-Enrollments header into a set of experiment, hash, group name tuples
-
It finds the tuple that matches
{ experiment.enrolled, *, experiment.assigned }-
If no tuple is found, then an error is thrown and the event is sent to the
eventgate-analytics-external.error.validationstream
-
If no tuple is found, then an error is thrown and the event is sent to the
-
It sets the
experiment.subject_idto the hash from the tuple -
It adds the event to the
eqiad.product_metrics.web_baseandcodfw.product_metrics.web_baseKafka topics - ~3 hours after the event is added to the Kafka topics, Gobblin ingests the event into the Data Lake
Evaluate
When the growthbook_hourly DAG runs:
-
It fetches experiment events from from the
event.product_metrics_web_baseHive table where:-
experiment.coordinatorisdefault
-
-
It inserts all experiment events into the
wmf_experiments.experiment_event_v1timestamp-partitioned Iceberg table -
It fetches the experiment assignment events in the
wmf_experiments.experiment_event_v1timestamp-partitioned Iceberg table and-
Computes the
user_auth_statusanduser_platformdimensions for each event -
Inserts the events into the
wmf_experiments.experiment_assignment_v1table
-
Computes the
GrowthBook queries both the
experiment_event_v1
and
wmf_experiments.experiment_assignment_v1
tables using our Presto cluster when computing experiment results either periodically or when request by the user.
Legacy
When the automated analysis pipeline runs:
- It fetches experiment configurations from Hive
-
For each experiment:
-
For each metric:
- It computes the value of the metric from the experiment-related analytics events that have been received
- It persists the computed value to disk
-
For each metric:
See also
- See Test Kitchen/Troubleshooting for instructions on how to inspect and troubleshoot the processes detailed above