# Processamento Paralelo

## Objetivo

O motor deve permitir sincronização paralela por município sem perder controle de NSU, logs e retentativas.

## Modelo recomendado

Usar paralelismo controlado por município:

- cada município ativo entra em uma fila de trabalho;
- um pool de workers processa os municipios;
- cada município possui seu próprio `nfse_sync_state`;
- dentro de um mesmo município, a consulta por NSU deve ser sequencial;
- municípios diferentes podem rodar em paralelo.

## Por que sequencial dentro do município

O NSU é uma sequência de distribuição. Processar o mesmo município com múltiplos workers pode gerar:

- lacuna de NSU;
- corrida ao atualizar `last_nsu`;
- duplicidade de documento;
- dificuldade para retomar após erro.

Por isso, a concorrência deve ser entre municípios, não entre NSUs do mesmo município.

## Implementação Atual

O comando `scripts/sync_full_all.py` executa sincronização completa para todos os municípios ativos com certificado ativo:

```sh
NFSE_MASTER_KEY='base64-32-bytes' \
NFSE_DB_DOCKER_CONTAINER=maspernf-mysql \
./scripts/sync_full_all.py --concurrency 3
```

Regras implementadas:

- municípios em `pending` ou `running` são ignorados para evitar execução duplicada;
- `pending` ou `running` só bloqueiam nova execução quando `locked_at` é recente;
- cada worker chama `run_sync_full` para um município;
- o botão `Sincronizar tudo` do painel continua rodando em background para apenas um município;
- `run_sync_full` atualiza `last_nsu`, `max_nsu` e `locked_at` no máximo a cada `NFSE_SYNC_PROGRESS_EVERY` itens e também ao final de cada lote.
- no início de cada execução, o próximo NSU parte do maior valor entre `nfse_sync_state.last_nsu` e `MAX(nsu)` da tabela física do município.
- documentos e eventos são processados no mesmo lote DFe; eventos são persistidos em `nfse_eventos_{codigo_ibge}`.

## Otimização de Banco

O MVP usa o cliente `mysql` via subprocesso para manter a instalação simples. Para reduzir custo:

- a gravação do documento usa `LAST_INSERT_ID(id)` no `ON DUPLICATE KEY`;
- as partes do documento são gravadas em lote por documento;
- os índices de consulta ficam nas tabelas físicas por município.

Para produção com volume alto, o próximo ganho relevante é trocar o acesso por uma conexão persistente MySQL (`PyMySQL` ou `mysqlclient`) e gravar cada lote em transação única.

## Configurações previstas

Variáveis sugeridas:

```text
NFSE_WORKER_CONCURRENCY=4
NFSE_SYNC_INTERVAL_SECONDS=300
NFSE_EMPTY_RESULT_BACKOFF_SECONDS=3600
NFSE_REQUEST_TIMEOUT_SECONDS=60
NFSE_MAX_RETRIES=3
NFSE_SYNC_PROGRESS_EVERY=100
NFSE_SYNC_STALE_LOCK_MINUTES=120
```

## Controle no banco

A tabela `nfse_sync_state` possui um registro por município/ambiente/perfil. O motor deve usar lock transacional ou campo de controle para evitar dois processos sincronizando o mesmo município ao mesmo tempo.

Campos relevantes:

- `last_nsu`;
- `max_nsu`;
- `status`;
- `locked_at`;
- `locked_by`;
- `last_success_at`;
- `last_error_at`;
- `last_error_message`.

## Estrategia inicial em Python

No MVP, usar `ThreadPoolExecutor` e limitar a concorrência com `NFSE_WORKER_CONCURRENCY`. A carga principal será I/O de rede e banco, então threads são suficientes no início.

Se no futuro houver parsing XML pesado ou validação XSD em massa, pode-se avaliar `ProcessPoolExecutor` para essas etapas específicas.
