Contexto
Hoje cada integration-* (discord, twitch, devto, github) mistura OAuth + Transport + ETL num só módulo, e o ETL escreve direto nas tabelas do activity, no mesmo Postgres transacional do login/painéis/gamification/identity.
Medições em produção:
| Tabela |
Linhas |
Tamanho |
messages |
3,27M |
2,3 GB |
message_embeds |
~1M |
907 MB |
voice_messages |
394k |
67 MB |
| demais activity |
— |
~150 MB |
| total |
|
~3,4 GB |
Taxa de ingestão real: ~800 msg/dia (~0,01 escrita/s). Ou seja, a dor não é throughput — é massa fria acumulada coabitando com o banco transacional + agregações temporais caras. Some-se a isso a entrada planejada de Instagram e WhatsApp (além de Discord/Twitch).
Decisão
Extrair uma nova aplicação Laravel — ingestion — como ponto único de ingestão de todos os provedores, dona de um datastore TimescaleDB. O app principal vira consumidor do resultado e larga os ~3,4 GB.
Decisão completa, alternativas rejeitadas e consequências: docs/adr/0001-extract-ingestion-context.md (ADR system-wide).
Fronteira
PROVEDORES (ao vivo) ─ Discord WS · Twitch EventSub · Instagram/WhatsApp webhooks
▼
┌──────────────────────────────────────────┐
│ INGESTION (app Laravel separada) │
│ Raw Landing → Transform → Serving │
│ chave: (provider, external_account_id) │
│ TimescaleDB: hypertables + continuous agg │
└───────┬───────────────────────────┬───────┘
PULL │ connection read-only │ PUSH: fila Redis
│ (agregados / histórico) │ (ModerationContentDTO)
▼ ▼
Painéis/Gamification/Portal Moderation → Identity (soberano)
Decisões travadas
Glossário
| Termo |
Definição |
| Raw Landing |
Payload cru do provedor (JSONB, append-only), como chega. Permite replay. |
| Transform |
Normaliza + resolve conta-de-provedor + enriquece (hoje em ETL/Actions). |
| Serving |
O resultado consumido pelo app principal: hypertables normalizadas + continuous aggregates. |
| Provider Account |
(provider, external_account_id, tenant) — a chave de ingestão. Cega ao He4rt User. |
Termo "data lake" / "lake" aposentado — o store é normalizado, schema-on-write.
Fora de escopo desta issue (detalhe de execução — issues próprias)
- Modelagem das hypertables,
time_bucket das continuous aggregates, COUNT(DISTINCT) aproximado (hyperloglog) para "unique users".
- Migração das tabelas de referência (
discord_guilds/channels/roles) junto com a ingestão.
- Idempotência do PUSH (dedupe por
provider_message_id).
- Mecânica do dual-write: feature flag, reconciliação de paridade, gatilho de cutover.
Consequências
- App principal acopla ao schema da Timescale (views materializadas = contrato estável).
- Reescrever ~7
Query classes do dashboard + ranking: external_identity_id → external_account_id.
- Connector Saloon (Discord/Twitch) duplicado entre as camadas.
- Linhas históricas backfilladas são não-reprocessáveis (sem raw original).
Contexto
Hoje cada
integration-*(discord, twitch, devto, github) mistura OAuth + Transport + ETL num só módulo, e o ETL escreve direto nas tabelas doactivity, no mesmo Postgres transacional do login/painéis/gamification/identity.Medições em produção:
messagesmessage_embedsvoice_messagesTaxa de ingestão real: ~800 msg/dia (~0,01 escrita/s). Ou seja, a dor não é throughput — é massa fria acumulada coabitando com o banco transacional + agregações temporais caras. Some-se a isso a entrada planejada de Instagram e WhatsApp (além de Discord/Twitch).
Decisão
Extrair uma nova aplicação Laravel —
ingestion— como ponto único de ingestão de todos os provedores, dona de um datastore TimescaleDB. O app principal vira consumidor do resultado e larga os ~3,4 GB.Fronteira
Decisões travadas
(provider, external_account_id, tenant); nova camada cega ao He4rt User;identitypermanece soberano (ADR identity/0001).integration-*se parte: OAuth + enforcement Transport ficam; só Transport(ETL) migra.Glossário
ETL/Actions).(provider, external_account_id, tenant)— a chave de ingestão. Cega ao He4rt User.Fora de escopo desta issue (detalhe de execução — issues próprias)
time_bucketdas continuous aggregates,COUNT(DISTINCT)aproximado (hyperloglog) para "unique users".discord_guilds/channels/roles) junto com a ingestão.provider_message_id).Consequências
Queryclasses do dashboard + ranking:external_identity_id→external_account_id.