Sessionization no GA4 e BigQuery: como ler sessões de verdade

Sessão no GA4 parece objeto simples até você exportar eventos para BigQuery e descobrir que session_id, ga_session_id e lógica da UI não são a mesma coisa. Sessionization — agrupar hits em sessões com regras consistentes — é pré-requisito para funis, atribuição e análises que a interface do GA4 não entrega ou entrega com definição opaca. No warehouse, você controla timeout, exclusões e critérios — e assume responsabilidade quando divergir da UI.
Como o GA4 define sessão nativamente?
GA4 agrupa eventos em sessões com base em ga_session_id gerado client-side, com timeout de 30 minutos de inatividade (configurável via session_timeout). Nova campanha com UTM pode iniciar nova sessão dependendo de configuração. Sessões encerram à meia-noite no timezone da propriedade.
Na UI, métricas de sessão refletem essa lógica. No BigQuery export, cada evento traz ga_session_id, session_engaged e parâmetros de traffic source. Porém, export não entrega tabela de sessões pronta — você recebe eventos e reconstrói sessões via SQL.
O que muda quando você sessioniza no BigQuery?
Controle total: timeout custom (45 min para SaaS, 10 min para e-commerce flash), exclusão de eventos (internal traffic, bots), regras de atribuição de campanha à sessão, e sessões cross-device se tiver user_id. Também responsabilidade total: número não baterá com UI se lógica diferir — e isso precisa ser comunicado.
Casos que exigem sessionization custom: funil multi-step com eventos específicos, sessão definida por engagement mínimo (2+ pageviews), atribuição first-touch por sessão, análise de landing page excluindo redirects. UI do GA4 não flexibiliza essas regras.
- ga_session_id como base, com override se necessário
- Timeout de inatividade documentado
- Regra de meia-noite e timezone explícitos
- Critério de sessão engajada (session_engaged ou custom)
- Tratamento de user_id para stitch cross-session
Qual SQL prático para sessionization no export GA4?
Abordagem comum: window functions sobre events_* particionado por user_pseudo_id (e user_id quando disponível). LAG(event_timestamp) para calcular gap; quando gap > timeout ou data muda, incrementar session_number. Gerar surrogate session_key = hash(user + session_number + date).
Alternativa: usar ga_session_id nativo quando lógica default basta — mais simples, alinha com UI. Custom sessionization só quando regra de negócio exige. Sempre compare totais com UI GA4 para mesma janela antes de confiar.
| Abordagem | Prós | Contras | Quando usar |
|---|---|---|---|
| ga_session_id nativo | Alinha com UI, SQL simples | Sem flexibilidade de timeout | Reporting padrão |
| Custom timeout | Controle de inatividade | Diverge da UI | SaaS com sessões longas |
| Engagement-based | Filtra bounce noise | Definição subjetiva | Funis de qualidade |
| user_id stitch | Cross-device parcial | Requer login | Produtos autenticados |
| Campanha-forced new session | Atribuição agressiva | Infla session count | Testes de atribuição |
Como tratar engaged session vs sessão bruta?
GA4 define engaged session como ≥10s, ou ≥2 pageviews, ou conversion event. Parâmetro session_engaged=1 no export. Para funis de qualidade, filtre engaged sessions — sessões de 1 segundo distorcem bounce rate e pages/session.
Documente qual definição alimenta qual dashboard. Executivo pode ver sessões totais; operacional de CRO vê engaged only. Misturar definições no mesmo relatório gera confusão.
Quais armadilhas de sessionization no BigQuery?
Timezone: event_timestamp é UTC; sessão por ‘dia’ depende de conversão correta. Particionamento: queries sem filtro de _TABLE_SUFFIX escaneiam tudo — custo e timeout. Unnest de event_params sem cuidado multiplica rows. user_pseudo_id muda com consent — sessões fragmentadas pós-denial.
Cross-device sem user_id: não tente sessionizar como se tivesse identidade unificada. Dois dispositivos = duas sessões, dois usuários — a menos que login com user_id unifique.
Como validar sessões modeladas vs GA4 UI?
Compare sessions, engaged_sessions e average_session_duration por date para últimos 7 dias. Divergência <5% é aceitável (processing delay, definição). >10% exige investigação: timezone, filtros, internal traffic exclusion na UI mas não no SQL.
Automatize reconciliação diária como dbt test. Quando divergir, marketing precisa saber qual número é ‘oficial’ — documente que warehouse usa definição X, UI usa definição Y.
- Reconciliar sessions by date por 7 dias
- Verificar timezone conversion
- Confirmar filtros (internal, bots) consistentes
- Testar impacto de consent na contagem
- Documentar divergência aceitável e causa
Como evoluir de sessão para jornada e funil?
Com sessões modeladas, construa funil: within session (step1 → step2 → step3) ou cross-session (sessão 1 view → sessão 2 signup). Window functions PARTITION BY session_key ORDER BY event_timestamp. Para cross-session, PARTITION BY user_pseudo_id com janela de atribuição (7d, 30d).
Materialize tabela fct_sessions com session_key, start/end timestamp, landing page, campaign, engaged flag, conversion flag. Funis e atribuição consomem essa fato — não re-sessionizam a cada query.
Checklist prático
- Definição de sessão documentada (timeout, engaged, timezone)
- Abordagem escolhida: ga_session_id nativo vs custom
- SQL de sessionization versionado em dbt model
- Reconciliação diária sessions vs GA4 UI
- Tabela fct_sessions materializada com grain claro
- Tratamento de user_id para produtos autenticados
- Particionamento e filtro de data em toda query
- Comunicação ao time sobre divergência vs UI
Erros comuns
- Assumir session_id do export = sessão da UI sem validar
- Query full-scan em events_* sem filtro de partição
- Sessionizar cross-device sem user_id
- Ignorar timezone na agregação diária
- Unnest event_params gerando duplicatas na sessão
- Mudar regra de sessionization sem versionar dashboards
Sessão no GA4 não bate com sua lógica?
A DataScroll modela sessões no BigQuery com critérios claros para análises que a UI do GA4 não resolve.