Carga paralela e incremental, Properties compactas e modo ao vivo confiável - #11
Merged
Merged
Conversation
…á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>
There was a problem hiding this comment.
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
.clefgrandes 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
Dictionarypor evento por uma forma compacta (PropriedadesEvento) + pooling deScalarValueno 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 on lines
+751
to
+753
| var agora = Environment.TickCount64; | ||
| var inicioRodizio = _rodizioTail; | ||
| for (var i = 0; i < arquivos.Length; i++) |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O que muda
Desempenho da carga (itens 2–4 aprovados)
.gzcontinua sequencial.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).Dictionarypor evento vira arrays paralelos de chaves/valores com dedup last-wins e singleton vazio. OLeitorClefganhou pool de escalares com promoção em dois acessos; os contadores usamInterlockedporqueConcurrentDictionary.Countadquire 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 —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
PropriedadesEventoTests, leitura paralela, carga incremental, eTail_picks_up_lines_from_a_writer_that_keeps_the_file_open— escritor que segura o handle aberto entre rajadas, como um serviço real).Notas ao revisor
_ultimaAtividadeTailcompartilha o lock de_fileOffsets(mesmo padrão do arquivo, evita um segundo lock).🤖 Generated with Claude Code