Entregando uma árvore de ativos grande de ponta a ponta, do SQL recursivo ao prefetch progressivo

Entreguei busca e navegação rápida sobre árvores de ativos muito grandes de ponta a ponta, com SQL recursivo no backend e uma UX de busca com cache em memória e prefetch progressivo em segundo plano no frontend, eliminando o carregamento repetido que tornava o fluxo mais usado lento.

impactoEntreguei busca e navegação rápida sobre árvores de ativos grandes de ponta a ponta entre banco de dados, backend e frontend, no fluxo mais usado do módulo.Eliminei o carregamento repetido que tornava lenta a navegação em árvores grandes, via cache e prefetch progressivo.Adicionei busca de ativos onde não havia nenhuma, com resultados mostrados em contexto.Qualitativo: uma melhora clara na performance percebida e na UX em um fluxo de alto tráfego; nenhuma métrica rígida de antes/depois foi capturada. <!-- TODO: adicionar números de tempo se disponíveis -->
01

Contexto

Esta é minha evidência mais forte de ownership full-stack e raciocínio de performance. Por ser dona do banco de dados, do backend e do frontend, pude colocar cada parte da solução na camada a que pertencia, travessia no banco de dados e ocultação de latência no cliente, em vez de forçar uma camada a compensar outra.

Isso também mostra uma decisão de UX em nível de produto tomada como um trade-off de engenharia: escolher revelar resultados de busca expandindo a árvore em vez de filtrá-la mudou tanto o que o usuário vê quanto como os dados precisam carregar.

02

Problema

Ao construir ou editar uma rota de inspeção, usuários navegam por uma hierarquia de ativos que pode ser muito grande. Eles precisavam ver descrições de ativos e buscar ativos por nome, mas a árvore carregava devagar e não havia busca. No fluxo mais usado do módulo, isso significava espera repetida e nenhuma forma de pular para um ativo conhecido.

03

Restrições

Os dados são profundamente hierárquicos e grandes. Encontrar resultados e mostrá-los em contexto significa percorrer uma árvore grande, e a abordagem ingênua é uma cascata de queries por nível.
Busca sobre uma árvore tem uma bifurcação de UX com consequências de dados. Você filtra a árvore até os resultados, ou revela os resultados no lugar? A escolha muda o que o usuário entende e quais dados você precisa carregar.
Performance percebida é o alvo real. Até um backend rápido parece lento se o cliente bloqueia a cada expansão, então a latência precisava ser escondida, não só reduzida.
Eu era dona das três camadas, então todo trade-off entre fazer o trabalho em SQL, na API, ou no cliente era meu para acertar.
04

Decisão

Coloquei cada responsabilidade na camada adequada a ela.

Travessia no banco de dados, via SQL recursivo. Uma query recursiva encontra ativos correspondentes e sobe até seus ancestrais em uma única passagem, então o servidor retorna resultados já em seu contexto de árvore em vez do cliente costurar várias requisições. Deduplicei resultados e computei "tem filhos" de forma barata para que os nós renderizassem corretamente sem round-trips extras.
Busca que revela em vez de filtrar. No frontend, apliquei debounce na query e escolhi expandir os nós dos resultados no lugar, com navegação entre resultados, em vez de reduzir a árvore só aos resultados. Isso mantém cada resultado legível em sua hierarquia real.
Um cache em memória por termo de busca, para que repetir ou refinar uma busca não recarregue o que já é conhecido.
Prefetch progressivo, nível a nível, em segundo plano, para que os próximos níveis já estejam carregando antes do usuário expandi-los, o que esconde latência no caminho comum.
Documentei os casos de uso (incluindo fluxos alternativos para os diferentes atores) e fiz o rollout atrás de uma feature flag, staging antes de produção.
05

Trade-offs

Uma query recursiva em vez de muitas queries por nível. Uma travessia recursiva retorna resultados com seus ancestrais em uma única passagem, evitando uma cascata tagarela, ao custo de uma query mais complexa para ser dona e raciocinar sobre ela.
Expandir resultados no lugar em vez de filtrar a árvore. Revelar resultados em sua hierarquia real preserva contexto e orientação, onde uma lista filtrada seria mais simples mas removeria a estrutura de que os usuários dependem. Aceitei uma lógica de carregamento mais envolvida para manter o resultado significativo.
Prefetch progressivo em segundo plano em vez de carregamento sob demanda. Prefetch esconde latência no caminho mais comum ao custo de buscar antecipadamente algo que o usuário pode não acabar precisando, uma boa troca no fluxo mais movimentado do módulo.
Um cache em memória por termo em vez de recarregar. Cache troca um pouco de memória e contabilidade de cache pela eliminação do carregamento repetido durante uma sessão de busca.
06

Impacto

Entreguei busca e navegação rápida sobre árvores de ativos grandes de ponta a ponta entre banco de dados, backend e frontend, no fluxo mais usado do módulo.
Eliminei o carregamento repetido que tornava lenta a navegação em árvores grandes, via cache e prefetch progressivo.
Adicionei busca de ativos onde não havia nenhuma, com resultados mostrados em contexto.
Qualitativo: uma melhora clara na performance percebida e na UX em um fluxo de alto tráfego; nenhuma métrica rígida de antes/depois foi capturada. <!-- TODO: adicionar números de tempo se disponíveis -->
07

Lições Aprendidas

Conhecimento de engenharia reutilizável que levo adiante disso:

Empurre a travessia de hierarquia para o banco de dados. Uma query recursiva que retorna resultados com seus ancestrais vence uma cascata de requisições por nível que o cliente teria que orquestrar.
Uma escolha de UX de busca é uma decisão de engenharia. "Revelar no lugar" vs. "filtrar até os resultados" muda tanto a compreensão quanto o formato dos dados que você carrega, então decida isso deliberadamente.
Esconda a latência, não só a reduza. Prefetch progressivo e cache por termo fazem o caminho comum parecer instantâneo mesmo quando ainda resta algum trabalho.
Ser dona de cada camada permite resolver cada problema onde ele pertence. Essa é a maior vantagem de um ownership full-stack de verdade.
08

Evidência

Entreguei sozinha entre banco de dados (SQL recursivo), backend e frontend.
Funcionalidade lançada em produção atrás de uma feature flag depois de staging.
Documentei casos de uso incluindo fluxos alternativos por ator.
Verificado contra o tracker (2026-05-19 a 2026-06-17): dois itens, o endpoint que expõe a descrição de um ativo aos formulários de rota, e a travessia recursiva em si. O antes/depois registrado é N×3 queries sequenciais substituídas por 3 no total: uma CTE recursiva para todos os nós descendentes, depois uma query em lote para pontos de medição e outra para checklists, com a árvore montada em memória em O(n) via um mapa id→nó.
Verificado trabalho arquitetural, não só uma query: as regras de montagem da árvore (agrupar uma única folha como um nó direto versus N folhas sob um agrupador, a regra de ordenação, e `hasChildren` computado a partir dos filhos montados em vez de uma subquery no banco) foram extraídas do adapter do repositório para um montador na camada de domínio, restaurando a fronteira hexagonal que o código anterior tinha cruzado. Um nó raiz sentinela removeu a necessidade de arrays separados para filhos de raiz versus aninhados.
Verificado um bug encontrado e com causa raiz identificada na mesma passagem: máquinas folha sem nós filhos mas com pontos de medição diretos não retornavam nada de forma recursiva, porque um retorno antecipado em "nenhuma linha descendente" as descartava antes da query de folha rodar. Corrigido incluindo o próprio id da raiz na busca de folhas.
Verificado trabalho de frontend: eliminei uma dupla busca ao adicionar (uma expansão de um nível seguida imediatamente por uma recursiva), armazenei em cache o resultado recursivo na árvore do cliente para que uma expansão posterior não custe nada, e mudei a saga de latest-wins para tratamento por ação para que adicionar duas máquinas rapidamente não cancele mais a primeira. Tudo isso protegido por uma feature flag, com o caminho legado intocado quando a flag está desligada.
Fonte (privada): itens do Jira no domínio de inspeção, 2026-05 a 2026-06; base de conhecimento de carreira consolidada.