O uso de dados em testes de performance é uma etapa essencial do planejamento e, ao mesmo tempo, um dos grandes desafios dessa prática. A conhecida massa de teste, frequentemente utilizada em testes funcionais, apresenta obstáculos como manutenção, validade e renovação dos dados.
Em testes não funcionais, esses desafios se tornam ainda mais evidentes devido à necessidade de grandes volumes de dados para simular cenários em alta escala, o que pode resultar em um alto consumo de recursos computacionais.
Neste artigo, exploraremos como o K6 gerência dados de teste, e de que forma o SharedArray pode otimizar o uso de recursos, tornando seus testes mais eficientes.
Pré-requisitos📑
- K6 instalado
Um erro comum🗣️
Como o K6 utiliza a sintaxe do JavaScript para a criação de scripts, muitos usuários acabam, erroneamente, empregando funções nativas da linguagem de forma inadequada. Um dos erros mais comuns é o uso da função open()
para abertura e manipulação de arquivos:
export const options = {
vus: 5,
duration: '10s',
}
const data = JSON.parse(open('./data.json'));
export default function () {
console.log(data[0].username);
}
No exemplo acima, cada VU terá sua própria cópia do conteúdo de data.json
, manipulando-o ao longo do seu ciclo de vida. Em um cenário simples, o impacto no uso de memória e CPU pode não ser perceptível para o usuário ou o ambiente. No entanto, vamos analisar um cenário mais realista:
export const options = {
stages: [
{ duration: '15m', target: 300 },
{ duration: '30m', target: 300 },
{ duration: '15m', target: 0 },
],
}
const data = JSON.parse(open('./data.json'));
export default function () {
console.log(data[0].username);
}
Neste exemplo, modelamos um cenário um pouco diferente: um teste com pico de 300 VUs rodando em um ambiente com memória limitada. Se o arquivo data.json
for grande o suficiente, a execução do script pode ser interrompida por falta de memória.
Além disso, mesmo que haja memória disponível, o impacto no uso da CPU pode ser significativo. Como o K6 é escrito em Go, ele conta com um garbage collector que percorre todos os objetos instanciáveis, incluindo os do Javascript, para identificar quais podem ser descartados. Em cenários onde grandes matrizes de dados são copiadas centenas de vezes, esse processo pode gerar uma sobrecarga adicional na CPU.
O modulo fs🧠
O K6 disponibiliza um módulo experimental de file system(fs), oferecendo uma alternativa mais eficiente para lidar com interações com arquivos dentro dos scripts de teste. Esse módulo permite abrir arquivos, ler seu conteúdo, realizar buscas e recuperar metadados de forma otimizada.
import { open } from 'k6/experimental/fs';
Uma das principais vantagens de utilização da função open()
do modulo file system é sua eficiência na utilização de memória. Diferente da função open()
tradicional do javascript, que carrega um arquivo várias vezes em memória para cada Vu, o módulo file system reduz o uso de memória carregando o arquivo o mínimo possível e compartilhando o mesmo endereço de memoria entre todas as VUs.
import { open } from 'k6/experimental/fs';
const data = await open('./data.txt');
export default async function () {
console.log(JSON.parse(data));
}
Conhecendo o modulo SharedArray📚
O SharedArray é uma função disponível no módulo k6/data
e oferece uma forma eficiente de manipular dados em testes de performance com o K6. Ele atua como um array que é inicializado apenas uma vez e compartilha seu endereço de memória entre todas as VUs, reduzindo o consumo de recursos e melhorando a eficiência dos testes.
import { SharedArray } from 'k6/data';
const data = new SharedArray('some name', function () {
const dataArray = [];
// more operations
return dataArray; // must be an array
});
O SharedArray deve ser criado durante a fase de inicialização do teste. Entre os requisitos para sua configuração, é necessário atribuir um nome a função anônima e garantir que o retorno seja um array. Caso a criação do sharedArray ocorra fora do escopo de inicialização, uma exceção será lançada durante a execução do teste.
Quando falamos em dados compartilhados, é comum pensar em condições de corrida ou concorrência. No entanto, o SharedArray funciona apenas para leitura, eliminando esses problemas. As operações suportadas incluem:
- Obter o número de elementos com length
- Acessar um elemento pelo índice usando array[index]
- Iterar sobre os dados com for-of loops
Para conjuntos de dados pequenos, o SharedArray pode ter um desempenho inferior à função open()
do módulo filesystem e a função open()
do javascript. No entanto, a eficiência depende do caso de uso e do modelo do teste.
Para fins de comparação, a equipe do K6 realizou um experimento com 100 VUs, executando um script com arquivos de diferentes tamanhos e quantidade de linhas para avaliar o impacto no desempenho.
import { check } from 'k6';
import http from 'k6/http';
import { SharedArray } from 'k6/data';
const n = parseInt(__ENV.N);
function generateArray() {
const arr = new Array(n);
for (let i = 0; i < n; i++) {
arr[i] = { something: 'something else' + i, password: '12314561' };
}
return arr;
}
let data;
if (__ENV.SHARED === 'true') {
data = new SharedArray('my data', generateArray);
} else {
data = generateArray();
}
export default function () {
const iterationData = data[Math.floor(Math.random() * data.length)];
const res = http.post('https://quickpizza.grafana.com/api/post', JSON.stringify(iterationData), {
headers: { 'Content-type': 'application/json' },
});
check(res, { 'status 200': (r) => r.status === 200 });
}
Script utilizado para benchmark.
Para arquivos de até 1.000 linhas, a abordagem de compartilhamento de dados do SharedArray e a cópia de dados na memória da função open()
do JavaScript apresentaram resultados semelhantes. No entanto, para arquivos com mais de 10.000 linhas, observou-se uma diferença significativa no uso de recursos, como pode ser visto na tabela abaixo:
Qtd linhas | Compartilhado | CPU % | Uso de MEM | Solicitações HTTP |
---|---|---|---|---|
100 | verdadeiro | 70-79% | 213-217 MB | 92191-98837 |
100 | falso | 74-75% | 224-232 MB | 96851-98643 |
1000 | verdadeiro | 74-79% | 209-216 MB | 98251-98806 |
1000 | falso | 75-77% | 333-339 MB | 98069-98951 |
10000 | verdadeiro | 78-79% | 213-217 MB | 97953-98735 |
10000 | falso | 80-83% | 1364-1400 MB | 96816-98852 |
100000 | verdadeiro | 78-79% | 238-275 MB | 98103-98540 |
100000 | falso | 120-124% | 8,3-9,1 GB | 96003-97802 |
Compartilhado representa a utilização de sharedArray. Números puramente ilustrativos em experimento conduzido pela equipe do K6.
Utilizando SharedArray
O processo de utilização do SharedArray em arquivos é bastante simples. O usuário precisa apenas encapsular sua estrutura de dados dentro do SharedArray.
const data = new SharedArray('nome funcao', function () {
return dataArray; // must be an array
});
Arquivos JSON📄
Para a leitura de arquivos json, podemos utilizar a função open()
do javascript ou do modulo file system.
import { SharedArray } from 'k6/data';
const data = new SharedArray('leitura dados', function () {
return JSON.parse(open('./data.json')).users;
});
export default function () {
console.log(data[0].username);
}
{
"users": [
{ "username": "test", "password": "qwerty" },
{ "username": "test", "password": "qwerty" }
]
}
Após a abertura do arquivo, podemos realizar o parse para JSON utilizando a função JSON.parse()
. A estrutura de dados retornada pela nossa função anônima será encapsulada pelo SharedArray, e os dados serão distribuídos entre todas as VUs por meio da constante data.
Arquivos CSV📄
Na leitura de arquivos CSV, utilizava-se o módulo PapaParse juntamente com o k6.jslib para realizar o parse dos dados lidos. Em seguida, o SharedArray era utilizado para distribuir os dados entre as VUs de forma eficiente.
Na leitura de arquivos CSV, utilizava-se o módulo PapaParse
da k6.jslib
para realizar o parse dos dados lidos. Em seguida, o SharedArray é utilizado para distribuir os dados entre as VUs de forma eficiente.
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { SharedArray } from 'k6/data';
const csvData = new SharedArray('leitura csv', function () {
// Leitura e parse do CSV
return papaparse.parse(open('./data.csv'), { header: true }).data;
});
export default function () {
// ...
}
A partir da versão 0.54 do K6, foi introduzido o módulo experimental k6/experimental/csv
, que simplifica esse processo. Agora, não é mais necessário utilizar o módulo PapaParse para realizar o parse dos dados.
import { open } from 'k6/experimental/fs';
import csv from 'k6/experimental/csv';
const file = await open('data.csv');
const dadosCsv = await csv.parse(file, { delimiter: ',' });
export default async function () {
// ...
}
Uma combinação dos modulos file system e csv simplifica o processo de leitura, onde a função csv.parse() já encpasula os dados brutos em um sharedArray para melhorar o desempenho de utilização de dados.
Ambos os modulos csv e fs estão atualmente em fase experimental, ou seja, passando por melhorias e análise de possiveis problemas pela comunidade, antes de se tornarem parte do core do k6. Para conhecer melhor ambos os modulos acesse: csv e fs.
Conclusão❤️
O K6 é conhecido por sua alta performance e baixo consumo de recursos. No entanto, a performance pode ser comprometida à medida que se adotam soluções inadequadas ou mal modeladas nos scripts de teste.
Como podemos observar, a utilização do SharedArray não terá um impacto significativo em testes com baixa volumetria e poucos dados manipulados. Contudo, em cenários de alta volumetria e grande volume de dados, sua utilização se torna essencial para garantir a eficiência e o desempenho do teste.
Gostou do conteúdo e quer saber mais sobre testes de performance com K6? Confira meu curso na Udemy!