Aprenda a gerar dados aleatórios com paralelização no shell

Aprenda a gerar dados aleatórios com paralelização no shell

head /dev/urandom

Gerar dados aleatórios com paralelização no shell. Falando assim parece até alguma coisa muito importante ou difícil, mas vamos entender sua utilidade na prática. Vez ou outra preciso de uma forma rápida de fazer isso, geralmente quando estou ministrando um curso de MySQL ou MongoDB, ou prestando alguma consultoria.

Durante os cursos, pode haver a necessidade de gerar alguns dados aleatórios no shell, para demonstrar características de índices, compressão, fragmentação etc. Outras vezes, em consultoria, gero dados similares aos que o banco recebe para monitorar seu comportamento. Acontece que gerar os dados não é o problema, mas a velocidade em criá-los ou executar os testes sim, e é neste caso entra a paralelização.

Particularmente, prefiro fazer isso em Perl, mas o shell nos fornece alguns mecanismos interessantes e quero compartilhá-los aqui. Existem ferramentas, como o Sysbench, que permitem simular estes aspectos, mas por vezes queremos algo mais simples, ou não estamos trabalhando diretamente com um banco de dados.

Programar utilizando o shell ainda é, e provavelmente sempre será, um dos grandes diferenciais de um bom administrador de sistemas.

/dev/urandom

Tudo no Linux é um “arquivo”, e o nosso gerador de dados aleatórios não poderia ser diferente. Em /dev/urandom podemos encontrar esse arquivo, que é uma interface para acessar o gerador de números aleatórios do kernel. Existe também um “irmão mais velho” em /dev/random, mas é uma interface legada com uma entropia menor.

Sempre que acessamos os dados de /dev/urandom, ele nos retornará bytes pseudoaleatórios, fornecidos por uma reserva de entropia, em inglês, entropy pool. Apesar de parecer que falamos de um processo aleatório, ele não é. Esse nome aparece porque o nível de “bagunça” (entropia) para gerá-lo é tão complexo e difícil de se reproduzir, que será improvável gerar uma nova sequência com os mesmos valores. A aleatoriedade é uma propriedade extremamente custosa, o que me faz pensar se o universo realmente possui eventos aleatórios…

Mas voltando ao assunto da postagem, o entropy pool é alimentado pelo ruído dos drivers do dispositivo e outros recursos. É daí que vem nossos dados aleatórios, e essa mistura toda garante que novos dados possam ser gerados o tempo todo, já que o entropy pool é reabastecido com certa frequência.

Gerar Dados Aleatórios

Para puxar dados do /dev/urandom é preciso ter alguns cuidados:

  • Utilizar binários que limitem a leitura do início, como por exemplo head;
  • Filtrar ou converter os bytes em caracteres.

O parâmetro -c de head controla a quantidade de bytes lida:

head -c10 /dev/urandom | base64
# oQ0rqJqHribc7g==
head -c20 /dev/urandom | md5sum
# b6964de932cd4efb6f989d44fcf8aa30
head -c40 /dev/urandom | sha256sum
# 843cdbc6d898213e5baf2eb38accbc70c63003a989729efcab7109041a1d5da0

Utilizando o -d no tr podemos remover os caracteres especificados pelas classes [:alpha:] ou [:digit:] e com o parâmetro -c nós passamos a utilizar o que foi excluído, não o que restou:

tr -dc [:alpha:] < /dev/urandom | head -c10
VRxbBMGCaS
tr -dc [:digit:] < /dev/urandom | head -c10
4554571015

As vezes não temos o /dev/urandom porque estamos em outro sistema utilizando, por exemplo, o Cygwin. Porém, por vezes, temos alguns comandos do Linux disponíveis como o bom e velho openssl. Neste caso o próprio openssl pode ser utilizado diretamente ou mais ou menos da forma como utilizamos o /dev/urandom:

openssl rand -base64 10
# ktxtdP0kpvm8cA==
openssl rand -hex 10
# 3b64934a9716cab18a2c
openssl rand 200 | tr -dc [:alpha:] | head -c10
# wgaWFRhNcz

Agora que sabemos como gerar os dados, vamos criar um script para inserir estes dados em um banco, por exemplo, MySQL.

Shell Script Inicial

#!/bin/bash

cat <<'EOF' > ~/.my.cnf
[client]
user = root
password = !Abc123
host = 127.0.0.1
EOF

mysql -e 'CREATE DATABASE IF NOT EXISTS pseudo'; 
mysql <<EOF 
CREATE TABLE IF NOT EXISTS pseudo.aleatorio ( 
   id INT PRIMARY KEY AUTO_INCREMENT, 
   numero INT, 
   texto CHAR(10) 
) 
EOF

for X in {1..1000}; do
   TEXTO=$(tr -dc [:alpha:] < /dev/urandom | head -c 10)
   NUMERO=$(tr -dc [:digit:] < /dev/urandom | head -c 5)
   mysql pseudo -e "INSERT INTO aleatorio (numero, texto) VALUES ($NUMERO, '$TEXTO')"
done

Este script funciona bem, mas demora bastante, veja:

time bash insert.sh
# real    0m11.972s 
# user    0m6.908s 
# sys     0m6.435s

Os script levou quase 12 segundos para cadastrar 10 mil registros em um Ryzen 5, e isso é bastante tempo. Mas a culpa não é do script, ele executou o mais rápido que pôde, o problema é o script executar todos estes comandos em sequência, um após o outro.

Shell Script com Paralelização

O próximo script é ligeiramente mais complexo que o anterior. Ele controla a quantidade de processos paralelos pelo módulo da variável de controle $X do nosso laço for, além de utilizar um comando built-in wait do shell. Não adicionamos muita coisa, e já temos um script capaz de gerar dados aleatórios com paralelização no shell:

#!/bin/bash 

cat <<'EOF' > ~/.my.cnf 
[client] 
user = root 
password = !Abc123 
host = 127.0.0.1 
EOF 

mysql -e 'CREATE DATABASE IF NOT EXISTS pseudo'; 
mysql <<EOF 
CREATE TABLE IF NOT EXISTS pseudo.aleatorio ( 
   id INT PRIMARY KEY AUTO_INCREMENT, 
   numero INT, 
   texto CHAR(10) 
) 
EOF

function _insert { 
   TEXTO=$(tr -dc [:alpha:] < /dev/urandom | head -c 10) 
   NUMERO=$(tr -dc [:digit:] < /dev/urandom | head -c 5) 
   mysql pseudo -e "INSERT INTO aleatorio (numero, texto) VALUES ($NUMERO, '$TEXTO')" 
} 

for X in {1..1000}; do 
   _insert & 
   if [ $(($X % 10)) == 0 ]; then 
       wait 
   fi 
done 

wait # força espera caso total de iterações não seja múltiplo de 10

O paralelismo foi conseguido através do operador de controle & mas o truque está na função declarada como _insert. Declarando uma função no shell, podemos agrupar quantos comandos forem necessários e chamar esta função executando-os todos em segundo plano. Mas para evitar que 10 mil processos sejam gerados, nós fazemos uma pequena verificação.

A verificação feita na estrutura de controle if retorna 0 (zero) somente quando a variável $X for um múltiplo de 10 (0, 10, 20, 30, 40…). Como iniciamos a variável de controle como 1, o script só executará wait quando chegar a 10 processos rodando em segundo plano.

A chamada de wait, um comando built-in do shell, faz com que o shell espere por processos identificados pelos IDs que fornecermos. No nosso caso não fornecemos ID nenhum, isso faz com que o shell espere por todos os processos filhos, ou seja, todos os nossos processos que estão rodando em segundo plano.

Vamos executá-lo!

Opa! Conseguimos diminuir o tempo pela metade:

time bash insert.sh  
# real    0m5.456s 
# user    0m7.631s 
# sys     0m42.539s

Conclusão

Sem nenhum pacote extra do Linux, apenas com o conhecimento que já temos, conseguimos gerar dados aleatórios com paralelização no shell sem muitas dificuldades.

É possível gerar scripts bastante poderosos, para simular muitas coisas, nas mais diversas aplicações. Ao invés de uma inserção no banco, podemos simular chamadas com curl em uma api do Kubernetes. Esta forma é bastante rudimentar e exige bem mais da máquina do que uma linguagem de programação graças as constantes chamadas a binários, o que diminui um pouco nossa poder de fogo, mas é bastante útil nos momentos em que não temos essas opções à mão.

 

Líder em Treinamento e serviços de Consultoria, Suporte e Implantação para o mundo open source. Conheça nossas soluções:

CURSOSCONSULTORIA

Anterior Novo curso na trilha de Python!
Próxima phpIPAM: É um bom gerenciador de redes?

About author

Hector Silva
Hector Silva 7 posts

Hector Vido Silva atua como Consultor de Infraestrutura em Software Livre, cursando Engenharia da Computação, formado em Análise e Desenvolvimento de Sistemas com MBA em Cibersegurança. Atua desde 2006 com desenvolvimento, implantação, arquitetura e resolução de problemas em ambientes envolvendo diferentes plataformas. Na 4Linux também ministra os cursos de desenvolvimento, banco de dados e infraestrutura para ambientes ágeis e automatizados com forte utilização de contêineres. Sim, é um generalista. Possuí certificações CKA, Openshift, LPIC-2, LPI DevOps, GCP Engineer, MongoDB, Blockchain e ZCE.

View all posts by this author →

Você pode gostar também

Desenvolvimento

API RESTful com Laravel – Parte 2

Neste segundo post, iremos ver a nossa API Laravel funcional e mostrar na tela os nossos resultados. Iremos ver como o Laravel torna simples a criação de Rotas e Controller,

Infraestrutura TI

Linux Bonding: Alta Disponibilidade em Interfaces de Rede

Quando falamos de infraestrutura, um dos pré-requisitos é se pensar em alta disponibilidade, seja ela de: Máquinas Virtuais Storages Máquinas Físicas Links de Internet E etc. Nesse post vou explicar

Segurança

Gerenciando identidades e acessos com Keycloak – parte 1

O Keycloak [1] é um software open source de gerenciamento de identidade e acesso voltado para WebApps e RESTful web services. Inicialmente desenvolvido em 2014 pela comunidade de JBoss. Em