Skip to content

perf(sincronizacao): remover os tetos que travavam a base em mil empr… - #30

Merged
net0well merged 1 commit into
mainfrom
feature/020-capacidade-de-ciclo
Jul 29, 2026
Merged

net0well merged 1 commit into
mainfrom
feature/020-capacidade-de-ciclo

Conversation

@net0well

Copy link
Copy Markdown
Owner

…esas

Quatro gargalos medidos contra o ambiente real, onde uma execução leva cerca de nove segundos por consulta à SEFAZ e o ciclo de trinta minutos enfileira quinhentas empresas.

Capacidade do ciclo deixa de ser constante

EmpresasPorCiclo e Workers eram constantes privadas, e a base parava de crescer em mil empresas sem que nada avisasse. Empresa sem novidade volta em uma hora, então metade da base fica elegível a cada ciclo: quinhentas por ciclo atendem mil empresas, e o excedente escorrega para o ciclo seguinte em silêncio.

A validação recusa no boot. Agendador com zero worker sobe calado e não processa nada — a fila enche, os documentos não chegam, e nada no sistema explica por quê.

Certificado deixa de ser recarregado a cada lote

Drenar uma empresa atrasada encadeia até vinte consultas à SEFAZ, duzentas na carga inicial, e cada uma pedia o mesmo certificado de volta ao object storage para decifrá-lo com AES-GCM e derivá-lo com PBKDF2 outra vez, produzindo exatamente o mesmo material.

O cache tem escopo de execução, não de processo: o registro é Scoped e cada trabalho do Hangfire abre o próprio escopo. A chave privada continua sem persistir entre execuções e sem atravessar empresas; o descarte zera os bytes decifrados. Remover um certificado invalida o que estava em memória — continuar servindo a cópia entregaria o que o operador mandou apagar.

Uma consulta no lugar de cinquenta

A SEFAZ entrega até cinquenta documentos por chamada, e o laço perguntava ao banco por um de cada vez: mil consultas numa execução que encadeia vinte lotes.

A comparação é sobre o objeto de valor, não sobre o texto dentro dele. A chave é mapeada por conversor e o provedor enxerga uma coluna só, então Chave.Valor não é membro que ele saiba traduzir — comparando o objeto, o conversor entra e a condição vira um IN.

Índices com a forma da pergunta

O filtro do contador vira EXISTS (SELECT 1 FROM itens WHERE documento_fiscal_id = ? AND cfop LIKE ?), e os índices eram só pelo código. A consulta parte da grade, já estreitada por empresa e período, e sonda os itens de cada documento; indexar apenas o código obrigaria o banco a percorrer todas as linhas com aquele CFOP no acervo inteiro para depois cruzar.

Os antigos foram removidos, e não somados: a tabela cresce por item, não por nota, e chega a dezenas de milhões de linhas no primeiro ano. Índice que nenhuma consulta usa custa escrita e espaço em cada uma delas.

…esas

Quatro gargalos medidos contra o ambiente real, onde uma execução leva cerca de nove
segundos por consulta à SEFAZ e o ciclo de trinta minutos enfileira quinhentas empresas.

Capacidade do ciclo deixa de ser constante

`EmpresasPorCiclo` e `Workers` eram constantes privadas, e a base parava de crescer em mil
empresas sem que nada avisasse. Empresa sem novidade volta em uma hora, então metade da
base fica elegível a cada ciclo: quinhentas por ciclo atendem mil empresas, e o excedente
escorrega para o ciclo seguinte em silêncio.

A validação recusa no boot. Agendador com zero worker sobe calado e não processa nada — a
fila enche, os documentos não chegam, e nada no sistema explica por quê.

Certificado deixa de ser recarregado a cada lote

Drenar uma empresa atrasada encadeia até vinte consultas à SEFAZ, duzentas na carga inicial,
e cada uma pedia o mesmo certificado de volta ao object storage para decifrá-lo com AES-GCM
e derivá-lo com PBKDF2 outra vez, produzindo exatamente o mesmo material.

O cache tem escopo de execução, não de processo: o registro é `Scoped` e cada trabalho do
Hangfire abre o próprio escopo. A chave privada continua sem persistir entre execuções e sem
atravessar empresas; o descarte zera os bytes decifrados. Remover um certificado invalida o
que estava em memória — continuar servindo a cópia entregaria o que o operador mandou apagar.

Uma consulta no lugar de cinquenta

A SEFAZ entrega até cinquenta documentos por chamada, e o laço perguntava ao banco por um de
cada vez: mil consultas numa execução que encadeia vinte lotes.

A comparação é sobre o objeto de valor, não sobre o texto dentro dele. A chave é mapeada por
conversor e o provedor enxerga uma coluna só, então `Chave.Valor` não é membro que ele saiba
traduzir — comparando o objeto, o conversor entra e a condição vira um `IN`.

Índices com a forma da pergunta

O filtro do contador vira `EXISTS (SELECT 1 FROM itens WHERE documento_fiscal_id = ? AND cfop
LIKE ?)`, e os índices eram só pelo código. A consulta parte da grade, já estreitada por
empresa e período, e sonda os itens de cada documento; indexar apenas o código obrigaria o
banco a percorrer todas as linhas com aquele CFOP no acervo inteiro para depois cruzar.

Os antigos foram removidos, e não somados: a tabela cresce por item, não por nota, e chega a
dezenas de milhões de linhas no primeiro ano. Índice que nenhuma consulta usa custa escrita
e espaço em cada uma delas.
@net0well
net0well merged commit cc8eeca into main Jul 29, 2026
2 checks 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.

1 participant