Marcio Policarpo
Posted on October 4, 2022
Foi liberado em 28/09 o release 9.32 do Laravel.
A grande novidade deste release é a inclusão do helper Benchmark.
Com ele é possível medir o tempo de execução de qualquer processo dentro da aplicação de forma isolada, bastando para isso adicionar a referência use Illuminate\Support\Benchmark;
na classe onde a análise será feita.
A classe Benchmark possui apenas dois métodos estáticos a saber:
public static function measure(Closure|array $benchmarkables, int $iterations = 1): array|float
public static function dd(Closure|array $benchmarkables, int $iterations = 1): void
Ambos recebem dois parâmetros:
$benchmarkables
: array de funções para análise do tempo de processamento$iterations
: inteiro que indica a quantidade de iterações que serão aplicadas às funções do parâmetro anterior. Este parâmetro é opcional.
O método measure
retorna um array com o tempo de cada função executada. Já o método dd
, como o próprio nome diz, executa um dd
(dump and die) comando bem comum em PHP geralmente utilizado para mostrar o valor de uma variável na camada de apresentação.
Testando
Para efeitos didáticos, vamos montar um exemplo simples onde consultaremos um cliente através do seu ID, utilizando 5 abordagens distintas.
⚠️ Conhecimento prévio sobre aplicações Laravel (criação, configuração e execução) é requerido.
As consultas serão feitas em uma tabela que possui 1037 registros, através de uma contoller chamada CustomerController
. Apesar do pouco volume de informações, ressalto que a máquina onde os testes serão executados não possui uma performance elevada, equilibrando os resultados.
Migração
O arquivo de migração abaixo dará uma noção da estrutura da tabela de clientes, ajudando a compreender melhor o ambiente utilizado nos testes.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('last_name');
$table->string('first_name');
$table->string('email')->nullable();
$table->string('phone', 30)->nullable();
$table->string('street');
$table->string('city');
$table->string('building_number', 30);
$table->string('country');
$table->string('post_code');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('customers');
}
};
Rota
Vamos editar o arquivo de rotas de api (\routes\api.php) criando uma nova rota e adicionando a referência para a controller CustomerController
que criaremos em breve:
Route::get('/{id}/show', [CustomerController::class, 'show']);
E a referência para a controller:
use App\Http\Controllers\CustomerController;
Controller
Nossa controller terá apenas um método responsável por consultar o cliente de acordo com o ID informado.
Abaixo como a classe deve se parecer:
<?php
namespace App\Http\Controllers;
use App\Models\Customer;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\DB;
class CustomerController extends Controller
{
public function show(int $id)
{
$customer = Customer::find($id);
$result = Benchmark::measure(
[
'Scenario 1' => fn() => Customer::find($id),
'Scenario 2' => fn() => Customer::where('id', ($id))->get(),
'Scenario 3' => fn() => DB::table('customers')->where('id', $id)->first(),
'Scenario 4' => fn() => DB::table('customers')->where('id', $id)->get(),
'Scenario 5' => fn() => DB::select('select * from customers where id = ?', [$id])
], 10);
if ($customer) {
return response()->json([
'time' => $result,
'data' => $customer]);
} else {
return response()->json(['message' => 'Customer not found'], 404);
}
}
}
Perceba que à frente de cada funções adicionei um álias: 'Scenario 1', 'Scenario 2', etc.
Esse álias, apesar de opcional, ajudará bastante a identificar qual tempo refere-se a qual função analisada.
Testando
Assim que a aplicação estiver executando vamos fazer uma chamada à rota que configuramos anteriormente e informar um código para pesquisar o cliente.
Explicando
Cenário 1
A consulta realizada neste cenário é a mais básica, onde utilizamos o próprio modelo para buscar o cliente através da chave primária com o método find
.
Apesar do tempo não ser um dos melhores, há que se frisar que existe um custo de processamento para converter o resultado no modelo Customer.
Cenário 2
Neste o tempo melhorou um pouquinho em relação ao cenário anterior. A diferença é que passamos a coluna id
diretamente para consultar.
Acredito que se a coluna id
não fosse indexada o resultado seria significativamente pior.
E neste cenário ainda temos o custo de conversão do resultado da consulta no modelo Customer
.
Cenário 3
A partir deste cenário ficamos mais próximos do banco de dados realizando consultas consideradas mais 'brutas'.
Por conta dessa abordagem note que os tempos de retorno são melhores justamente por eliminarmos o processamento feito na camada de abstração do Eloquent ORM.
Cenário 4
A única diferença em relação ao cenário anterior é que estamos utilizando o método get()
ao invés do método first()
.
Ocorre que ao executarmos o método first()
há um processamento adicional para retornar somente o primeiro registro da consulta o que não acontece com o método get()
.
Cenário 5
Na maioria dos testes este se mostrou o mais rápido de todos porque passamos uma consulta 'bruta' ao banco de dados filtrando o cliente pelo ID informado no parâmetro.
Apesar do tempo consideravelmente menor é importante lembrar que este tipo de consulta não se aplica a todas as situações possíveis.
Um bom exemplo onde o resultado poderia se apresentar mais produtivo, seriam as funções de agregação de dados (sum
, max
, count
, etc) onde é menos custoso já trazer as consultas agrupadas ao invés de fazer um processamento adicional na aplicação.
Todos os testes foram executados 10 vezes, conforme informamos no parâmetro opcional $iterations
do método estático measure()
. Então, o resultado mostrado refere-se ao tempo médio das 10 tentativas realizadas.
Ao suprimir este parâmetro cada uma das funções será executada apenas uma vez.
Adições
Este release também trouxe outras funcionalidades:
Caminho do arquivo na função 'dd'
A partir deste release, sempre que utilizarmos a função dd
(dump and die) o caminho completo do arquivo também fará parte do resultado
(mais detalhes aqui)
Encriptar e decriptar arquivos .env
Foram adicionados dois novos comandos ao script artisan
, com a finalidade de gerar um arquivo encriptado a partir do arquivo '.env', bem como decriptá-lo.
Para encriptar: php artisan env:encrypt
Para decriptar: php artisan env:decrypt
Lembrando que os comandos devem ser executados utilizando o terminal de sua preferência a partir do diretório raiz da aplicação.
(mais detalhes aqui)
A lista completa das novas funcionalidades deste release, bem como correções e melhoramentos, pode ser encontrada aqui (em inglês).
Até breve!
😎
Posted on October 4, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.