Skip to content

Carga paralela e incremental, Properties compactas e modo ao vivo confiável - #11

Merged
afernandes merged 1 commit into
mainfrom
feat/perf-carga-e-tail-ao-vivo
Aug 3, 2026
Merged

Carga paralela e incremental, Properties compactas e modo ao vivo confiável#11
afernandes merged 1 commit into
mainfrom
feat/perf-carga-e-tail-ao-vivo

Conversation

@afernandes

@afernandes afernandes commented Aug 3, 2026

Copy link
Copy Markdown
Owner

O que muda

Desempenho da carga (itens 2–4 aprovados)

  • Leitura paralela de arquivo grande (LeitorArquivoLog.cs): arquivos ≥ 32 MB são divididos em segmentos alinhados a quebras de linha (fronteiras resolvidas por seeks antes de ler) e processados por workers em paralelo, com equivalência total com a leitura sequencial — mesmos eventos, mesma ordem. .gz continua sequencial.
  • Carga incremental (LogStore): durante a carga, lotes parciais são publicados a cada ≥ 400 ms — a UI mostra os primeiros eventos em vez de esperar o fim (metadados vão no primeiro lote; a mescla usa busca binária por região).
  • Properties compactas (PropriedadesEvento.cs): o Dictionary por evento vira arrays paralelos de chaves/valores com dedup last-wins e singleton vazio. O LeitorClef ganhou pool de escalares com promoção em dois acessos; os contadores usam Interlocked porque ConcurrentDictionary.Count adquire todos os locks e custava segundos no caminho quente (3,6 M chamadas × 20 workers).

Medido na pasta real (C:\TOTVSPDV\Logs, 313 mil eventos): carga 3.864 → 2.808 ms, memória 1.295 → 453 MB.

Correção: modo ao vivo morria em silêncio no PDV real

O portão do poll confiava no tamanho vindo da enumeração de diretório — mas esse valor sai do índice do diretório, que o NTFS deixa stale enquanto o logger mantém o arquivo aberto. O Serilog do PDV nunca fecha o handle, então a enumeração devolvia o tamanho da carga indefinidamente e o tail pulava os arquivos para sempre. O harness sintético não pegava: o escritor de teste fechava o arquivo a cada linha, o que atualiza o índice.

Correção em LogStore.PollTailAsync: o portão deixou de ser a única palavra —

  1. enumeração diz que mudou → abre (sinal positivo continua valendo);
  2. arquivo com atividade recente (moveu o offset nos últimos 60 s) é aberto todo tick — latência de 1 s durante rajadas;
  3. os demais entram num rodízio de 8 por tick — com 104 arquivos, qualquer um volta a ser visto em ≤ 13 s e é promovido a quente na hora.

Os arquivos de dias já encerrados (65 dos 104) continuam descartados de graça pela enumeração; o poll abre ~10 handles/tick em vez de 104.

Validação

  • Suíte completa: 515/515 (novos: PropriedadesEventoTests, leitura paralela, carga incremental, e Tail_picks_up_lines_from_a_writer_that_keeps_the_file_open — escritor que segura o handle aberto entre rajadas, como um serviço real).
  • Harness E2E do tail (12 cenários: anexo, rotação à meia-noite, truncamento com BOM, religar): 12/12.
  • Prova na pasta viva do PDV: antes da correção, +0 eventos em 90 s com os arquivos comprovadamente crescendo; depois, +2 eventos entregues sozinhos em ~25 s (ciclo de escrita do PDV é ~30 s).

Notas ao revisor

  • O comentário longo no portão do poll é intencional: o staleness do índice NTFS é um comportamento fácil de "otimizar de volta" sem perceber a regressão.
  • _ultimaAtividadeTail compartilha o lock de _fileOffsets (mesmo padrão do arquivo, evita um segundo lock).
  • Fidelidade do parser validada por oráculo evento a evento contra o leitor anterior: 0 divergências em 314.973 eventos reais.

🤖 Generated with Claude Code

…ável

Carga:
- LeitorArquivoLog divide arquivos grandes (>= 32 MB) em segmentos alinhados
  a quebras de linha e lê em paralelo, com equivalência total com a leitura
  sequencial (mesmos eventos, mesma ordem).
- LogStore publica lotes parciais durante a carga (a cada >= 400 ms): a UI
  mostra os primeiros eventos em vez de esperar o arquivo inteiro.
- PropriedadesEvento troca o Dictionary por evento por arrays paralelos com
  dedup last-wins e singleton vazio; LeitorClef ganha pool de escalares com
  promoção em dois acessos e contadores Interlocked no lugar de
  ConcurrentDictionary.Count, que adquire todos os locks e custava segundos
  no caminho quente. Na pasta real: carga 3.864 -> 2.808 ms e memória
  1.295 -> 453 MB.

Modo ao vivo:
- O portão do poll confiava no tamanho vindo da enumeração de diretório, mas
  o NTFS deixa esse índice stale enquanto o logger mantém o arquivo aberto —
  no PDV real o tail morria em silêncio (o harness sintético não pegava
  porque o escritor de teste fechava o arquivo a cada linha). Agora arquivo
  com atividade recente (60 s) é aberto todo tick, os demais entram num
  rodízio de 8 por tick, e a enumeração segue descartando de graça os
  arquivos de dias já encerrados.
- Teste novo com escritor que mantém o handle aberto entre rajadas, como o
  Serilog de um serviço de longa vida.

Validação: 515/515 testes; harness E2E do tail 12/12; na pasta real do PDV o
tail voltou a entregar eventos sozinho (+2 em ~25 s; antes, +0 em 90 s).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 20:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR foca em reduzir tempo/memória de carga de logs CLEF e em tornar o modo ao vivo (tail) mais confiável em cenários reais (writer mantendo o arquivo aberto), preservando compatibilidade comportamental com a implementação anterior.

Changes:

  • Leitura paralela de arquivos .clef grandes por segmentação alinhada em \n, mantendo equivalência com o caminho sequencial.
  • Carga incremental no LogStore, publicando lotes parciais em intervalos configuráveis.
  • Substituição de Dictionary por evento por uma forma compacta (PropriedadesEvento) + pooling de ScalarValue no parser; ajustes no tail para lidar com tamanho “stale” via enumeração do NTFS, com janela de atividade e rodízio.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/ClefExplorer.Tests/PropriedadesEventoTests.cs Novos testes de contrato para a forma compacta de propriedades e o pool de escalares.
test/ClefExplorer.Tests/LogStoreTests.cs Ajustes e novos testes para carga incremental e cenários reais de tail (handle aberto, rotação/rewrite).
test/ClefExplorer.Tests/LogColumnDiscoveryTests.cs Adequação do helper de eventos para mudanças no tipo de Properties.
test/ClefExplorer.Tests/LeitorArquivoLogTests.cs Novos testes de equivalência entre leitura paralela e sequencial.
src/Services/LogStore.cs Publicação incremental e novo gate/rodízio de tail para contornar staleness do índice NTFS.
src/Services/LeitorClef.cs Pool de ScalarValue (strings/números/bool/null) e uso da forma compacta de propriedades.
src/Services/LeitorArquivoLog.cs Segmentação e leitura paralela de um único arquivo grande (exceto .gz), com recorte de stream.
src/Models/PropriedadesEvento.cs Novo IReadOnlyDictionary compacto baseado em arrays paralelos com last-wins OrdinalIgnoreCase.
src/Models/ClefEvent.cs Properties passa a expor IReadOnlyDictionary para suportar implementação compacta.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +100 to +102
public bool TryGetValue(string key, out LogEventPropertyValue value)
{
var indice = IndiceDe(_chaves, _chaves.Length, key);
Comment thread src/Services/LogStore.cs
Comment on lines +751 to +753
var agora = Environment.TickCount64;
var inicioRodizio = _rodizioTail;
for (var i = 0; i < arquivos.Length; i++)
@afernandes
afernandes merged commit 03804da into main Aug 3, 2026
2 checks passed
afernandes added a commit that referenced this pull request Aug 3, 2026
Carga paralela e incremental, Properties compactas e modo ao vivo confiável
(PR #11).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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