Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

Sistemas de Recomendação em Produção com LSH Forest, MinHash e Cache LRU Serverless (latência de 200ms)

Quando falamos de Inteligência Artificial hoje, quase toda a atenção vai para LLMs. Mas em produtos de alto volume — como e-commerce, delivery e plataformas de saúde — Sistemas de Recomendação Clássicos continuam sendo a

Quando falamos de Inteligência Artificial hoje, quase toda a atenção vai para LLMs. Mas em produtos de alto volume — como e-commerce, delivery e plataformas de saúde — Sistemas de Recomendação Clássicos continuam sendo a espinha dorsal da personalização em larga escala.

Durante 12 meses de pesquisa e extensão na Universidade Estadual de Feira de Santana (UEFS), sob orientação do Prof. Dr. João Batista da Rocha Junior, projetei e coloquei em produção o módulo de recomendação do LogicFarma (plataforma SaaS da Farmácia Universitária com mais de 4.700 operações registradas, 666 históricos clínicos avaliados e 1.060 medicamentos).

O desafio de engenharia era claro: como recomendar procedimentos e consultas personalizadas para centenas de pacientes em tempo real e em disparos em lote (e-mail/WhatsApp), rodando sobre funções Serverless com limites rígidos de memória e tempo de execução?

Neste artigo, compartilho a arquitetura Memory-First, a adaptação do Locality-Sensitive Hashing (LSH Forest) com MinHash e a estratégia de degradação graciosa em 4 níveis que levou a latência de consulta para ~200ms.

1. O gargalo da Filtragem Colaborativa tradicional O(N × M)

Na Filtragem Colaborativa Baseada no Usuário, queremos encontrar pacientes com histórico clínico semelhante ao paciente-alvo e recomendar procedimentos que esses vizinhos realizaram, mas que o paciente ainda não conhece.

A métrica natural para conjuntos binários (realizou / não realizou um procedimento) é a Similaridade de Jaccard:

J(A, B) = |A ∩ B| / |A ∪ B|

O problema é que comparar um paciente contra todos os outros a cada requisição tem custo linear no número de usuários e itens. Em um endpoint HTTP ou num disparo de mensagens para 300 pacientes de uma vez, calcular Jaccard exato no banco de dados é inviável.

2. MinHash + LSH Forest: busca aproximada em tempo sublinear

Baseado nos experimentos de Rocha-Junior et al. (2025) sobre sistemas de recomendação com LSH em e-commerce, substituímos a comparação exata por duas estruturas probabilísticas:

  1. MinHash (l = 200 permutações): transforma o conjunto de procedimentos e consultas de cada paciente (de tamanho variável) em uma assinatura compacta de comprimento fixo l = 200. A probabilidade de duas assinaturas coincidirem em uma posição é exatamente igual à Similaridade de Jaccard entre os conjuntos. Os experimentos mostraram que l = 200 atinge o pico de Precisão Média (15,1%) mantendo Recall próximo de 92%, enquanto l = 250 degrada a relação custo/precisão em RAM.
  2. LSH Forest (Bawa et al., WWW 2005): em vez do LSH tradicional (que exige calibrar manualmente o número de buckets e o limiar de similaridade), utilizamos uma floresta de árvores de prefixos auto-ajustável (MinHashLSHForest), consultando k_build = 80 vizinhos na indexação e k_query = 20 vizinhos em tempo real.

3. Adaptação do E-commerce para o Domínio Clínico

No modelo original de e-commerce, as interações possuem pesos graduados (visited = 1, cart_added = 4, bought = 8). No domínio farmacêutico, a interação é estritamente binária (o paciente realizou ou não o procedimento/consulta).

Fizemos três adaptações de domínio:

  • Unificação de pesos por co-ocorrência: cada item é codificado como "Nome|Categoria" (ex.: "Glicemia|Procedimento", "Cefaleia|Consulta"), e a pontuação dos itens candidatos nos k = 20 vizinhos mais próximos é calculada pela frequência de co-ocorrência entre os vizinhos que possuem o item (excluindo o que o paciente já realizou).
  • Filtro de elegibilidade e outliers: apenas perfis com min_items = 2 até max_items = 100 entram no índice LSH Forest.
  • Filtro determinístico por restrição clínica de gênero: consultas exclusivas ao público feminino (como "Cólica Menstrual") são filtradas tanto na saída do LSH quanto na pré-computação do ranking de fallback masculino (GLOBAL_MASCULINO).

4. Arquitetura Memory-First com Cache LRU e Batch Inference

Como a aplicação principal rodava em Next.js / TypeScript e a biblioteca de referência acadêmica para LSH (datasketch) estava em Python, separamos o motor de recomendação em funções Serverless Python integradas ao MongoDB.

Para evitar ler e desserializar o índice do banco a cada chamada, desenhamos uma arquitetura Memory-First:

[Cron Mensal Python] ---> Constrói LSH Forest (l=200) ---> Serializa blobs no MongoDB
                                                                  |
[Next.js Top Tags]   ---> POST /api/lsh/batch (até 300 pac.) ---> Cache LRU em RAM (50 farmácias)
  • Cold Start (~2 a 3s): carrega os pickle blobs (forestBlob, minhashesBlob, fallbacksBlob, itemsByPatientBlob) do MongoDB e hidrata o container.
  • Warm Start (~0,2 a 0,3s): o Cache LRU em RAM responde diretamente da memória da função Serverless, sem tocar no banco.
  • Endpoint em Lote (/api/lsh/batch): quando o farmacêutico dispara comunicados com Top Tags (#Top3ProcedimentosRec, #Top3ConsultasRec), o módulo recommendationTagResolver.ts agrupa até 300 pacientes em uma única chamada HTTP POST, carregando o cache da farmácia uma só vez.

5. Degradação Graciosa em 4 Níveis (Zero Falha no Disparo)

Em um sistema real, o algoritmo de recomendação nunca pode derrubar o fluxo principal de comunicação (seja por cold start de um paciente novo sem histórico, seja por timeout de rede). Implementamos 4 níveis de degradação:

  1. LSH Completo (lsh): os 20 vizinhos do LSH Forest preenchem todas as k sugestões pedidas.
  2. LSH Parcial + Suplementação (lsh_partial): quando os vizinhos têm pouca variedade, o sistema completa as vagas faltantes com o Fallback Global segmentado por gênero (GLOBAL_FEMININO ou GLOBAL_MASCULINO).
  3. Fallback de Cold Start (fallback): paciente recém-cadastrado (sem histórico) recebe imediatamente o ranking global segmentado pré-computado em memória, ou via leitura direta da coleção recomendacoesMensais.
  4. Fail-safe de Timeout (30s): se a API Python falhar, as Top Tags degradam para string vazia e a mensagem transacional segue normalmente.

Em produção na Farmácia Universitária da UEFS, o indexador processou 666 históricos de pacientes em 4 unidades, indexando 353 perfis elegíveis e 1.978 registros clínicos em menos de 1,7 segundo.

Links relacionados

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.