Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Performance Optimization

Performance Optimization

~17 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Before deploying in chapter 48, a systematic look at performance – especially the N+1 query problem, one of the most common causes of slow Symfony/Doctrine applications.

Using the Symfony Profiler

In the dev environment (chapter 4), the debug toolbar at the bottom of the screen shows an overview of EVERY request – clicking it opens the full profiler with details on:

  • Total execution time AND a breakdown by phase (routing, controller, rendering).
  • ALL executed database queries, including each individual query's actual execution time.
  • Memory usage.
  • Loaded services AND their creation order.

Recognizing the N+1 query problem

A CLASSIC performance problem, demonstrated with a project list showing a task count:

{% for project in projects %}
    <li>{{ project.name }} ({{ project.tasks|length }} tasks)</li>
{% endfor %}

Achtung: Looks harmless, but ISN'T: project.tasks triggers its OWN database query per loop iteration (Doctrine's "lazy loading", chapter 22) – with 50 projects, that's ONE query for the project list PLUS 50 MORE queries for their respective tasks, instead of ONE single, efficient query. The profiler from this chapter makes EXACTLY this pattern instantly visible as "51 queries".

The solution: eager loading with JOIN

src/Repository/ProjectRepository.php
/**
 * @return Project[]
 */
public function findAllWithTaskCount(): array
{
    return $this->createQueryBuilder('p')
        ->leftJoin('p.tasks', 't')
        ->addSelect('t')
        ->getQuery()
        ->getResult()
    ;
}

addSelect('t') is the DECISIVE difference from the plain leftJoin in chapter 24: WITHOUT addSelect, Doctrine would only use the JOIN for FILTERING, while still loading the Task objects SEPARATELY. WITH addSelect('t'), projects AND their tasks get loaded in ONE SINGLE query – 51 queries become 1.

Comparing query count in the profiler

Tipp: Load the project list BEFORE and AFTER the change in the profiler and compare the number of database queries – THIS measurable number, not just a subjective "feels faster", is the reliable proof of a real improvement.

Other common performance traps

TrapDescription
Missing database indexesA field FREQUENTLY filtered/sorted by (e.g. status in chapter 24), but WITHOUT an index, leads to slow full-table scans as the data grows.
Loading unused resultsUsing findAll() when only 10 of 10,000 rows are needed – ALWAYS use pagination or targeted WHERE conditions.
Missing cachingNot using chapters 45/46 for repeatedly computed/queried values, even though they change RARELY.

Adding a missing index

#[ORM\Entity(repositoryClass: TaskRepository::class)]
#[ORM\Index(columns: ['status'], name: 'idx_task_status')]
class Task
{
    // ...
}
php bin/console make:migration
php bin/console doctrine:migrations:migrate

EXACTLY the same workflow from chapter 20 – an index is, at the end of the day, a database structure change like any other, versioned via migration.