Article - 28 mars 2024
Corréler des données évènementielles autours d'un identifiant virtuel, l'exemple des sessions Google Analytics.
A travers cet article, je vais vous montrer comment corréler des données évènementielles autours d'un identifiant virtuel.
Dans mon exemple, les données évènementielles sont les requêtes de nos utilisateurs, où nous souhaitons reproduire la notion de session définit par Google Analytics.
Voici l'interprétation que l'on retiendra pour une session :
Google Analytics définit une session comme un groupe de données enregistrées au cours d'une seule visite sur le site.
Une session commence lorsque l'utilisateur arrive pour la première fois sur le site et se termine après 30 minutes d'inactivité suivant la dernière requête du même utilisateur.
Les données brutes représentent plusieurs dizaines de millions de lignes, réparties au sein de plusieurs fichiers .parquet dont voici la structure utile à notre sujet :
| request_id (int64) | date (timestamp) | visitor_id (varchar) |
|---|---|---|
| 69284875 | 2023-09-01 14:00:14.231158 | e29c8ec7f724 |
| 69285207 | 2023-09-01 14:01:44.634173 | d08bed591825 |
| 69285214 | 2023-09-01 14:13:25.425332 | e29c8ec7f724 |
| 71234525 | 2023-09-02 09:34:35.543233 | e29c8ec7f724 |
Le visitor_id est un identifiant que l'on attribut à un utilisateur par l'intermédiaire d'un cookie, il est conservé sur une période dépassant largement la notion de session.
L'objectif est donc d'ajouter une nouvelle colonne contenant un session_id commun à l'ensemble des requêtes répondant à la notion d'une session.
Le résultat final doit ressembler à cela :
| request_id (int64) | date (timestamp) | visitor_id (varchar) | session_id (varchar) |
|---|---|---|---|
| 69284875 | 2023-09-01 14:00:14.231158 | e29c8ec7f724 | session-123 |
| 69285207 | 2023-09-01 14:01:44.634173 | d08bed591825 | session-456 |
| 69285214 | 2023-09-01 14:13:25.425332 | e29c8ec7f724 | session-123 |
| 71234525 | 2023-09-02 09:34:35.543233 | e29c8ec7f724 | session-789 |
Le processus est découpé en plusieurs étapes, organisées techniquement par des CTE.
lag_time
start_session_id. Pour déterminer cette première requête, il suffit de voir si la requête précédente a plus de 30min ou s'il n'y a pas de précédent.session_id dont la valeur est fonction de deux cas :
start_session_id, on utilise cette valeur.start_session_id non NULL des requêtes précédentes.Pour pouvoir se déplacer sur la requête précédente, il faut utiliser la fonction lag des WINDOW Functions et ignorer les valeurs NULL, ce que DuckDB nous permet de faire 😇.
with
source as (
select
id as request_id,
date,
visitor_id
from 'seeds/tracking-visits/2023-*.parquet'
),
requests as (
select
request_id,
visitor_id,
date,
date_diff(
'minute',
lag(date) over (
partition by visitor_id
order by date
),
date
) as lag_time -- minutes séparant deux requêtes consécutives du même utilisateur.
from source
),
start_session_flagged as (
select
*,
if(
(lag_time is null or lag_time > 30),
sha256(concat(date, visitor_id)),
null
) as start_session_id -- création d'un identifiant prédictible pour le début de session
from requests
),
session_flagged as (
select
*,
if (
start_session_id is not null,
start_session_id,
lag(start_session_id, 1 IGNORE NULLS) OVER (
partition by visitor_id
order by date
)
) as session_id -- transposition de l'identifiant de session
from start_session_flagged
)
select * EXCLUDE (lag_time, start_session_id)
from session_flagged
J'utilise DuckDB depuis plusieurs mois pour réaliser deux types de traitement :
Comme à chaque fois que je manipule des millions de ligne en moins de 10 secondes, j'ai cet effet Wahou si caractéristique de DuckDB.
Pour rappel : 30M de lignes sur 12 fichiers .parquet qui représentent 1.5GB sur disque, le processus ci-dessus c'est moins de 8 secondes 🤩.
Outre ces performances de folies, j'ai pu faire les traitements directement sur mon environnement de travail sans aucune liaison avec Internet, niveau productivité c'est imbatable !