3
respostas

[Projeto] Execuções em Filas no SO Linux

Olá pessoal,

Tenho uma incógnita em meu projeto e gostaria de saber a opinião de vocês, caso alguém já tenha trabalhado em algo parecido e queira contribuir. Meu projeto, resumidamente é um sistema de front-end (em laravel 11) que recebe requisições de usuários e salva em um banco de dados (mysql). O backEnd da aplicação (Python) é responsavel por obter cada uma das requisições e fazer um processamento em fila. Este processamento consiste em pegar um arquivo de strings submetido pelo usuário na requisição, obter parametros definidos pelo usuario através da interface gráfica no ato da requisição, e a partir daí montar um comando que deve executar um programa monousuário de bioinformática, o qual fará o processamento do arquivo submetido pelo usuário. Este programa de bioinformática só opera via linhas de comando, neste caso em SO Linux Ubuntu, e a execução do comando com arquivo da requisição deve ser observada, pois o programa nao possui nenhum parametro para acomanhar o estado da operação que tem como resultado um arquivo de saída. A única forma de monitorar é através do ID do processo e consumo de recursos (memória e processador), e do arquivo de saída (que é gerado imediatamente após execução do comando, porém, é preenchido aos poucos). Como o front-end deve trabalhar em laravel e o back-end em python, ainda não sei exatamente como posso fazer a integração dessas partes, uma vez que ambas podem tentar alterar um campo do banco de dados ao mesmo tempo. Além disso, tem a questão da fila de execução para processar as requisições que podem a qualquer momento serem canceladas pelo usuário (no momento utilizo um atributo para cada requisição que é o "status", se estiver "aguardando", o back-end pode processar, se estiver em "cancelado" e já estiver na fila para ser executado, deve remover, outros estados são "processado" e "falha"). Já pensei em utilizar Redis para essa fila de execução, e tambem o Snakemake, poreḿ, não consigo ver como ficaria essa integração, além disso, o Snakemake não é indicado para execução cíclica, que é o que preciso, na verdade, um just in-time. Outra questão que penso é não utilizar muitos softwares terceiros, e sim desenvolver tudo em único código (na parte do back-end), para limitar possíveis pontos de falha, pois só até aqui já tenho em cima do SO ubuntu server (quem deve executar essa aplicação web), o laravel, o software que preciso executar e que pode laçar erros, o proprio SO que pode lançar erros, o python, servidor apache, servidor Js. Não sei se ficou claro, mas caso alguém tenha uma outra visão que possa contribuir, aceito opiniões.

Att.

3 respostas

Oi Luiz, tudo bem?

Vou deixar umas sugestões para te ajudar.

  1. Integração Front-end (Laravel) e Back-end (Python):

    Para a integração entre Laravel e Python, uma abordagem comum é utilizar filas de mensagens, como o Redis, que você já mencionou. O Redis pode ser usado tanto para gerenciar a fila de execução quanto para comunicação entre Laravel e Python. Aqui está um exemplo básico de como você pode configurar isso:

    • Laravel (Front-end):

      • Quando uma requisição é recebida, você pode empilhar uma mensagem no Redis com os detalhes da requisição.
      • Laravel pode usar a biblioteca predis/predis para interagir com o Redis.
      // Exemplo de código Laravel para empilhar uma mensagem no Redis
      use Illuminate\Support\Facades\Redis;
      
      $requestData = [
          'user_id' => $userId,
          'file_path' => $filePath,
          'parameters' => $parameters,
      ];
      
      Redis::rpush('processing_queue', json_encode($requestData));
      
    • Python (Back-end):

      • O script Python pode ser configurado para monitorar a fila do Redis e processar as requisições conforme elas chegam.
      • Você pode usar a biblioteca redis-py para interagir com o Redis.
      import redis
      import json
      
      r = redis.Redis(host='localhost', port=6379, db=0)
      
      while True:
          _, request_data = r.blpop('processing_queue')
          request_data = json.loads(request_data)
          # Processar a requisição aqui
          process_request(request_data)
      
  2. Monitoramento de Processos:

    Para monitorar o estado do processo de bioinformática, você pode usar o módulo psutil em Python para acompanhar o ID do processo e o consumo de recursos.

    import psutil
    import subprocess
    
    def run_bioinformatics_command(command):
        process = subprocess.Popen(command, shell=True)
        return process.pid
    
    def monitor_process(pid):
        try:
            process = psutil.Process(pid)
            while process.is_running():
                print(f"CPU: {process.cpu_percent()}%, Memory: {process.memory_info().rss}")
                # Aqui você pode atualizar o banco de dados com o status do processo
        except psutil.NoSuchProcess:
            print("Processo não encontrado")
    
    pid = run_bioinformatics_command("comando_bioinformatica")
    monitor_process(pid)
    
  3. Gerenciamento de Estado:

    Para gerenciar o estado das requisições (aguardando, cancelado, processado, falha), você pode usar transações no banco de dados para garantir que apenas um processo possa alterar o estado de uma requisição em um dado momento. Laravel e Python possuem suporte a transações para MySQL.

  4. Cancelamento de Requisições:

    Para cancelar uma requisição, você pode verificar o estado da requisição antes de iniciar o processamento no backend Python. Se o estado for "cancelado", simplesmente ignore a requisição.

Espero ter ajudado e bons estudos!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓.

Olá Armano, obrigado pela participação!

O que acha de utilizar Apache kafka ao invés do Redis, assim evito de ter dois bancos de dados, até porque armazenaria no Redis apenas o ID da requisição para ficar em fila. Porém, com qualquer uma das soluções ainda vejo problema de concorrência no acesso ao banco de dados mysql que é onde vou salvar as requisições. Ao utilizar um campo "status", com valores possíveis (processando, falha, processado, aguardando), o back-end após processar a requisição deve alterar seu status, assim como o usuário ao cancelar sua pesquisa em mesmo instante de tempo o láravel pode tentar alterar o status no banco de dados. Assim como outros cenários durante o processo podem incorrer em problmeas de concorrência. Ainda não sei como posso resolver isso. O Kafka me pareceu uma solução pronta para apenas implementar, porém, há pouca flexibilidade no gerenciamento das mensagens no broker. Veja, pelo que pesquisei existem apenas duas formas de excluir a mensagem do broker após o consumidor processar ela:

  • por limite de armazenamento, ao atingir limite o kafka exclui mensagens mais antigas, porém, pode ocorrer de mensagens mais antigas ainda não terem sido processadas, digamos que o sistema receba muitas requisições.
  • por tempo, porem, esse tempo pre-definido conta a partir do momento que a mensagem é criada pelo produtor, logo, tambem se enquadra no problema de perder-se a mensagem antes que seja processada pelo back-end.

Teria que ter aí uma forma manual de gerenciar isso, de modo que somente após o consumo a mensagem fosse excluída. Aqui está o conceitual do meu banco de dados, caso ajude a entender melhor a situação: https://docs.google.com/document/d/1Kbb53ZB8CYJITZuYEPhYJxyCWUe_MzgvTJcAB_vnsLk/edit?usp=sharing

Opa, Luiz, boa tarde!

Você está certo ao considerar as complexidades envolvidas na gestão de mensagens no Kafka, especialmente em cenários onde é crucial garantir que as mensagens sejam processadas corretamente antes de serem removidas do broker. Aqui estão algumas abordagens que podem ajudar a lidar com esses desafios de concorrência:

  1. Confirmação Manual: Uma abordagem seria implementar a confirmação manual das mensagens pelo consumidor após o processamento completo da requisição. Isso garante que a mensagem só seja removida do Kafka após ter sido processada com sucesso.

  2. Partições e Consumidores: Utilizar partições no Kafka pode ajudar a distribuir o processamento entre vários consumidores. Cada partição mantém a ordem das mensagens, permitindo que múltiplos consumidores processem simultaneamente sem interferir uns com os outros.

  3. Status de Processamento: Ao atualizar o status da requisição no MySQL, considere utilizar um mecanismo que gerencie locks de forma eficiente, como transações no MySQL ou semântica de "lock pessimista" no Laravel, para evitar condições de corrida.

  4. Timeouts e Retentive Windows: No Kafka, você pode configurar timeouts e retentive windows para garantir que as mensagens sejam retidas por um período mínimo após a produção, mesmo que o consumidor ainda não tenha processado completamente. Isso ajuda a minimizar a perda de mensagens antes do processamento.

  5. Monitoramento e Retentativa: Implementar um mecanismo de monitoramento e retentativa para mensagens não processadas pode ser necessário. Isso pode incluir políticas de retry para mensagens que falharam no processamento inicialmente.

Ao escolher entre Kafka e Redis, considere também a escalabilidade e a complexidade operacional de cada solução em relação às suas necessidades específicas de concorrência e gerenciamento de filas.