GA4 envoie des événements bruts à BigQuery sans aucune agrégation au niveau des sessions. La logique de sessionisation doit être construite manuellement — regrouper les événements par utilisateur, détecter les limites de session en fonction des seuils d’inactivité, et calculer des métriques comme la durée et le nombre d’événements.
Cette logique implique de l’arithmétique sur les horodatages (les horodatages en microsecondes de GA4), des fonctions de fenêtrage pour calculer les écarts de temps, des seuils d’inactivité configurables, et l’agrégation du niveau événement vers le niveau session. Les bugs dans cette logique produisent des taux de conversion, des durées de session et des taux de rebond incorrects.
Tester la détection des limites de session
Le cœur de la sessionisation est de décider quand une session se termine et une autre commence — typiquement après 30 minutes d’inactivité. Voici un modèle qui l’implémente :
-- models/intermediate/int__ga4_sessions.sqlwith events as ( select user_pseudo_id, ga_session_id, event_timestamp, event_name, timestamp_diff( timestamp_micros(event_timestamp), lag(timestamp_micros(event_timestamp)) over ( partition by user_pseudo_id order by event_timestamp ), minute ) as minutes_since_last_event from {{ ref('base__ga4__events') }}),
sessionized as ( select *, case when minutes_since_last_event > 30 or minutes_since_last_event is null then 1 else 0 end as is_new_session from events)
select user_pseudo_id, ga_session_id, concat(user_pseudo_id, '_', ga_session_id) as session_key, min(event_timestamp) as session_start, max(event_timestamp) as session_end, timestamp_diff( timestamp_micros(max(event_timestamp)), timestamp_micros(min(event_timestamp)), second ) as session_duration_seconds, count(*) as event_countfrom sessionizedgroup by 1, 2, 3unit_tests: - name: test_int_ga4_sessions_boundaries model: int__ga4_sessions description: "Les sessions devraient se couper après 30 minutes d'inactivité" given: - input: ref('base__ga4__events') rows: # Utilisateur 1, Session 1 : événements dans les 30 min - {user_pseudo_id: "user_1", ga_session_id: 1001, event_timestamp: 1717200000000000, event_name: "page_view"} - {user_pseudo_id: "user_1", ga_session_id: 1001, event_timestamp: 1717201800000000, event_name: "scroll"} # +30 min # Utilisateur 1, Session 2 : écart > 30 min - {user_pseudo_id: "user_1", ga_session_id: 1002, event_timestamp: 1717207200000000, event_name: "page_view"} # +90 min depuis le premier # Utilisateur 2, Session 1 - {user_pseudo_id: "user_2", ga_session_id: 2001, event_timestamp: 1717200000000000, event_name: "page_view"} expect: rows: - {session_key: "user_1_1001", session_duration_seconds: 1800, event_count: 2} - {session_key: "user_1_1002", session_duration_seconds: 0, event_count: 1} - {session_key: "user_2_2001", session_duration_seconds: 0, event_count: 1}Ce test vérifie trois comportements fondamentaux :
- Session 1001 : deux événements distants de 30 minutes exactement (exactement à la limite) appartiennent à la même session. La durée est de 1800 secondes. Cela teste la condition aux limites — votre seuil est-il
> 30ou>= 30? - Session 1002 : un troisième événement 90 minutes après le premier déclenche une nouvelle session (l’écart dépasse 30 minutes depuis le dernier événement de la session 1001). La durée est de 0 seconde pour une session à événement unique.
- Session 2001 : l’événement unique de l’utilisateur 2 crée sa propre session avec une durée de 0 seconde. Cela vérifie la construction de la clé de session et l’isolation des partitions — les événements de l’utilisateur 1 n’affectent pas l’utilisateur 2.
Les horodatages en microsecondes (1717200000000000) sont le format natif de GA4. Vous devrez calculer ces valeurs en fonction de vos scénarios de test. 1717200000000000 correspond approximativement à 2024-06-01 00:00:00 UTC en microsecondes.
Tester les sessions à cheval sur minuit
Un cas limite subtil mais important : les sessions qui chevauchent minuit.
unit_tests: - name: test_int_ga4_sessions_cross_midnight model: int__ga4_sessions description: "Les sessions couvrant minuit ne devraient pas être interrompues artificiellement" given: - input: ref('base__ga4__events') rows: - {user_pseudo_id: "user_1", ga_session_id: 1001, event_timestamp: 1717199400000000, event_name: "page_view"} # 23:50 - {user_pseudo_id: "user_1", ga_session_id: 1001, event_timestamp: 1717200600000000, event_name: "purchase"} # 00:10 jour suivant expect: rows: - {session_key: "user_1_1001", session_duration_seconds: 1200, event_count: 2}Ce test détecte un vrai bug : certaines implémentations interrompent accidentellement les sessions aux limites de date parce qu’elles partitionnent par date plutôt que par utilisateur. Un utilisateur qui navigue à 23h50 et achète à 00h10 devrait avoir une seule session de 20 minutes, pas deux sessions séparées.
Si votre modèle utilise des tables GA4 partitionnées (events_YYYYMMDD) et partitionne par la date de la table, le test de session à cheval sur minuit peut légitimement échouer — auquel cas le test documente cette limitation connue.
Ce qu’il faut tester au-delà des bases
Pour un modèle de sessionisation en production, pensez également à tester :
- Écarts très courts : deux événements distants de 29 minutes devraient appartenir à la même session. Deux événements distants de 31 minutes ne devraient pas.
- Plusieurs utilisateurs au même horodatage : l’utilisateur 1 et l’utilisateur 2 ont tous deux des événements à la même microseconde. L’isolation des partitions doit tenir.
- Sessions à événement unique : un utilisateur avec exactement un événement devrait avoir une session de 0 seconde de durée et un event_count de 1.
- Seuils configurables : si votre seuil d’inactivité est une variable (
var('session_timeout_minutes', 30)), testez avec des valeurs surchargées pour vérifier que la variable est réellement utilisée.
Chacun de ces tests vérifie une hypothèse spécifique que votre logique de sessionisation fait. Quand la logique change — par exemple, vous passez de 30 à 20 minutes, ou ajoutez une limite de session basée sur la page — les tests montrent immédiatement quelles hypothèses tiennent encore.