08 março 2023

Limitando uso de recursos de máquina pelo Docker no WSL2 (VMMEM)

 Trabalhar com containers no Windows tornou-se fácil depois do WSL2. Com ele é possível ter uma experiência de usabilidade próxima ao GNU/Linux, porém existe um problema crônico em toda solução Windows que é o excesso de uso de memória RAM, causando lentidão e travamentos no sistema, afetando outros serviços e recursos. No Docker for Windows não é diferente, após fazer a instalação e integração com WSL2, é necessário deixar explicito o limite de uso de recursos, seja CPU ou RAM.

Como limitar?

No meu caso, estava com um sério problema de excesso de uso de memória RAM. Minha máquina é bem limitada, porém o Docker com o WSL utilizava em médio 70 à 90% da memória (de 4GB). Meu objetivo era limitar a 1GB de uso, apenas da memória RAM. Para isso é necessário criar um arquivo .wslconfig no "HOME" do Windows (%UserProfile%/.wslconfig, o mesmo que C:\Users\<meu usuario>\.wslconfig).

Abra o explorador de arquivos (win + E) e na barra de endereços digite:




Após aberto o editor com o arquivo, basta limitar o número da memória RAM a ser utilizada:

[wsl2]
memory=1GB
debugConsole=true

Além de notar o desempenho melhor para confirmar a configuração, é possível visualizar no Gerenciador de Tarefas o processo VMMen com consumo abaixo dos 1GB:



Referências:

06 janeiro 2023

Manipulando caminhos (path) e arquivos no Python moderno

Uma atividade comum no desenvolvimento de software com Python é a manipulação de arquivos e caminhos do sistema operacional. Seja para leitura de um arquivo ou a escrita do mesmo em qualquer que seja o caminho destino. Isso não é complicado no Python com a ajuda da biblioteca os e a instrução open, por exemplo. Porém podemos acabar tendo que descrever em mais linhas que o necessário em versões mais modernas do Python ou utilizando bibliotecas "pobre" de recursos para este fim. Tomemos como exemplo um trecho de código comum em muitos programas escritos em Python (2 ou 3). O objetivo deste pequeno trecho de código é ler o arquivo de configuração do git, se existir, caso contrário não retorna nada:

import os

git_config = os.path.join(os.path.expanduser('~'), '.gitconfig')

if os.path.exists(git_config):
    with open(git_config, 'r') as raw_file:
        print(raw_file.read())

É um código funcional e cumpre seu objetivo. Mas existem formas "melhores" de descrever essa mesma atividade, em menos linhas, sem truques de abertura ou fechamento de arquivos e com recursos nativos da linguagem.

A partir da versão >= 3.4, Python introduziu o módulo pathlib. Com o principal e único objetivo de fazer manipulações de caminhos e arquivos de forma genérica e compatível com diferentes sistemas operacionais. Além de não retornar uma simples string, como é o caso do módulo os.path. o pathlib retorna um objeto Path (se for Unix PosixPath se for Windows WindowsPath). Além permitir uma sintaxe mais expressiva em relação aos caminhos, já que permite o uso de / (barras) como separador o módulo conta com recursos para leitura e escrita sem a necessidade do statement open.

import pathlib

git_config = pathlib.Path.home() / '.gitconfig'

if git_config.exists():
    print(git_config.read_text())

É possível também fazer a escrita de forma mais simples e sem a necessidade do uso do gerenciador de contexto ou fechamento de arquivos de forma explicita:

import pathlib

my_file = pathlib.Path('/tmp') / 'my_file.txt'
content = 'content file with pathlib module'

my_file.write_text(content)

18 novembro 2022

Integridade de dados e race condition com sinais (signals) no Django

Para quem já trabalhou com Django, sabe dos ótimos recursos disponíveis dentro do framework, além do ilustríssimo e ótimo ORM, uma estrutura de rotas, comunidade enorme e uma vasta experiência para fornecer ferramentas de qualidade para resolução de problemas no desenvolvimento de aplicações web. Mas além desses recursos ele conta também com um o sinais ou signals um disparador de eventos que acontece antes ou depois que uma ação é feita.

Uma das formas mais comuns de usar sinais no Django é quando precisamos executar uma determinada tarefa depois de salvar as informações no banco de dados, claro, partindo de um modelo (model) qualquer. O código seria assim:

from django.db.models.signals import pre_save
from django.core.mail import send_mail
from django.dispatch import receiver

from bank.models import Account

@receiver(post_save, sender=Account)
def send_mail(sender, instance, **kwargs):
    if instance.balance < 0:
        send_mail(
            subject="Saldo baixo!",
            message="Você entrou no cheque especial",
            from_email="admin@domain.tld",
            recipient_list=["user@domain.tld"]
        )

Essa seria uma possível implementação, porém falha, dado que parte da instrução da função send_email depende dos dados do modelo, como é visto logo abaixo da definição dela. O problema é que logo após o sinal ser acionado, o banco de dados ainda está processando o salvamento das informações, por tanto pode ser que no momento da consulta do instance.balance o valor seja o anterior e não 0, como seria esperado, isso é conhecido também como race condition.

A resolução do problema é simples e utilizando os próprios recursos do Django. Dado que as duas atividades estão perdendo a comunicação ao longo de suas execuções e uma depende da outra. É necessário esperar uma ser concluída para em seguida acionar a outra. Para isso é necessário garantir que os dados foram salvos para na sequência fazer o disparo da tarefa (send_mail). Para isso podemos usar o modulo de transaction e a função on_commit que é a responsável por garatir que o sinal será chamado ao fim da "transação" de dados no banco (ou seja depois que ele salvar os dados).

O uso do on_commit para essa finalidade teria uma implementação assim:

[...]

from django.db import transaction

def do_mail(instance):
    if instance.balance < 0:
        send_mail(
        subject="Saldo baixo!",
        message="Você entrou no cheque especial",
        from_email="admin@domain.tld",
        recipient_list=["user@domain.tld"]
    )

@receiver(post_save, sender=Account)
def send_mail(sender, instance, **kwargs):
    transaction.on_commit(lambda: do_email(instance))

Os sinais do Django é uma ferramenta muito útil em muitos casos, mesmo tendo possibilidade de adicionar ferramentas externas para atividades de baixa escala o uso desse recurso se faz necessário, porém é importante entender alguns detalhes para extrair o máximo possível de funcionalidade durante o desenvolvimento de aplicações web.