É importante que o ambiente de desenvolvimento tenha proporções mais próximas da realidade para ser possível testar o desempenho do software adequadamente. O uso de recursos por um programa aumenta conforme aumenta o tamanho da entrada de um programa, esse aumento do uso de recursos nem sempre será algo linear (ou seja, não é por levar 1ms para 1 entrada que vai levar 100ms para 100 entradas) e quando estamos tratando de entradas pequenas, os problemas de desempenho não são perceptíveis, por isso é essencial buscar um volume de entrada considerável para que o desempenho do ambiente de teste seja mais próximo de um caso real.
Um programa pode receber dados por diversos meios, um meio comum é pelo sistema gerenciador de banco de dados (SGBD ou DBMS em inglês). Para alimentar o banco de dados no ambiente de desenvolvimento, existem os chamados database seeder (algo como semeador de banco de dados em tradução livre), o objetivo do mesmo é “plantar” dados no banco de dados.
Talvez você esteja pensando que não existe nenhum mistério nisso, basta criar várias entradas repetidas no banco de dados, porém, caso faça isso, a distribuição dos seus dados não representam a realidade. Supondo que o seu programa tenha alguma função que precise listar as pessoas com menos de 30 anos, caso o SGBD/DBMS possua mecanismos para otimizar a busca de acordo com estatísticas da tabela e a idade mediana no banco de dados seja de 30 anos, a busca poderia ser apenas na metade do banco, podendo ser algo distante da realidade.
Projeto de exemplo
Para usar de exemplo, criei um projeto simples (porém minimamente complexo) de histórico escolar disponível aqui. O diagrama do banco de dados é o seguinte:

Traduzindo o diagrama: um registro no histórico escolar possui um aluno e uma turma associados; e nota final e verificação suplementar (prova final/recuperação). Um aluno possui apenas nome, uma turma possui uma disciplina associada e o semestre que foi lecionada e uma disciplina possui apenas nome e carga horária. Esse banco de dados não chega perto de como seria num caso real, porém é minimamente complexo para abordar diversos pontos importantes de Seeder (também é interessante para outros experimentos que virão em posts futuros!).
Factory
O Eloquent possui Model Factory, em tradução livre é fábrica de modelos, esse é o responsável por criar os objetos que serão inseridos no banco de dados pelo Seeder (também é utilizado em outros contextos como teste unitário). Um factory pode ser criado utilizando o Artisan com o comando php artisan make:factory NomeDaFactory
, esse comando irá criar uma factory no diretório database/factories
, esse factory terá uma função chamada definition em que você irá definir as regras padrões para geração daquele objeto, o exemplo abaixo é do factory de aluno, o mesmo está gerando matrícula aleatória que é: valor único no banco de dados com valor numérico utilizando a máscara #########
, em que # representa um número, é possível utilizar qualquer formato para máscara (CPF, por exemplo, seria ###.###.###-##
); e, também, gerando nome aleatório, tem diversos tipos de dados possíveis de gerar com o faker, consulte a documentação para verificar qual utilizar.
class AlunoFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'matricula' => $this->faker->unique()->numerify('#########'),
'nome' => $this->faker->name(),
];
}
}
Seeder
O seeder é o programa responsável por “semear” os objetos fabricados pelas factories, o seeder padrão de um projeto Laravel está localizado em database/seeders/DatabaseSeeder.php
, esse programa possui apenas a função run que, como o próprio nome diz, é a função executada quando executa o seeder.
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call([
DisciplinaSeeder::class,
AlunoSeeder::class,
]);
}
}
É possível escrever todo o código de seeder nessa única classe, porém desaconselho fortemente, quando você estiver “semeando” o seu banco de dados, o Artisan exibe a duração de cada seeder individualmente, ou seja, você consegue saber o progresso e, também, consegue identificar facilmente em qual parte está o gargalo (bottleneck) dos seus seeders (digo isso por experiência própria, meus seeders iniciais estavam demorando muito para serem executados e ter visão mais estratificada do desempenho do seeder me possibilitou identificar onde precisava de melhoria e, também, identificar o ganho que tive após otimização). Sem mais delongas, a função run está basicamente dizendo “executa primeiro o DisciplinaSeeder e, após, executa o AlunoSeeder”.
class DisciplinaSeeder extends Seeder
{
public function run()
{
Disciplina::factory(1000)
->has(Turma::factory(100))
->create();
}
}
O DisciplinaSeeder está fabricando 1000 disciplinas, em que cada uma possui 100 turmas. Uma observação importante: a função has depende que os seus Models estejam funcionando corretamente para que o programa possa identificar como são as relações no seu banco de dados.
class AlunoSeeder extends Seeder
{
public function run()
{
$turmas = Turma::all();
Aluno::factory(10000)
->create()
->each(function ($aluno) use ($turmas) {
$turmasToAdd = $turmas->random(80);
for ($i = 0; $i < 80; $i++) {
HistoricoEscolar::factory()->create([
'turma_id' => $turmasToAdd[$i],
'aluno_matricula' => $aluno->matricula
]);
}
});
}
}
Por último (e mais complexo), está o AlunoSeeder, esse seeder não é tão intuitivo de entender quanto o outro, pois como o meu objetivo era criar um banco de dados relativamente grande, uma pequena perda de desempenho nesse seeder representa um tempo considerável para a criação do banco de dados.
O AlunoSeeder cria 10.000 alunos, em que cada um possui 80 itens no histórico escolar (de turmas diferentes). Inicialmente foi criado de forma semelhante ao DisciplinaSeeder, porém uma lesma estava mais rápida que o seeder,
O primeiro problema foi a escolha das 80 turmas aleatórias, originalmente eram lidas 80 turmas aleatórias no banco de dados para cada aluno, ou seja, temos um problema N + 1 queries. Para resolver isso, inicialmente eu carrego todas as turmas na RAM (Turma::all()), dessa forma eu realizo apenas 1 query para pegar todas as turmas e não N queries para recuperar as turmas. Como tudo na computação, existe uma troca (tradeoff) nisso, o meu programa está consumindo mais memória RAM do que antes; para o meu caso, foi uma troca aceitável por bons minutos a menos executando o seeder.
O AlunoSeeder possui outra característica diferente, está sendo utilizada a função each
com uma função passada como parâmetro, essa função é executada após a criação do objeto e recebe o mesmo como parâmetro. Foi utilizado também o use
do php que serve para dizer “use a(s) variável(is) externas na execução da função”, nesse caso, está passando a variável $turmas
para o escopo da função anônima.
Por fim, vamos executar os seeders! Para isso, execute o comando php artisan migrate:fresh --seed
esse comando irá zerar o seu banco de dados, executar as migrations existentes e, por último, executará os seeders.

Como é possível notar na saída do programa, primeiro todas as tabelas são derrubadas (por conta do fresh), após as migrações do banco de dados pendentes (nesse caso, todas) são executadas e, por último, os seeders são executados. Como citado anteriormente, uma das vantagens de dividir em diversos seeders é saber exatamente onde está demorando, no caso acima, o DisciplinaSeeder levou aproximadamente 9 minutos (537834,39ms) e o AlunoSeeder levou aproximadamente 1 hora e 7 minutos (4025166,09ms); ou seja, como nos meus testes eu utilizei banco de dados relativamente grandes, é muito importante se preocupar com o desempenho do seeder já que pequenos detalhes, como o caso de N+1 queries de turmas, podem significar um seeder levando muitas horas para ser finalizado.
Ficou alguma dúvida, sugestão ou crítica? Sinta-se a vontade para deixar um comentário abaixo!