Skip to content

PAV-125: [Backend/Valkey] Implementar índices por família, cache determinístico e match score - #307

Merged
hltav merged 1 commit into
Cla-Code-Community:developfrom
hltav:hltavdev/pav-125-backendvalkey-implementar-indices-por-familia-cache-deterministico-e-match-score
Oct 7, 2026
Merged

hltav merged 1 commit into
Cla-Code-Community:developfrom
hltav:hltavdev/pav-125-backendvalkey-implementar-indices-por-familia-cache-deterministico-e-match-score

Conversation

@hltav

@hltav hltav commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator

Linear

PAV-125 — Backend/Valkey: Implementar índices por família, cache determinístico e match score

Objetivo

Implementa a infraestrutura de persistência, indexação e busca necessária para suportar de forma eficiente a taxonomia profissional introduzida nas PAV-123/PAV-124.

A implementação estabelece uma separação explícita entre:

PostgreSQL = source of truth do catálogo de vagas
Valkey     = índices, cache e camada de leitura otimizada

O Processor passa a persistir a vaga no PostgreSQL antes de publicar ou atualizar seus índices no Valkey.

Fluxo principal:

coleta
  ↓
classificação
  ↓
persistência PostgreSQL
  ↓
COMMIT confirmado
  ↓
indexação Valkey
  ↓
busca/cache

Isso permite reconstruir e reconciliar os índices sem depender de uma nova coleta nas fontes externas.


Catálogo PostgreSQL

Foi criado um catálogo persistente de vagas no PostgreSQL porque não existia uma implementação equivalente reutilizável no projeto.

A tabela preserva o ID estável atual da vaga e armazena o documento necessário para reconstrução do catálogo e dos índices.

Também foram adicionados dados de ciclo de vida e revisão para permitir:

  • upsert idempotente;
  • primeira coleta;
  • última observação;
  • atualização;
  • expiração;
  • controle de revisão;
  • identificação de vagas ainda não indexadas;
  • rebuild e reconciliação.

Migration adicionada:

backend/drizzle/0015_job_catalog.sql

O PostgreSQL passa a ser a fonte de verdade do catálogo.


Ciclo de vida das vagas

O ciclo atual de aproximadamente nove dias foi preservado, mas deixou de depender exclusivamente do TTL do Valkey.

Uma nova coleta da mesma vaga:

  • mantém o mesmo ID;
  • realiza upsert;
  • atualiza a última observação;
  • renova sua expiração;
  • persiste a nova classificação quando necessário;
  • reindexa somente após o commit.

Reclassificar uma vaga não renova artificialmente sua expiração.

Vagas expiradas deixam de participar do catálogo ativo e dos índices de busca, mas não precisam ser fisicamente removidas do PostgreSQL.

O período é configurável por ambiente.


Índices Valkey

Foram implementados índices separados para os modos definidos pela PAV-124:

scraper:jobs:family:<family>

Compatibilidade com busca any, contendo primary ou related.

scraper:jobs:family:primary:<family>

Somente vagas cuja família principal corresponde à família consultada.

scraper:jobs:family:related:<family>

Somente famílias relacionadas.

Também foi implementado registro inverso de membership por vaga, permitindo descobrir seus índices anteriores sem percorrer todas as famílias.

Apenas as 13 famílias canônicas podem ser indexadas.

other permanece interno e não integra os índices públicos.


Reclassificação e consistência

A atualização dos índices foi projetada para suportar reclassificações como:

other → product
product → product_design

ou alterações simultâneas de família principal e famílias relacionadas.

A publicação usa operação Lua no Valkey para:

  • validar previamente os tipos das chaves envolvidas;
  • remover associações antigas da vaga;
  • adicionar as novas associações;
  • atualizar seu registro inverso;
  • preservar idempotência em retries;
  • atualizar a geração do cache somente após sucesso.

A persistência PostgreSQL acontece antes da indexação.

Se o commit no PostgreSQL falhar, a vaga não é publicada nos novos índices.


Busca por famílias

A busca da PAV-124 passa a utilizar os índices da PAV-125 quando o novo namespace está ativo.

São suportados:

  • uma família;
  • múltiplas famílias;
  • familyMode=any;
  • familyMode=primary;
  • OR entre famílias;
  • AND com os demais filtros.

Para múltiplas famílias, o conjunto de candidatos é resolvido no Valkey antes da paginação.

Os filtros residuais são aplicados antes do cálculo final de:

  • total;
  • página;
  • ordenação.

Não há pós-filtro depois da paginação.

Foi mantido fallback compatível com o mecanismo anterior enquanto o novo índice ainda não estiver ativo.


Cache determinístico de busca

Foi criada uma camada de cache específica para /jobs/search.

A chave considera os parâmetros que afetam o resultado, incluindo:

  • famílias normalizadas;
  • familyMode;
  • senioridade;
  • modalidade;
  • localização;
  • contrato;
  • texto;
  • provider;
  • tecnologias;
  • paginação;
  • ordenação;
  • contexto de ranking;
  • versão/geração dos índices.

As famílias são:

  • normalizadas;
  • deduplicadas;
  • ordenadas.

Portanto, por exemplo:

backend,fullstack

e:

fullstack,backend,backend

produzem o mesmo fingerprint.

familyMode=primary e familyMode=any produzem fingerprints diferentes.

Texto livre não é exposto diretamente na chave: é representado por fingerprint.

O cache possui TTL e geração para invalidação.


Invalidação

A geração de busca é avançada somente depois de uma atualização de catálogo/indexação bem-sucedida.

Não foi introduzido:

  • KEYS jobs:search:*;
  • FLUSHALL;
  • FLUSHDB;
  • limpeza global do Valkey.

A limpeza administrativa existente também foi protegida para não remover o namespace ativo do catálogo.


Rebuild dos índices

Foi criado processo explícito de rebuild a partir do PostgreSQL.

Fluxo:

PostgreSQL
  ↓
leitura em batches
  ↓
vagas ativas
  ↓
namespace Valkey novo
  ↓
validação
  ↓
troca da versão ativa

O rebuild:

  • não depende de nova coleta externa;
  • não carrega todo o catálogo em memória;
  • é cancelável;
  • é idempotente;
  • trabalha em batches;
  • não utiliza KEYS;
  • não utiliza FLUSHALL ou FLUSHDB;
  • mantém o namespace anterior disponível durante a troca.

Reconciliação

Foi adicionada reconciliação entre:

PostgreSQL
↕
Valkey

Ela detecta situações como:

  • vaga ativa sem índice;
  • ID stale no Valkey;
  • vaga inativa ainda indexada;
  • família primary divergente;
  • related divergente;
  • membership divergente;
  • índice any incorreto;
  • taxonomia inválida.

O comportamento padrão é read-only.

Correções exigem execução explícita.


Backfill

Como o catálogo anterior existia somente no Valkey, foi implementado fluxo controlado para migração inicial.

O backfill:

  • é explicitamente acionado;
  • trabalha em batches;
  • é idempotente;
  • pode ser repetido;
  • respeita cancelamento;
  • não utiliza KEYS;
  • não executa automaticamente no startup normal.

A ativação do novo Processor exige que migration e backfill/rebuild sejam concluídos previamente.


CLI de manutenção

Foi adicionada CLI específica do catálogo em:

scraper-go/cmd/catalog/

Ela concentra as operações de manutenção do catálogo, rebuild e reconciliação sem misturá-las ao fluxo normal do Processor.


Match score — Produto e Product Design

O match foi adaptado especificamente para:

  • product;
  • product_design.

Para Produto são consideradas evidências relacionadas a:

  • família;
  • senioridade;
  • modalidade;
  • localização;
  • competências;
  • ferramentas;
  • termos de produto.

Para Product Design são consideradas evidências relacionadas a:

  • UX/UI;
  • pesquisa;
  • prototipação;
  • design systems;
  • acessibilidade;
  • Figma e ferramentas equivalentes.

HTML/CSS podem contribuir para Product Design, mas não determinam o score principal.

Ausência de linguagens/frameworks não reduz o score de Product/Product Design.

As demais famílias continuam utilizando a fórmula anterior.

Também foi adicionado matchReasons opcional com justificativas públicas do match, sem expor pesos internos, PII ou descrição completa da vaga.


Preferências de usuário

Foi revisada a semântica do modelo existente de UserPreferences.

No contrato atual:

  • jobTypes representa Remoto, Híbrido e Presencial;
  • remoteOnly representa preferência por remoto;
  • searchLocation representa localização de busca;
  • keywords contém texto livre.

Por isso:

  • jobTypes permanece utilizado como modalidade;
  • valores legados incompatíveis são ignorados;
  • keywords não é usado para inferir família profissional;
  • contract não é inferido porque não existe atualmente fonte segura no modelo;
  • family também não é inferida a partir de texto livre.

Nenhum contrato público de preferências foi alterado.


Compatibilidade

Foram preservados os contratos introduzidos pela PAV-124:

  • family;
  • múltiplas famílias;
  • familyMode;
  • default any;
  • validação canônica;
  • erros existentes;
  • paginação;
  • ordenação;
  • autenticação;
  • rate limit;
  • endpoint /jobs/filters/options.

Também foram preservados:

  • IDs atuais das vagas;
  • saved jobs;
  • comportamento das demais famílias;
  • fallback para os índices anteriores durante a migração.

Não há alterações em:

  • frontend/**;
  • front_admin/**;
  • package-lock.json.

Principais arquivos

PostgreSQL

backend/src/db/schema/jobCatalog.ts
backend/drizzle/0015_job_catalog.sql
backend/drizzle/meta/0015_snapshot.json

Processor

scraper-go/internal/catalog/
scraper-go/internal/catalogops/
scraper-go/internal/jobindex/
scraper-go/cmd/catalog/

Backend / busca

backend/src/modules/jobs/cache/
backend/src/modules/jobs/repositories/valkeyJobSearch.adapter.ts
backend/src/modules/jobs/repositories/jobSearch.repository.ts

Match

backend/src/modules/jobs/services/jobMatch.service.ts
backend/src/modules/jobs/services/jobProfileMatch.service.ts

Documentação

BACKEND.md
SCRAPER.md
backend/PAV-125_REPORT.md

Testes e validações

Validações executadas durante a implementação:

  • 826 testes do backend aprovados;
  • testes Go aprovados;
  • go test -race aprovado;
  • go vet aprovado;
  • TypeScript typecheck aprovado;
  • Swagger validado;
  • Docker Compose validado;
  • build da CLI aprovado;
  • git diff --check aprovado.

Também foram executados testes de integração com PostgreSQL e Redis/Valkey isolados.

Os testes cobrem, entre outros:

  • insert;
  • upsert;
  • rollback;
  • falha de commit;
  • não indexar antes do commit;
  • reclassificação;
  • membership inverso;
  • primary/related/any;
  • rebuild;
  • reconciliação;
  • cache;
  • fingerprint;
  • invalidação;
  • paginação;
  • match de Produto;
  • match de Product Design;
  • separação entre modalidade, contrato e família.

Após a revisão final das preferências, 123 testes direcionados adicionais passaram, junto com typecheck e git diff --check.


Performance

A arquitetura reduz a quantidade de documentos hidratados durante buscas indexadas e evita consultas por vaga.

A busca opera em batches e mantém o PostgreSQL fora do caminho crítico normal de leitura.

Entretanto, o requisito:

/jobs/search p95 < 500 ms

não foi comprovado em ambiente representativo de produção.

Não foi criado benchmark artificial para declarar essa meta como atendida.

Essa medição deve ser realizada em staging/ambiente representativo.


Deploy / migração

Esta alteração não deve ser ativada simplesmente iniciando o novo Processor.

Ordem esperada:

  1. aplicar a migration PostgreSQL;
  2. validar o schema;
  3. executar o backfill controlado;
  4. executar rebuild dos índices;
  5. executar reconciliação;
  6. validar contagens/divergências;
  7. ativar/trocar o namespace Valkey;
  8. ativar o novo Processor.

O PostgreSQL é a fonte de verdade.

O Valkey pode ser reconstruído a partir dele.


Rollback

Em caso de problema durante a ativação:

  • manter/reverter para o namespace Valkey anterior;
  • interromper o novo Processor;
  • preservar o catálogo PostgreSQL;
  • não executar limpeza global do Valkey;
  • corrigir/reexecutar rebuild ou reconciliação;
  • reativar o novo namespace somente após validação.

A presença do catálogo PostgreSQL permite reconstruir os índices sem nova coleta externa.


Fora do escopo

Não fazem parte desta task:

  • novas métricas Prometheus completas;
  • dashboard administrativo;
  • alterações visuais;
  • filtros no frontend;
  • mudanças em frontend/**;
  • mudanças visuais em front_admin/**;
  • correção de package-lock;
  • alteração geral do match score das demais famílias;
  • política de deleção histórica;
  • Redis Cluster;
  • worker/outbox dedicado.

Checklist

  • PostgreSQL como source of truth
  • persistência antes da indexação
  • índices primary
  • índices related
  • índice compatível com any
  • membership inverso
  • reclassificação idempotente
  • cache determinístico
  • invalidação por geração
  • rebuild
  • reconciliação
  • backfill controlado
  • match Product/Product Design
  • contratos da PAV-124 preservados
  • sem KEYS, FLUSHALL ou FLUSHDB
  • sem alterações em frontend/front_admin/package-lock
  • testes backend
  • testes Go
  • race
  • vet
  • typecheck
  • git diff --check
  • validação de p95 < 500 ms em ambiente representativo

@hltav
hltav requested a review from Benevanio as a code owner October 7, 2026 15:23
@hltav
hltav merged commit 811ceff into Cla-Code-Community:develop Oct 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants