> ## Documentation Index
> Fetch the complete documentation index at: https://lifters.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Runbook: Incidentes de Session Replay (M8 Phase 2)

> Detecção, diagnóstico e mitigação dos 8 cenários cobertos pelos alertas do pipeline rrweb → R2 → ClickHouse → backend/.

# Runbook: Incidentes de Session Replay

**Serviço afetado:** `webanalytics/` (SDK), `events/` (ingest), `backend/` (analytics), Cloudflare R2 (blob), ClickHouse (`atlas.tbt_sdk_replay_events` + `atlas.mv_replay_sessions` + `atlas.audit_replay_views`).
**Provider observability:** New Relic — dashboard "Atlas — M8 Session Replay" + policy "Atlas — M8 Session Replay" (8 condições).
**Owner primário:** SRE on-call. Escalation: Tech Lead Atlas → CTO.

***

## Mapa de alertas → ação

| # | Alerta                                  | Severidade  | Canal         | Runbook                                    |
| - | --------------------------------------- | ----------- | ------------- | ------------------------------------------ |
| 1 | `Replay DLQ rate > 10/min`              | crítica     | PagerDuty SRE | [§1](#1-dlq-rate-alta)                     |
| 2 | `Replay R2 PutObject p99 > 5s`          | crítica     | PagerDuty SRE | [§2](#2-r2-latencia-p99-alta)              |
| 3 | `Replay tenant mismatch surge`          | crítica     | #security     | [§3](#3-tenant-mismatch-surge)             |
| 4 | `Replay active sessions > 10k`          | warning     | #platform     | [§4](#4-active-sessions-acima-do-baseline) |
| 5 | `Replay audit log failures > 5/min`     | warning     | #ops          | [§5](#5-audit-log-failure-rate)            |
| 6 | `Replay marketing role hit daily quota` | warning     | #ops          | [§6](#6-marketing-quota-batida)            |
| 7 | `Replay ingest 429 spike`               | warning     | #frontend     | [§7](#7-ingest-429-spike)                  |
| 8 | `Replay bundle gzip > 65KB`             | informativa | #frontend     | [§8](#8-bundle-gzip-acima-do-gate)         |

***

## 1. DLQ rate alta

**Sintoma:** `atlas.sdk-replay-snapshot-items.dlq` recebendo > 10 mensagens/min.
Significa que o Kafka producer no `events/` está falhando para publicar metadata
(R2 PutObject já passou — chunk está em R2, mas Gold MV não vai populá-lo).

**Diagnóstico:**

```bash theme={null}
# Detectar
docker exec atlas-kafka kafka-console-consumer \
  --bootstrap-server localhost:9092 \
  --topic atlas.sdk-replay-snapshot-items.dlq \
  --from-beginning --max-messages 20 \
  | jq '.error, .chunk.chunk_id, .failed_at'

# Quantos chunks órfãos (R2 sem CH)?
clickhouse-client --query "
  SELECT count() AS ch_count FROM atlas.tbt_sdk_replay_events
  WHERE ingest_at >= now() - INTERVAL 1 HOUR
"
# Comparar com count de objetos em R2 no mesmo intervalo:
aws --endpoint-url=$R2_ENDPOINT s3 ls s3://atlas-replay-stg/ --recursive \
  | awk '$1 >= "2026-05-19"' | wc -l
```

**Causas prováveis:**

* MSK broker queue cheio → revisar `atlas_msk_kafka_data_logs_disk_used`.
* Schema drift entre producer e consumer (CH Kafka Engine columns mismatch).
* Tópico não existe (`atlas.sdk-replay-snapshot-items` foi deletado).

**Resolver:**

1. `events/` logs: `kubectl logs deploy/atlas-events -n stg --tail=200 | grep replay`.
2. Recriar tópico se ausente: `make kafka-topics` (em `events/`).
3. Reconciliação manual de chunks órfãos (R2 sem CH): rodar
   `scripts/reconcile_orphan_replay_chunks.sh` (TODO Phase 2.5).
4. Se persistir > 30min: rollback do producer commit + páginar Tech Lead.

***

## 2. R2 latência p99 alta

**Sintoma:** `atlas_sdk_replay_r2_put_duration_seconds` p99 > 5s.

**Diagnóstico:**

```bash theme={null}
# Cloudflare R2 status
curl -s https://www.cloudflarestatus.com/api/v2/status.json | jq '.status'

# Testar latência manual do events/ → R2
curl -w "@curl-format.txt" -X PUT \
  --aws-sigv4 "aws:amz:auto:s3" \
  --user "$R2_ACCESS_KEY_ID:$R2_SECRET_ACCESS_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @/tmp/test-chunk.gz \
  "$R2_ENDPOINT/atlas-replay-stg/probe-$(date +%s).bin"
```

**Causas prováveis:**

* Incident Cloudflare (regional).
* Rate-limit R2 (busy bucket — improvável).
* ECS task `events/` em CPU throttling → menos goroutines disponíveis para retry.

**Resolver:**

1. Verificar [https://www.cloudflarestatus.com](https://www.cloudflarestatus.com).
2. Se Cloudflare OK, escalar tasks `events/` (CPU/memory) via Terraform.
3. Se latência persistir mesmo após escalar: abrir ticket Cloudflare Support.
4. **Não há fallback automático para S3.** SDK continua tentando — eventualmente
   o sender exponential backoff vai falhar e o chunk é perdido. Em incidente
   longo, considerar desabilitar replay temporariamente via env flag em
   `events/` (CONFIG `R2_BUCKET=""` quebra o init e habilita o
   `disabledReplayService` que retorna 502 imediato — SDK descarta sem retry).

***

## 3. Tenant mismatch surge

**Sintoma:** > 5 ocorrências de `replay_chunk_not_found` com flag de tenant
mismatch em 5 minutos. Indica possível tentativa de cross-org leakage.

**Diagnóstico:**

```bash theme={null}
# Identificar atores
clickhouse-client --query "
  SELECT actor_user_id, actor_role, organization_id, replay_id, count() AS attempts
  FROM atlas.audit_replay_views
  WHERE event_time >= now() - INTERVAL 1 HOUR
  GROUP BY actor_user_id, actor_role, organization_id, replay_id
  HAVING attempts > 3
  ORDER BY attempts DESC LIMIT 20
"

# Backend logs com correlation id
kubectl logs deploy/atlas-backend -n stg --tail=500 \
  | grep "replay_chunk_not_found\|replay_not_found" | tail -50
```

**Causas prováveis:**

* Bug benigno (frontend cachando old replay\_id de outra brand).
* Bot/scraper tentando IDs random.
* Insider threat (compliance/CRM testando IDs de outras orgs).

**Resolver:**

1. Cruzar `actor_user_id` com `users` table em Postgres — usuário legítimo?
2. Se padrão suspeito (IDs sequenciais, alta taxa): suspender token via Auth0.
3. Documentar incidente em `Security/Incidents` (PRP obrigatório).
4. Se confirmado access scan, **NÃO** apenas suspender — preservar audit log
   por 365d (TTL atual) + extrair CSV para evidência.

***

## 4. Active sessions acima do baseline

**Sintoma:** `atlas_sdk_replay_active_sessions` > 10k em 5min. Não é
incidente — é capacity planning.

**Resolver:**

1. Verificar custo R2 projetado: `total_size_bytes / 86400 * 30 * 0.015` USD/mês.
2. Se ultrapassar budget: reduzir `sampleRate` para 0.05 nos brands mais
   pesados via remote-config (Phase 2.5) ou patch SDK por brand.
3. Considerar habilitar `trigger.urlMatchRegex` no SDK init para grandes
   brands (`/(deposito|saque)` reduz 60-90% storage).

***

## 5. Audit log failure rate

**Sintoma:** `atlas_replay_audit_records_failed_total` > 5/min.

**Diagnóstico:**

```bash theme={null}
# CH overload?
clickhouse-client --query "
  SELECT
    event_time,
    query_duration_ms,
    exception_code,
    substring(query, 1, 100) AS query_preview
  FROM system.query_log
  WHERE query LIKE '%audit_replay_views%'
    AND event_time >= now() - INTERVAL 30 MINUTE
    AND exception_code != 0
  ORDER BY event_time DESC LIMIT 20
"
```

**Causas prováveis:**

* CH em backpressure (memory/CPU).
* `audit_replay_views` table com TTL travado.
* Network entre `backend/` ECS e ClickHouse Cloud.

**Resolver:**

1. INSERT falha é best-effort — não bloqueia o 200 do GET manifest. Impacto
   imediato = audit gap. Compliance não consegue auditar viewers.
2. CH overload → escalar service em `clickhouse-cloud-dashboard` provisioning.
3. Se persistir > 1h, escalar Tech Lead.

***

## 6. Marketing quota batida

**Sintoma:** ator `marketing` chegou em ≥ 50 views/dia (limite hardcoded
Phase 2). Não é incidente — é signal de uso.

**Resolver:**

* Comportamento esperado: ator recebe 429 nos próximos GETs.
* Se padrão recorrente, abrir ticket Product para considerar quota maior.
* **NÃO** aumentar quota ad-hoc — Phase 2.5 vai mover para backoffice config.

***

## 7. Ingest 429 spike

**Sintoma:** `outcome="rejected_ratelimit"` rate > 20/min. SDK está sendo
throttled pelo rate-limit per `replay_id` (12 chunks/min default).

**Causas prováveis:**

* SDK bug — sender entrou em retry loop sem backoff.
* Cliente final em rede flaky → retries normais
* Operador customizando intervalo de chunk para muito agressivo.

**Resolver:**

1. Inspecionar SDK no browser de teste (DevTools Network → ver intervalo
   entre POSTs):
   ```
   tail -f /tmp/devtools-har | grep sdk-replay-snapshots
   ```
2. Se intervalo \< 5s: bug do SDK (REPLAY\_BATCH\_FLUSH\_MS = 10s default).
3. Se intervalo > 5s mas spike de 429: muitos replays simultâneos no mesmo
   `replay_id` — improvável; investigar collision de IDs.

***

## 8. Bundle gzip acima do gate

**Sintoma:** `atlas_sdk_replay_bundle_size_bytes_gzipped` > 65 KB.
Informativo — CI já bloqueia merges via `npm run size:replay`.

**Resolver:**

1. Verificar último commit no `webanalytics/` que ultrapassou.
2. Reverter ou otimizar:
   * Remove dependências não-tree-shakeable.
   * Tightening terser passes.
   * Switch para `CompressionStream` se ainda usando pako (já default em
     Wp-37).

***

## Operações manuais

### Purge LGPD (direito ao esquecimento)

```bash theme={null}
USER_EXT_ID="user_abc_123"

# 1. Levantar todos os replay_id do user
REPLAY_IDS=$(clickhouse-client --query "
  SELECT DISTINCT replay_id, organization_id, brand_id
  FROM atlas.mv_replay_sessions
  WHERE user_ext_id = '$USER_EXT_ID'
" --format JSONEachRow)

# 2. Para cada replay_id, deletar R2 prefix + CH rows + audit references
echo "$REPLAY_IDS" | jq -c '.' | while read -r row; do
  rp=$(echo "$row" | jq -r '.replay_id')
  org=$(echo "$row" | jq -r '.organization_id')
  brand=$(echo "$row" | jq -r '.brand_id')

  # R2: deletar prefix completo do replay
  aws --endpoint-url=$R2_ENDPOINT s3 rm --recursive \
    "s3://atlas-replay-stg/org=$org/brand=$brand/" \
    --exclude "*" --include "*/replay=$rp/*"

  # CH: deletar metadados
  clickhouse-client --query "
    ALTER TABLE atlas.tbt_sdk_replay_events DELETE
    WHERE organization_id = '$org' AND replay_id = '$rp'
  "
done

# 3. Registrar a operação em audit (não é deleção — é registro do delete)
clickhouse-client --query "
  INSERT INTO atlas.audit_replay_views VALUES
  (now(), 'sre@atlas.io', 'admin', 'all', 'all', 'BULK_DELETE',
   '$USER_EXT_ID', 'LGPD_RIGHT_TO_ERASURE', NULL, NULL)
"
```

### Reconciliação chunks órfãos (R2 sem CH)

Acontece quando R2 PutObject sucede mas Kafka publish falha (e o DLQ
também não recupera). Result: blob em R2 sem metadata no Gold MV.

```bash theme={null}
# Listar R2 objects sem correspondência em CH
aws --endpoint-url=$R2_ENDPOINT s3 ls --recursive \
  s3://atlas-replay-stg/ \
  | awk '{print $4}' > /tmp/r2-keys.txt

clickhouse-client --query "
  SELECT s3_key FROM atlas.tbt_sdk_replay_events
" > /tmp/ch-keys.txt

comm -23 <(sort /tmp/r2-keys.txt) <(sort /tmp/ch-keys.txt) > /tmp/orphans.txt
wc -l /tmp/orphans.txt
```

Em Phase 2 não há reconciliation automática. Phase 2.5 vai shippar:
`scripts/reconcile_orphan_replay_chunks.go` que lê os arquivos e injeta
metadata reconstruída em CH.

### Rollback emergencial (vazamento PII)

```bash theme={null}
# 1. Desabilitar replay no SDK (operador precisa fazer no init config)
# Notificação para todos operators OBRIGATÓRIA via canal #onboarding-ops.

# 2. Cortar ingest no events/ via env var
kubectl set env deploy/atlas-events -n prod R2_BUCKET=
# Sem R2 creds, disabledReplayService retorna 502 imediato. SDK descarta.

# 3. Inspecionar últimas 24h
clickhouse-client --query "
  SELECT replay_id, user_ext_id, brand_id, entry_url
  FROM atlas.mv_replay_sessions
  WHERE ingest_at >= now() - INTERVAL 24 HOUR
  LIMIT 1000
"

# 4. Decidir purge global vs targeted. Documentar no incidente.
```
