jolly-raincoat-49731
01/10/2024, 3:53 PMrhythmic-agent-34208
01/12/2024, 3:29 AMrhythmic-agent-34208
01/12/2024, 3:34 AMjolly-raincoat-49731
01/12/2024, 2:03 PMbrief-honey-45610
01/12/2024, 3:55 PMjolly-raincoat-49731
01/12/2024, 4:02 PMjolly-raincoat-49731
01/12/2024, 4:05 PMSELECT
user_pseudo_id as anonymous_id,
TIMESTAMP_MICROS(event_timestamp) as timestamp,
experiment_id_param.value.string_value AS experiment_id,
variation_id_param.value.int_value AS variation_id,
geo.country as country,
traffic_source.source as source,
traffic_source.medium as medium,
device.category as device,
device.web_info.browser as browser,
device.operating_system as os
FROM
`xxxxxxx`.`xxxxxxx`.`events_*`,
UNNEST(event_params) AS experiment_id_param,
UNNEST(event_params) AS variation_id_param
WHERE
(_TABLE_SUFFIX BETWEEN '{{date startDateISO "yyyyMMdd"}}' AND '{{date endDateISO "yyyyMMdd"}}' or
_TABLE_SUFFIX BETWEEN 'intraday_{{date startDateISO "yyyyMMdd"}}' AND 'intraday_{{date endDateISO "yyyyMMdd"}}')
AND event_name = 'experiment_viewed'
AND experiment_id_param.key = 'experiment_id'
AND variation_id_param.key = 'variation_id'
AND user_pseudo_id is not null
jolly-raincoat-49731
01/12/2024, 4:07 PMimport { GrowthBook } from "@growthbook/growthbook";
export async function initGrowthBook(serverside = false) {
let gbClientKey = "";
let gbEnableDev = true;
if (PRODUCTION) {
gbClientKey = "";
gbEnableDev = false;
}
// Custom generated ID for user
let gbuuid = getUUID();
const growthbook = new GrowthBook({
apiHost: "",
clientKey: gbClientKey,
enableDevMode: gbEnableDev,
// Targeting attributes
attributes: {
id: gbuuid,
},
trackingCallback: (experiment, result) => {
if (serverside) {
<http://DOI.Ajax.post|DOI.Ajax.post>(
'/checkout/experiment.json',
{
data: {
experiment_id: experiment.key || "",
variation_id: result.key || "",
gb_user_id: gbuuid || "",
},
dataType: 'json',
success: () => {
},
error: (json) => {
if (!PRODUCTION) {
console.log("GrowthBook error");
console.log(json);
}
},
}
);
} else {
window.jxEventBus.push({
event: "experiment",
experiment_id: experiment.key,
variation_id: result.key,
gb_user_id: gbuuid,
});
}
if (!PRODUCTION) {
console.log("Experiment Viewed", {
experimentId: experiment.key,
variationId: result.key,
variationValue: result.value,
});
}
},
});
await growthbook.loadFeatures({ autoRefresh: true });
return growthbook;
}
// Attach to the window object
if ("undefined" !== typeof window) {
window.initGrowthBook = initGrowthBook;
}
// This is taken from GrowthBook
// <https://docs.growthbook.io/guide/GA4-google-analytics#generating-your-own-id>
const getUUID = () => {
const COOKIE_NAME = "gbuuid";
const COOKIE_DAYS = 400; // 400 days is the max cookie duration for chrome
// use the browsers crypto.randomUUID if set
const genUUID = () => {
if(window?.crypto?.randomUUID) return window.crypto.randomUUID();
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
const getCookie = (name) => {
let value = `; ${document.cookie}`;
let parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
const setCookie = (name, value) => {
var d = new Date();
d.setTime(d.getTime() + 24*60*60*1000*COOKIE_DAYS);
document.cookie = name + "=" + value + ";path=/;expires=" + d.toGMTString();
}
// get the existing UUID from cookie if set, otherwise create one and store it in the cookie
if(getCookie(COOKIE_NAME)) return getCookie(COOKIE_NAME);
const uuid = genUUID();
setCookie(COOKIE_NAME, uuid);
return uuid;
}
jolly-raincoat-49731
01/12/2024, 4:12 PMjolly-raincoat-49731
01/12/2024, 4:12 PMjolly-raincoat-49731
01/12/2024, 4:13 PMjolly-raincoat-49731
01/12/2024, 4:13 PMdocument.addEventListener("DOMContentLoaded", async () => {
if ("function" !== typeof window.initGrowthBook) {
return;
}
const growthbook = await window.initGrowthBook();
const featureName = "items-to-consider";
const feature = growthbook.isOn(featureName);
if (feature) {
const items = document.getElementById("jx-abt-items-to-consider");
items.style.display = "block";
}
});
brief-honey-45610
01/12/2024, 4:16 PMbrief-honey-45610
01/12/2024, 4:16 PMjolly-raincoat-49731
01/12/2024, 4:17 PMjolly-raincoat-49731
01/12/2024, 4:17 PMhelpful-application-7107
01/12/2024, 6:10 PMExperiment Assignment Query
for the Identifier Type you're using for this experiment? This is defined on your Data Source page.
a. Follow-up, is there a column specifically for the gb_user_id
and are you using it, or are you using some other identifier type in that query?jolly-raincoat-49731
01/12/2024, 6:18 PMjolly-raincoat-49731
01/12/2024, 6:19 PMjolly-raincoat-49731
01/12/2024, 6:19 PMhelpful-application-7107
01/12/2024, 6:19 PMhelpful-application-7107
01/12/2024, 6:19 PMhelpful-application-7107
01/12/2024, 6:19 PMjolly-raincoat-49731
01/12/2024, 6:20 PMhelpful-application-7107
01/12/2024, 6:21 PMjolly-raincoat-49731
01/12/2024, 6:22 PManonymous_id
Dimension Columns: country
, source
, medium
, device
, browser
, os
jolly-raincoat-49731
01/12/2024, 6:22 PMjolly-raincoat-49731
01/12/2024, 6:22 PMjolly-raincoat-49731
01/12/2024, 6:23 PMhelpful-application-7107
01/12/2024, 6:23 PMhelpful-application-7107
01/12/2024, 6:23 PMjolly-raincoat-49731
01/12/2024, 6:23 PMhelpful-application-7107
01/12/2024, 6:24 PMjolly-raincoat-49731
01/12/2024, 6:24 PMhelpful-application-7107
01/12/2024, 6:28 PMuser_psuedo_id
coming out of GA4 is getting out of sync with the gbuuid
that you're using for actual hashing. Basically, if there were a way to always use gbuuid
throughout the system (in your experiment assignment queries, in your metrics), this problem would go away, but that solution isn't always quite so simple.
Let me discuss with my team because this happens sometimes with GA4 and we need to ensure we have a better playbook for diagnosing and improving this situation.jolly-raincoat-49731
01/12/2024, 6:30 PMhelpful-application-7107
01/12/2024, 9:42 PMjolly-raincoat-49731
01/12/2024, 9:44 PMjolly-raincoat-49731
01/12/2024, 9:45 PMhelpful-application-7107
01/12/2024, 9:45 PMjolly-raincoat-49731
01/12/2024, 9:47 PMhelpful-application-7107
01/12/2024, 9:47 PMhelpful-application-7107
01/12/2024, 9:48 PMjolly-raincoat-49731
01/12/2024, 9:48 PMjolly-raincoat-49731
01/12/2024, 9:49 PMjolly-raincoat-49731
01/12/2024, 9:49 PMhelpful-application-7107
01/12/2024, 9:49 PMjolly-raincoat-49731
01/12/2024, 9:50 PMhelpful-application-7107
01/12/2024, 9:51 PMjolly-raincoat-49731
01/12/2024, 9:54 PMhelpful-application-7107
01/12/2024, 9:54 PMgbuuid
is getting out of sync with the user_psuedo_id
that you are actually using in the queries to check what experiment membership people are in. There is normally a little bit of error in this process that is worth it to make sure you have fast hashing ("build your own gbuuid
rather than waiting for ga4 to load the pseudo id") but consistent ids for metric tracking ("using the user_psuedo_id
from GA4").
They can get out of sync when you aren't storing the gbuuid in a cookie that has long life, akin to the user_pseudo_id
(which I don't think is your problem), or if there's something else preventing the gbuuid being set consistently for a given user_pseudo_id
.jolly-raincoat-49731
01/12/2024, 9:56 PMattributes: {
id: gbuuid,
},
jolly-raincoat-49731
01/12/2024, 9:57 PMhelpful-application-7107
01/12/2024, 9:57 PMgbuuid
to user_pseudo_id
mapping:
SELECT
user_pseudo_id,
gb_user_id_param.value.string_value AS gb_user_id,
TIMESTAMP_MICROS(event_timestamp) as timestamp,
experiment_id_param.value.string_value AS experiment_id,
variation_id_param.value.int_value AS variation_id,
FROM
`xxxxxxx`.`xxxxxxx`.`events_*`,
UNNEST(event_params) AS experiment_id_param,
UNNEST(event_params) AS variation_id_param,
UNNEST(event_params) AS gb_user_id_param
WHERE
_TABLE_SUFFIX BETWEEN '20230101' AND '20230112'
AND event_name = 'experiment_viewed'
AND experiment_id_param.key = 'experiment_id'
AND variation_id_param.key = 'variation_id'
AND gb_user_id_param.key = 'gb_user_id'
AND user_pseudo_id is not null
jolly-raincoat-49731
01/12/2024, 9:59 PMhelpful-application-7107
01/12/2024, 9:59 PMid
as gb_user_id
but in the actual analysis query you are using user_pseudo_id
. This is what most people do because it ends up being much easier to ensure that you have user_pseudo_id
everywhere in your GA4 data because it always appends it; if you wanted to use gb_user_id
you'd have to make sure it gets tracked along with every one of your metrics.
So your set up looks normal and typical, but something is going on that is causing certain users (as defined by some given user_psuedo_id
) to get re-assigned new gbuuid
values, and therefore get hashed againjolly-raincoat-49731
01/12/2024, 9:59 PMhelpful-application-7107
01/12/2024, 9:59 PMjolly-raincoat-49731
01/12/2024, 9:59 PMjolly-raincoat-49731
01/12/2024, 10:01 PMhelpful-application-7107
01/12/2024, 10:01 PMjolly-raincoat-49731
01/12/2024, 10:02 PMhelpful-application-7107
01/12/2024, 10:03 PMhelpful-application-7107
01/12/2024, 10:03 PMuser_pseudo_id
is good enough because you always have it for everything from GA4, and it normally lines up well enough with a custom gbuuid
. Many people get some multiple exposures (small percentage) when they mismatch, but it normally doesn't trigger SRM.jolly-raincoat-49731
01/12/2024, 10:05 PMjolly-raincoat-49731
01/12/2024, 10:06 PMhelpful-application-7107
01/12/2024, 10:07 PMuser_pseudo_id
to gb_user_id
mapping is not 1:1.jolly-raincoat-49731
01/12/2024, 10:07 PMhelpful-application-7107
01/12/2024, 10:13 PMusers
and doing something like
SELECT
user_psuedo_id,
experiment_id,
COUNT(DISTINCT variation_id) as n_variations,
COUNT(DISTINCT gb_user_id) as n_gb_uids
FROM
users
GROUP BY 1, 2
ORDER BY 4 DESC
Then you'll see how many gb_user_ids are being generate for some "user_pseudo_id"jolly-raincoat-49731
01/12/2024, 10:16 PMhelpful-application-7107
01/12/2024, 10:17 PMWITH users AS (
<...the query that returned the data you pasted ...>
)
SELECT
user_psuedo_id,
experiment_id,
COUNT(DISTINCT variation_id) as n_variations,
COUNT(DISTINCT gb_user_id) as n_gb_uids
FROM
users
GROUP BY 1, 2
ORDER BY 4 DESC
jolly-raincoat-49731
01/12/2024, 10:18 PMhelpful-application-7107
01/12/2024, 10:18 PMjolly-raincoat-49731
01/12/2024, 10:18 PMjolly-raincoat-49731
01/12/2024, 10:18 PMhelpful-application-7107
01/12/2024, 10:19 PMhelpful-application-7107
01/12/2024, 10:19 PMjolly-raincoat-49731
01/12/2024, 10:20 PMjolly-raincoat-49731
01/12/2024, 10:22 PMhelpful-application-7107
01/12/2024, 10:22 PMhelpful-application-7107
01/12/2024, 10:22 PMjolly-raincoat-49731
01/12/2024, 10:23 PMhelpful-application-7107
01/12/2024, 10:26 PMjolly-raincoat-49731
01/12/2024, 10:27 PM