Conheça o projeto
Este desafio simula um sistema de auditoria de logs que leva 45 minutos para processar dados. O objetivo é criar um motor de agregação em Go demonstrando domínio progressivo de concorrência através de quatro implementações: sequencial, concorrente ingênua (com race conditions), com Mutex, e usando Worker Pool com Channels, conceitos essenciais para sistemas de alta performance.
Recursos
Materiais para você usar como base para o desenvolvimento
Instruções
Estrutura, regras e requisitos do projeto
Nesse seguinte cenário hipotético temos uma equipe de Business Intelligence (BI) precisa de um relatório de auditoria de todos os eventos da última hora. O problema? O job atual que processa esses dados está demorando 45 minutos para rodar. Ninguém consegue trabalhar assim.
Sua Missão: É criar do zero um novo motor de agregação em Go para resolver esse gargalo de uma vez por todas. Nossos servidores despejam milhares de arquivos de log JSON em um diretório de logs. Sua tarefa é usar o arsenal de concorrência do Go (Goroutines, Channels e Mutexes) para implementar esse job de agregação.
🎯 Objetivos
O objetivo aqui é que você demonstre sua habilidade em aplicar os seguintes conceitos:
- Além do Básico: Provar que você domina a diferença entre processamento Síncrono (lento e seguro), concorrência (complexo e rápido) e Paralelismo (a execução que buscamos).
- Visão de Raio-x (Race Conditions): Não apenas corrigir bugs de concorrência, mas antecipá-los. Você deve ser capaz de provar que um código "ingênuo" está quebrado (usando
go run -race .) antes mesmo que ele corrompa dados em produção. Este é o tipo de bug que derruba sistemas. - Domínio dos Locks (Trade-offs): Saber quando usar um
sync.Mutexpara proteger seções críticas. E, mais importante, saber quando não usar. Você deve entender e medir o custo da Contenção de Lock — o ponto onde sua solução "concorrente" se torna mais lenta que a síncrona porque todas as goroutines estão paradas em uma fila. - Arquitetura de Concorrência (Padrões Reais):
- Pipelines Escaláveis: Projetar pipelines de processamento (Fan-Out/Fan-In) que evitam a necessidade de locks em pontos quentes, usando channels para orquestrar o fluxo de dados.
- Gerenciamento de Recursos (Worker Pools): Controlar o nível de paralelismo. Não queremos 10.000 goroutines abrindo 10.000 arquivos de uma vez. Você deve implementar um pool de workers para evitar que seu serviço sufoque a máquina.
- Ciclo de Vida (Robustez): Dominar o
sync.WaitGroupe o fechamento de channels para garantir que o sistema desligue de forma limpa, sem perder um único evento que estava "em trânsito".
💻 Suas Tarefas (O "Como")
Você deve criar um arquivo main.go que implemente as 4 funções a seguir. Na sua função main, você deve:
- Gerar um conjunto de arquivos de log de teste (use a função
GenerateMockFilesfornecida). - Chamar cada uma das 4 funções.
- Medir o tempo de execução (
time.Since(start)) de cada uma e imprimir os resultados para comparação.
🎯 Parte 1: A Baseline (Processamento Síncrono)
Implemente a versão mais simples e direta. Ela deve processar um arquivo de cada vez, sequencialmente, na goroutine principal.
- Assinatura:
func ProcessSequential(files []string) *Report - Lógica:
- Crie um
ReportusandoNewReport(). - Use um
for...rangepadrão sobre a lista defiles. - Dentro do loop, abra o arquivo, leia-o linha por linha (sugestão:
bufio.Scanner). - Para cada linha válida, decodifique o JSON para um
Evente chamereport.AddEvent(event). - Para cada linha inválida, chame
report.AddError().
- Crie um
🎯 Parte 2: Concorrência Ingênua (O Problema)
Agora, vamos tentar "acelerar" as coisas processando cada arquivo em sua própria Goroutine. Esta implementação é intencionalmente falha.
- Assinatura:
func ProcessConcurrentNaive(files []string) *Report - Lógica:
- Crie um único
Reportcompartilhado. - Crie um
sync.WaitGrouppara rastrear as goroutines. - Itere sobre
files. Para cada arquivo:wg.Add(1)- Inicie uma goroutine
go func(filename string) { ... }. - Dentro da goroutine:
defer wg.Done(), processe o arquivo (abrir, ler, decodificar). - Para cada evento, chame
report.AddEvent(event). - Para cada erro, chame
report.AddError().
- Chame
wg.Wait()para esperar todas as goroutines terminarem. - Retorne o relatório.
- Crie um único
🎯 Parte 3: Corrigindo com Mutex (A Solução de Memória Compartilhada)
Agora que você viu o problema, corrija-o usando a abordagem de "memória compartilhada protegida".
- Assinatura:
func ProcessConcurrentMutex(files []string) *Report - Lógica:
- Copie sua implementação da Parte 2 (incluindo
WaitGroupe goroutines). - A única diferença é: dentro da goroutine, em vez de chamar os métodos
AddEventeAddError, você deve chamar as versões seguras:report.AddEventSafe(event)ereport.AddErrorSafe().
- Copie sua implementação da Parte 2 (incluindo
🎯 Parte 4: O Padrão Idiomático (Worker Pool & Channels)
Esta é a arquitetura mais escalável. Vamos refatorar para um padrão que evita a memória compartilhada, seguindo o lema do Go: "Não comunique compartilhando memória; compartilhe memória comunicando."
- Assinatura:
func ProcessPipeline(files []string, numWorkers int) *Report - Lógica:
- Canais: Crie dois canais:
jobs := make(chan string, len(files))results := make(chan ProcessResult, 1000)(Use uma structProcessResultque possa conter ou umEvent, ou umerror).
- Workers (Fan-Out):
- Crie um
sync.WaitGrouppara os workers. - Inicie
numWorkersgoroutines. - Cada worker deve ler
filenamedo canaljobs(for filename := range jobs). - O worker processa o arquivo (abrir, ler, decodificar) e envia
ProcessResults para o canalresults.
- Crie um
- Aggregator (Fan-In):
- Crie um
NewReport(). - Inicie uma única goroutine agregadora que será a única a ler do canal
results(for res := range results). - Dentro dela, verifique o
ProcessResult: seres.Err != nil, chamereport.AddError(); senão, chamereport.AddEvent(res.Event). (Note que usamos as versões sem lock, pois esta é a única goroutine que modifica oreport).
- Crie um
- Coordenação:
- Envie todos os
filespara o canaljobs. close(jobs)(Sinaliza aos workers que não há mais trabalho).- Espere todos os workers terminarem (
wg.Wait()). - Depois que eles terminarem,
close(results)(Sinaliza ao agregador que não há mais resultados). - Espere o agregador terminar (use um canal
doneou outroWaitGroup) e então retorne oreport.
- Envie todos os
- Canais: Crie dois canais:
Resultados Esperados
- Os Dados Precisam Estar Corretos (Não-Negociável):
- As implementações, Parte 1 , Parte 3 e Parte 4 devem produzir um relatório final com resultados idênticos.
- A Parte 2 deve, previsivelmente, falhar em produzir o resultado correto ou, no mínimo, ser comprovadamente insegura.
- Robustez e Segurança:
- A execução do
go run -race .deve passar limpa (sem avisos) para as Partes 1, 3 e 4. - A execução do
go run -race .na Parte 2 deve obrigatoriamente detectar e reportar a "DATA RACE". Você deve ser capaz de apontar no código exatamente onde e por que ela ocorre.
- A execução do
- Performance Mensurável:
- Você deve fornecer métricas de benchmark (usando
time.Since(start)ou o pacotetesting) que comprovem a melhoria de desempenho das Partes 3 e 4 em relação à baseline (Parte 1) usando um conjunto de dados significativo (ex: 100 arquivos, 10.000 linhas/arquivo).
- Você deve fornecer métricas de benchmark (usando
- Adesão aos Padrões Exigidos:
- A Parte 3 deve usar
sync.Mutex(ousync.RWMutex) para garantir a segurança. - A Parte 4 deve obrigatoriamente usar o padrão Worker Pool e um pipeline de channels (Fan-Out/Fan-In), removendo a necessidade de um lock no agregador.
- A Parte 3 deve usar
Nesse seguinte cenário hipotético temos uma equipe de Business Intelligence (BI) precisa de um relatório de auditoria de todos os eventos da última hora. O problema? O job atual que processa esses dados está demorando 45 minutos para rodar. Ninguém consegue trabalhar assim.
Sua Missão: É criar do zero um novo motor de agregação em Go para resolver esse gargalo de uma vez por todas. Nossos servidores despejam milhares de arquivos de log JSON em um diretório de logs. Sua tarefa é usar o arsenal de concorrência do Go (Goroutines, Channels e Mutexes) para implementar esse job de agregação.
🎯 Objetivos
O objetivo aqui é que você demonstre sua habilidade em aplicar os seguintes conceitos:
- Além do Básico: Provar que você domina a diferença entre processamento Síncrono (lento e seguro), concorrência (complexo e rápido) e Paralelismo (a execução que buscamos).
- Visão de Raio-x (Race Conditions): Não apenas corrigir bugs de concorrência, mas antecipá-los. Você deve ser capaz de provar que um código "ingênuo" está quebrado (usando
go run -race .) antes mesmo que ele corrompa dados em produção. Este é o tipo de bug que derruba sistemas. - Domínio dos Locks (Trade-offs): Saber quando usar um
sync.Mutexpara proteger seções críticas. E, mais importante, saber quando não usar. Você deve entender e medir o custo da Contenção de Lock — o ponto onde sua solução "concorrente" se torna mais lenta que a síncrona porque todas as goroutines estão paradas em uma fila. - Arquitetura de Concorrência (Padrões Reais):
- Pipelines Escaláveis: Projetar pipelines de processamento (Fan-Out/Fan-In) que evitam a necessidade de locks em pontos quentes, usando channels para orquestrar o fluxo de dados.
- Gerenciamento de Recursos (Worker Pools): Controlar o nível de paralelismo. Não queremos 10.000 goroutines abrindo 10.000 arquivos de uma vez. Você deve implementar um pool de workers para evitar que seu serviço sufoque a máquina.
- Ciclo de Vida (Robustez): Dominar o
sync.WaitGroupe o fechamento de channels para garantir que o sistema desligue de forma limpa, sem perder um único evento que estava "em trânsito".
💻 Suas Tarefas (O "Como")
Você deve criar um arquivo main.go que implemente as 4 funções a seguir. Na sua função main, você deve:
- Gerar um conjunto de arquivos de log de teste (use a função
GenerateMockFilesfornecida). - Chamar cada uma das 4 funções.
- Medir o tempo de execução (
time.Since(start)) de cada uma e imprimir os resultados para comparação.
🎯 Parte 1: A Baseline (Processamento Síncrono)
Implemente a versão mais simples e direta. Ela deve processar um arquivo de cada vez, sequencialmente, na goroutine principal.
- Assinatura:
func ProcessSequential(files []string) *Report - Lógica:
- Crie um
ReportusandoNewReport(). - Use um
for...rangepadrão sobre a lista defiles. - Dentro do loop, abra o arquivo, leia-o linha por linha (sugestão:
bufio.Scanner). - Para cada linha válida, decodifique o JSON para um
Evente chamereport.AddEvent(event). - Para cada linha inválida, chame
report.AddError().
- Crie um
🎯 Parte 2: Concorrência Ingênua (O Problema)
Agora, vamos tentar "acelerar" as coisas processando cada arquivo em sua própria Goroutine. Esta implementação é intencionalmente falha.
- Assinatura:
func ProcessConcurrentNaive(files []string) *Report - Lógica:
- Crie um único
Reportcompartilhado. - Crie um
sync.WaitGrouppara rastrear as goroutines. - Itere sobre
files. Para cada arquivo:wg.Add(1)- Inicie uma goroutine
go func(filename string) { ... }. - Dentro da goroutine:
defer wg.Done(), processe o arquivo (abrir, ler, decodificar). - Para cada evento, chame
report.AddEvent(event). - Para cada erro, chame
report.AddError().
- Chame
wg.Wait()para esperar todas as goroutines terminarem. - Retorne o relatório.
- Crie um único
🎯 Parte 3: Corrigindo com Mutex (A Solução de Memória Compartilhada)
Agora que você viu o problema, corrija-o usando a abordagem de "memória compartilhada protegida".
- Assinatura:
func ProcessConcurrentMutex(files []string) *Report - Lógica:
- Copie sua implementação da Parte 2 (incluindo
WaitGroupe goroutines). - A única diferença é: dentro da goroutine, em vez de chamar os métodos
AddEventeAddError, você deve chamar as versões seguras:report.AddEventSafe(event)ereport.AddErrorSafe().
- Copie sua implementação da Parte 2 (incluindo
🎯 Parte 4: O Padrão Idiomático (Worker Pool & Channels)
Esta é a arquitetura mais escalável. Vamos refatorar para um padrão que evita a memória compartilhada, seguindo o lema do Go: "Não comunique compartilhando memória; compartilhe memória comunicando."
- Assinatura:
func ProcessPipeline(files []string, numWorkers int) *Report - Lógica:
- Canais: Crie dois canais:
jobs := make(chan string, len(files))results := make(chan ProcessResult, 1000)(Use uma structProcessResultque possa conter ou umEvent, ou umerror).
- Workers (Fan-Out):
- Crie um
sync.WaitGrouppara os workers. - Inicie
numWorkersgoroutines. - Cada worker deve ler
filenamedo canaljobs(for filename := range jobs). - O worker processa o arquivo (abrir, ler, decodificar) e envia
ProcessResults para o canalresults.
- Crie um
- Aggregator (Fan-In):
- Crie um
NewReport(). - Inicie uma única goroutine agregadora que será a única a ler do canal
results(for res := range results). - Dentro dela, verifique o
ProcessResult: seres.Err != nil, chamereport.AddError(); senão, chamereport.AddEvent(res.Event). (Note que usamos as versões sem lock, pois esta é a única goroutine que modifica oreport).
- Crie um
- Coordenação:
- Envie todos os
filespara o canaljobs. close(jobs)(Sinaliza aos workers que não há mais trabalho).- Espere todos os workers terminarem (
wg.Wait()). - Depois que eles terminarem,
close(results)(Sinaliza ao agregador que não há mais resultados). - Espere o agregador terminar (use um canal
doneou outroWaitGroup) e então retorne oreport.
- Envie todos os
- Canais: Crie dois canais:
Resultados Esperados
- Os Dados Precisam Estar Corretos (Não-Negociável):
- As implementações, Parte 1 , Parte 3 e Parte 4 devem produzir um relatório final com resultados idênticos.
- A Parte 2 deve, previsivelmente, falhar em produzir o resultado correto ou, no mínimo, ser comprovadamente insegura.
- Robustez e Segurança:
- A execução do
go run -race .deve passar limpa (sem avisos) para as Partes 1, 3 e 4. - A execução do
go run -race .na Parte 2 deve obrigatoriamente detectar e reportar a "DATA RACE". Você deve ser capaz de apontar no código exatamente onde e por que ela ocorre.
- A execução do
- Performance Mensurável:
- Você deve fornecer métricas de benchmark (usando
time.Since(start)ou o pacotetesting) que comprovem a melhoria de desempenho das Partes 3 e 4 em relação à baseline (Parte 1) usando um conjunto de dados significativo (ex: 100 arquivos, 10.000 linhas/arquivo).
- Você deve fornecer métricas de benchmark (usando
- Adesão aos Padrões Exigidos:
- A Parte 3 deve usar
sync.Mutex(ousync.RWMutex) para garantir a segurança. - A Parte 4 deve obrigatoriamente usar o padrão Worker Pool e um pipeline de channels (Fan-Out/Fan-In), removendo a necessidade de um lock no agregador.
- A Parte 3 deve usar
Tarefas
Use este checklist para ajudar a organizar a sua entrega
Resolução
Confira os resultados esperados do projeto

Envie o projeto para ver a resolução
Ao enviar seu projeto, você poderá conferir os resultados esperados