
## Quando múltiplas threads mascaram problemas de arquitetura: uma refatoração real no TOTVS Protheus
Em muitos projetos no TOTVS Microsiga Protheus, principalmente em rotinas legadas de faturamento, precificação e atualização massiva de tabelas como `DA1`, `SB1` e `SB2`, existe um padrão muito comum:
> O processo está lento?
> Então “joga thread”.
Na prática, isso normalmente significa criar múltiplas `threads`, `StartJob`, processos paralelos ou dividir cargas artificialmente para tentar compensar gargalos estruturais do código.
O problema é que, na maioria dos casos, isso não resolve a causa raiz. Apenas mascara.
---
## O cenário clássico
Encontramos recentemente uma rotina extremamente lenta de carga e gravação de preços.
O comportamento era o seguinte:
* Loop registro a registro.
* `dbSeek` para cada item.
* `RecLock` individual.
* `dbAppend` repetitivo.
* Atualizações linha a linha.
* Busca SQL dentro de loops.
* Reprocessamento desnecessário.
* Concorrência artificial via múltiplas threads.
O resultado?
* Alto consumo de CPU.
* Excesso de roundtrip entre AppServer e banco.
* Locking excessivo.
* Timeout no DBAccess.
* Crescimento exponencial do tempo de processamento.
* Necessidade “aparente” de paralelismo.
Ou seja:
O sistema não precisava de mais threads.
Precisava de menos loops.
---
## O grande problema das múltiplas threads em customizações Protheus
Muita gente trata thread como solução de performance.
Mas em ambientes Microsoft SQL Server + DBAccess + AppServer, múltiplas threads podem:
* aumentar contenção de lock;
* gerar disputa de recursos;
* elevar uso de TEMPDB;
* piorar deadlocks;
* aumentar timeout;
* elevar custo de sincronização;
* mascarar queries ruins;
* esconder arquitetura ineficiente.
Na prática:
> Você paraleliza o problema ao invés de resolver o problema.
---
## O verdadeiro gargalo: processamento registro a registro
O maior vilão normalmente não é o banco.
É o padrão:
```advpl
dbSeek()
RecLock()
campo := valor
MsUnlock()
```
Executado milhares de vezes.
Ou pior:
```advpl
While !EOF()
U_SeekSQL(...)
DBSkip()
EndDo
```
O famoso anti-pattern:
* N+1 Query
* Row-by-row processing
* Registro a registro
Extremamente comum em customizações antigas do Protheus.
---
## A refatoração: mudar o paradigma
Ao invés de “acelerar o loop”, a ideia foi:
### Eliminar o loop
A solução aplicada foi baseada em:
* `UPDATE FROM`
* `INSERT INTO SELECT`
* operações em lote;
* uso correto de índices;
* tratamento manual de `R_E_C_N_O_`;
* cálculo sequencial de `???_ITEM`;
* `LEFT JOIN` correto;
* redução de roundtrips;
* minimização de locks.
---
## O impacto real
O mais interessante foi:
### O novo modelo conseguiu atender a regra de negócio inteira usando apenas uma thread.
Sem necessidade de paralelismo artificial.
Ou seja:
* menos consumo;
* menos lock;
* menos timeout;
* menos stress no DBAccess;
* menos disputa de recursos;
* mais previsibilidade;
* mais estabilidade;
* mais velocidade.
---
## O principal aprendizado
Threads não substituem arquitetura.
Muitas vezes elas apenas escondem:
* query mal escrita;
* acesso excessivo ao banco;
* processamento redundante;
* falta de operações em lote;
* design procedural excessivo;
* ausência de pensamento orientado a dados.
---
## O que mudou tecnicamente
### Antes
* `RecLock()` em massa.
* `dbAppend()` em loop.
* múltiplos `dbSeek`.
* atualização individual.
* SQL dentro de loops.
* múltiplas threads para “aguentar”.
### Depois
* SQL set-based.
* processamento vetorizado.
* atualização em lote.
* inserção em lote.
* exclusão lógica em lote.
* uma única thread.
---
## Resultado prático no Protheus
Customizações mais performáticas significam:
* menor tempo de faturamento;
* menor impacto no AppServer;
* menor carga no DBAccess;
* menos timeout;
* menos deadlock;
* menor janela de lock;
* menor necessidade de infraestrutura;
* mais escalabilidade.
E principalmente:
> Menos “gambiarras arquiteturais” para compensar problemas de modelagem.
---
## Um detalhe importante sobre o Protheus
O próprio framework do Protheus induz muitos desenvolvedores ao paradigma procedural linha a linha.
Mas quando falamos de processamento massivo:
* SQL set-based quase sempre vence;
* menos tráfego quase sempre vence;
* menos lock quase sempre vence;
* menos thread quase sempre vence.
---
## Conclusão
Performance real não nasce de multiplicar threads.
Ela nasce de:
* reduzir operações;
* minimizar roundtrips;
* processar em lote;
* pensar orientado a dados;
* remover gargalos estruturais.
No fim das contas:
> A melhor otimização não foi criar mais threads.
> Foi fazer o sistema parar de precisar delas.
---
#DNATech, #NaldoDJ, #Protheus, #TOTVS, #ADVPL, #TLPP, #SQLServer, #DBAccess, #Performance, #ERP, #Desenvolvimento, #ArquiteturaDeSoftware, #CleanCode, #Refactoring, #Microsiga, #Backend, #TechLead, #Programacao
Comentários
Postar um comentário