Fundando um serviço de analytics ao mover relatórios para fora do banco transacional
Fundei um novo serviço de relatórios que separa leituras analíticas pesadas do banco de dados transacional, resolvendo timeouts de indicadores, enquadrando o trade-off OLTP-vs-OLAP, revisando criticamente a decisão de arquitetura, e construindo o walking skeleton e sua camada de dados.
impactoFundei o serviço de relatórios (autora fundadora) com leituras analíticas separadas do banco transacional, que é a correção estrutural para os timeouts de indicadores.Entreguei o serviço base integrado ao warehouse analítico, com um modelo de dados curado, consciente de custo, isolado por tenant, e rotinas de carga em infrastructure-as-code.Tornei a decisão de arquitetura revisável e multifuncional ao enquadrá-la como uma matriz explícita com uma comparação escrita, uma revisão de segurança/privacidade, e uma recomendação preliminar, revisada e aprovada por sete stakeholders entre engenharia e o time de plataforma em vez de decidida isoladamente.Identifiquei e fechei um risco de latência antes de ir para produção, revisando minha própria recomendação de primeira passagem ao identificar que o warehouse analítico não era inerentemente de baixa latência, transformando um possível incidente pós-lançamento em um requisito de design.Qualitativo: arquitetura analítica em rollout para eliminar os timeouts; latência final do indicador ainda não capturada como um número de antes/depois. <!-- TODO: adicionar números de latência quando disponíveis -->
01
Contexto
Esta é minha evidência mais forte de design de sistemas estratégico, no nível de arquitetura de dados, e de ownership greenfield. A maior parte do meu trabalho estende sistemas existentes; aqui eu comecei um, o que significou tomar decisões fundamentais sobre runtime, processo de release, modelo de dados, modelo de custo e isolamento de tenant que são caras de reverter depois.
Isso mostra que consigo manter uma decisão na altitude de *onde uma classe de carga de trabalho pertence* (armazenamento transacional vs. warehouse analítico) em vez da altitude de uma única query; que decomponho uma decisão conflada em eixos independentes em vez de comparar opções empacotadas; e que reviso minha própria análise anterior quando encontro uma lacuna real nela, em vez de defender a primeira versão. É também minha evidência mais clara de que incorporo segurança e privacidade em uma decisão de arquitetura como um insumo de primeira classe em vez de um checklist aplicado depois, e que a própria decisão foi revisada e aprovada por stakeholders entre engenharia e o time de plataforma, não tomada isoladamente.
02
Problema
Um conjunto de indicadores voltados ao cliente agregava grandes volumes de dados de inspeção. Essas agregações rodavam como queries analíticas pesadas, vários joins mais cálculos em tempo de execução (agregações, porcentagens, contagens), diretamente contra o banco de dados transacional que também servia tráfego de aplicação ao vivo. Conforme os dados cresciam, os indicadores começaram a dar timeout: o schema tinha sido desenhado para acesso transacional, não para leituras analíticas, e a carga analítica competia com a carga transacional com quem dividia o armazenamento. Otimizar queries individuais era tratar o sintoma; a carga de trabalho estava no lugar errado, e uma funcionalidade de relatórios relacionada estava prestes a precisar das mesmas agregações, o que só aumentaria a contenção.
03Restrições
+
—A decisão tinha dois eixos conflados. Uma passagem anterior por essa decisão comparava apenas duas alternativas empacotadas, cada uma mudando *tanto* qual serviço é dono da lógica de relatórios *quanto* qual banco de dados a sustenta, o que dificultava saber qual eixo estava de fato conduzindo cada trade-off.
—A correção óbvia subestimava um risco real. O warehouse analítico em consideração não é, por natureza, um armazenamento de baixa latência, já que toda query tem um piso de centenas de milissegundos a segundos. Servir uma tela síncrona voltada ao cliente diretamente dele arriscava trocar timeouts por lentidão em vez de corrigi-los.
—Fundar um serviço significa escolhas quase irreversíveis. Runtime, estrutura de projeto, ferramentas de release e o modelo de dados são baratos de escolher e caros de mudar depois que código e dados se acumulam.
—O espaço de trade-offs era amplo e multifuncional. Isolamento de carga, isolamento de deploy, complexidade operacional, consistência eventual, multi-tenancy, custo e vendor lock-in interagiam entre si, e a decisão precisava ser legível o suficiente para que stakeholders entre engenharia e o time de plataforma a revisassem e aprovassem.
—Custo é uma restrição de primeira classe em analytics. Um warehouse analítico cobra por dados escaneados, então uma query sem filtro é tanto um problema de custo quanto, no extremo, um problema de disponibilidade.
—Os dados cruzam uma fronteira de privacidade. A cópia analítica carregaria dados operacionais multi-tenant, incluindo campos que podem identificar a pessoa que realizou uma inspeção, tornando proteção de dados uma questão de arquitetura em vez de algo parafusado depois.
04
Decisão
Comecei pelo trade-off, corrigi minha própria análise quando tinha uma lacuna, e depois reduzi o risco da construção.
—Decompus a decisão em uma matriz 2×2 explícita. Qual serviço é dono da lógica de relatórios (o serviço transacional existente vs. um novo) cruzado com qual banco de dados a sustenta (um armazenamento relacional ajustado vs. um warehouse analítico), em vez de comparar alternativas empacotadas. Isolar os dois eixos tornou cada trade-off legível por si só: isolamento de carga acabou dependendo quase inteiramente do eixo do serviço, enquanto adequação analítica e custo dependiam quase inteiramente do eixo do banco de dados.
—Voltei atrás e corrigi minha própria recomendação anterior. Depois da primeira passagem, identifiquei que o warehouse analítico que eu estava recomendando não é um armazenamento de baixa latência por natureza, e que servir uma tela síncrona voltada ao cliente diretamente dele poderia trocar um tipo de timeout por outro tipo de lentidão. Revisei a decisão para exigir uma camada de serving explícita em vez de consultar o warehouse a cada requisição.
—Negociei uma fronteira de ownership explícita com o time de plataforma. O pipeline de ingestão deles já movia eventos operacionais para o warehouse analítico; escopei o novo serviço para ser dono apenas da camada de agregação, da API e do cache sobre ela, com uma camada bem definida de tabelas limpas como o contrato entre os dois domínios, em vez de duplicar ingestão, retry e dead-lettering que o time de plataforma já tinha construído.
—Rodei uma revisão completa de risco de segurança e privacidade como parte da mesma decisão, cobrindo confidencialidade (acesso de menor privilégio e autorização por tenant em cada leitura), integridade (deduplicando eventos que podem chegar mais de uma vez ou fora de ordem), disponibilidade (uma query sem filtro se torna um risco de custo e disponibilidade em um warehouse cobrado por escaneamento), e privacidade (campos que podem identificar a pessoa que realizou uma inspeção), com uma mitigação concreta para cada uma, respaldada por pesquisa comparativa de stacks e um benchmark de mercado para o restante da decisão.
—Construí um walking skeleton, a versão mais fina possível de ponta a ponta do serviço (um runtime enxuto, scaffolding de projeto, containerização, e uma conexão funcional com o warehouse analítico), para provar o formato antes de investir em funcionalidades. Sou a autora fundadora do repositório.
—Modelei tabelas analíticas curadas para custo e isolamento de tenant, incluindo um snapshot diário e uma view do estado mais recente, particionadas por data e clusterizadas com o identificador de tenant primeiro para que queries comuns escaneiem menos dados e a query de um tenant não possa ver as linhas de outro.
—Criei o dataset, as tabelas e as rotinas de carga como infrastructure-as-code, incluindo a lógica de consistência e incremental diária, para que a camada de dados seja reproduzível em vez de construída manualmente.
05Trade-offs
+
—Separar leituras analíticas do OLTP em vez de ajustar as queries transacionais. Mover a carga de trabalho remove a causa raiz, contenção de recursos em um armazenamento construído para transações, enquanto ajustar apenas adia o problema. Custo aceito: um segundo armazenamento de dados e um pipeline para mantê-lo atualizado.
—Uma camada de serving materializada com um cache na frente dela, em vez de consultar o warehouse diretamente a cada requisição. Custa uma peça móvel extra, materialização agendada e invalidação de cache, mas é o que de fato corrige uma tela síncrona voltada ao cliente; consultar um warehouse cobrado por escaneamento ao vivo a cada requisição teria recriado o problema de latência em outro lugar.
—Um teto rígido de custo que falha a query de forma fechada, em vez de confiar que toda query será escrita eficientemente. Uma query sem os filtros exigidos é rejeitada antes de rodar, em vez de ter permissão para escanear (e cobrar por) uma tabela inteira. Custa uma query rejeitada ocasional; compra uma conta limitada e previsível em vez de um incidente silencioso de custo ou disponibilidade.
—Ser dona apenas da camada de agregação em vez de ser dona da ingestão de ponta a ponta. A alternativa rejeitada teria reconstruído os consumers de mensageria, retry e dead-lettering que o pipeline do time de plataforma já fornecia. A superfície operacional extra, e a migração de dados históricos que viria junto, não valia o controle marginal que compraria.
—Consistência eventual para relatórios em vez de atualização estrita. Relatórios toleram dados brevemente desatualizados, então um read model atualizado em uma agenda é aceitável, e bem mais barato, do que manter uma cópia analítica estritamente sincronizada.
06
Impacto
—Fundei o serviço de relatórios (autora fundadora) com leituras analíticas separadas do banco transacional, que é a correção estrutural para os timeouts de indicadores.
—Entreguei o serviço base integrado ao warehouse analítico, com um modelo de dados curado, consciente de custo, isolado por tenant, e rotinas de carga em infrastructure-as-code.
—Tornei a decisão de arquitetura revisável e multifuncional ao enquadrá-la como uma matriz explícita com uma comparação escrita, uma revisão de segurança/privacidade, e uma recomendação preliminar, revisada e aprovada por sete stakeholders entre engenharia e o time de plataforma em vez de decidida isoladamente.
—Identifiquei e fechei um risco de latência antes de ir para produção, revisando minha própria recomendação de primeira passagem ao identificar que o warehouse analítico não era inerentemente de baixa latência, transformando um possível incidente pós-lançamento em um requisito de design.
—Qualitativo: arquitetura analítica em rollout para eliminar os timeouts; latência final do indicador ainda não capturada como um número de antes/depois. <!-- TODO: adicionar números de latência quando disponíveis -->
07Lições Aprendidas
+
Conhecimento de engenharia reutilizável que levo adiante disso:
—Decomponha uma decisão conflada em eixos independentes antes de comparar alternativas. Empacotar duas escolhas em uma opção de "ou/ou" faz parecer um único trade-off quando na verdade são dois, e você pode acabar trocando algo que não precisava estar na mesa.
—Revisite sua própria decisão quando encontrar uma lacuna real nela. Perceber que um caminho de leitura síncrono e voltado ao cliente não tolera o piso natural de latência de um armazenamento, e corrigir a recomendação antes de ir para produção, valeu mais do que defender a primeira versão.
—Análise de segurança e privacidade pertence dentro da decisão de arquitetura, não depois dela. Uma decisão de modelo de dados já determina sua fronteira de isolamento de tenant, sua fronteira de acesso, e seu risco de disponibilidade baseado em custo, então revisar isso separadamente, depois, é revisar tarde demais para mudar barato.
—Carga analítica não pertence ao seu armazenamento transacional. Quando agregações pesadas e tráfego ao vivo dividem um banco de dados, a correção geralmente é separar a carga de trabalho, não ajustar a query.
—Em um warehouse analítico, o modelo de dados é uma decisão de custo, e uma query sem limites é um risco de disponibilidade, não só de lentidão. Clustering, tabelas curadas e um teto rígido de custo por query são o que mantém um armazenamento cobrado por escaneamento acessível e previsível.
08Evidência
+
—Autora fundadora do repositório do serviço; construí o walking skeleton e a camada de dados.
—Escrevi uma decisão de arquitetura documentada comparando quatro alternativas em uma matriz explícita serviço × banco de dados, com uma análise de risco de segurança/privacidade dedicada e guardrails de custo como parte da recomendação, revisada e aprovada por sete stakeholders entre engenharia e o time de plataforma.
—Revisei a decisão entre duas versões depois de identificar um risco de latência que a primeira versão tinha subestimado.
—Tabelas analíticas curadas e rotinas de carga em infrastructure-as-code.
—Verificado o andamento contra o tracker (2026-07): o dataset de staging, depois duas tabelas curadas (uma tabela de staging delta de 24 horas e uma tabela clusterizada de estado atual diário) carregadas incrementalmente por um `MERGE` diário mais uma rotina de intervalo mais amplo como rede de segurança para mensagens atrasadas; uma table-valued function como o único ponto de entrada que o serviço chama, para que o formato da query fique em infraestrutura versionada em vez de SQL construído por strings; queries agendadas para atualização incremental; permissões de warehouse para o time; e então a mesma stack em produção. Cada peça entregue como infrastructure-as-code, staging primeiro.
—Verificado trabalho de performance e custo: testes de carga rodados contra queries de produção parametrizadas por dois contextos reais de tenant (2026-06-26 a 2026-07-17), e uma investigação separada de clustering para reduzir bytes escaneados, ambos antes do serviço ir amplo.
—Verificada a superfície de produto (2026-07 a 2026-08): a página de gestão de anomalias, uma aba de gráfico de adherence, uma tabela de alertas recorrentes com seu contrato de dados acordado de ponta a ponta antes de qualquer lado ser construído, o endpoint por trás dela, e uma view de alertas acumulados ainda em progresso, cada uma entregue atrás de uma feature flag, várias com um contrato mockado chegando antes do endpoint real.
—Fonte (privada): base de conhecimento de carreira consolidada; registro interno de decisão de arquitetura; épicos e subtarefas do Jira no domínio de inspeção, 2026-03 a 2026-08.