SF
{ }
Symfony · Profiler · Performance · Debugging · N+1
Symfony Profiler:
Performance-Engpässe in Sekunden finden

Der Symfony Profiler ist mehr als die Debug-Leiste am Seitenrand. Er zeigt präzise, welche Datenbankabfragen eine Seite ausbremsen, wo Event-Listener übermäßig viel Zeit verbrauchen, welche Caches nicht greifen und wie viel Memory ein Request verbraucht – alles auf Knopfdruck, ohne einen einzigen Code-Änderung.

18 Min. Lesezeit Profiler · Web Debug Toolbar · N+1 · Cache · Custom Collector Symfony 7.x · Doctrine ORM · PHP 8.4

1. Was der Symfony Profiler wirklich kann

Der Symfony Profiler ist ein eingebautes Diagnose-Werkzeug, das für jeden HTTP-Request eine vollständige Laufzeitaufzeichnung erstellt. Diese Profile werden als Dateien im var/cache/dev/profiler-Verzeichnis gespeichert und sind über die /_profiler-URL abrufbar. Jedes Profil enthält die Ausführungszeit aller Symfony-Kernel-Phasen, alle Datenbankabfragen mit Dauer und Ausführungsplan, alle Twig-Templates mit Rendering-Zeit, alle Cache-Operationen, Event-Listener-Ausführungszeiten und Memory-Peaks. Dieses Detailniveau ist das, was externe APM-Tools wie Datadog oder New Relic erst nach aufwändiger Instrumentierung liefern — der Symfony Profiler ist von Anfang an dabei.

Der entscheidende Vorteil des Symfony Profilers in der Entwicklungsphase: Er liefert Kontext. Eine langsame Seite mit dem Profiler zu analysieren zeigt nicht nur "200 ms für Datenbankabfragen", sondern welche exakten SQL-Queries ausgeführt wurden, welche Parameter gebunden waren und wie viele identische Queries mehrfach ausgeführt wurden. Ohne den Symfony Profiler müsste ein Entwickler diese Informationen durch manuelles Logging oder externe Tools sammeln. Mit dem Profiler dauert es Sekunden, den Verursacher einer Performance-Regression zu finden. Die Profiler-Daten sind persistiert und können auch nach dem Request noch analysiert werden — für Fehler, die nur unter bestimmten Bedingungen auftreten, ist das unschätzbar.

2. Web Debug Toolbar: die wichtigsten Panels

Die Web Debug Toolbar, die im Development-Modus am unteren Seitenrand eingeblendet wird, ist das erste Diagnosewerkzeug für Performance-Probleme in Symfony. Die Toolbar zeigt auf einen Blick: HTTP-Status, Routing-Match, Controller-Klasse, Anzahl Datenbankabfragen, Twig-Rendering-Zeit, Cache-Hits und -Misses, Memory-Peak und Gesamtausführungszeit. Eine Seite, die ungewöhnlich viele Datenbankabfragen zeigt, ist sofort als N+1-Problem identifizierbar, noch bevor man den vollständigen Symfony Profiler öffnet.

Klickt man auf eine Zahl in der Toolbar, öffnet sich der vollständige Symfony Profiler für den betreffenden Request. Das Doctrine-Panel zeigt alle SQL-Queries mit Ausführungszeit, Parameter und dem stacktrace, der die Query ausgelöst hat. Das Twig-Panel zeigt alle gerenderten Templates mit hierarchischer Struktur und Rendering-Zeiten. Das Security-Panel zeigt den authentifizierten User und alle Voter-Entscheidungen. Das Cache-Panel listet alle Cache-Operationen. Für AJAX-Requests, die keine vollständige HTML-Seite zurückgeben, ist kein Toolbar-Icon sichtbar — aber die Profile werden trotzdem gespeichert und sind über /_profiler/latest abrufbar.


<?php
// config/packages/web_profiler.yaml — Profiler configuration
// (default dev config — shown here for reference)

// web_profiler:
//   toolbar: true
//   intercept_redirects: false

// framework:
//   profiler:
//     only_exceptions: false  # Profile all requests, not just failed ones
//     collect: true
//     dsn: 'file:%kernel.cache_dir%/profiler'  # Where profiles are stored
//     collect_serializer_data: true  # Enable serializer panel

// To access profiles programmatically in tests:
// use Symfony\Bundle\FrameworkBundle\KernelBrowser;
// $client->enableProfiler();
// $client->request('GET', '/products');
// $profile = $client->getProfile();
// $queryCount = $profile->getCollector('db')->getQueryCount();

// Useful Profiler URLs:
// /_profiler             → list of recent profiles
// /_profiler/latest      → most recent profile
// /_profiler/{token}     → specific profile by token
// /_profiler/{token}/db  → directly to database panel

// To profile CLI commands (Symfony Console):
// Profiler data is written to var/cache/dev/profiler/
// Set SYMFONY_PROFILER=1 env var to enable profiling for console commands

3. N+1-Queries mit dem Doctrine-Panel finden

Das N+1-Query-Problem ist die häufigste Performance-Ursache in Symfony-Anwendungen mit Doctrine ORM. Das Symptom: Eine Produktliste mit 50 Produkten erzeugt 51 SQL-Queries — eine für die Produktliste, je eine für die zugehörige Kategorie jedes Produkts. Der Symfony Profiler Doctrine-Panel macht das sofort sichtbar: 51 Queries, von denen 50 nahezu identisch sind und sich nur im ID-Parameter unterscheiden. Der Stacktrace jeder Query zeigt, welcher Code-Pfad die Query ausgelöst hat.

Die Lösung für N+1-Queries mit dem Symfony Profiler als Ausgangspunkt ist das Eager Loading via Doctrine's DQL oder QueryBuilder mit addSelect und leftJoin. Statt 51 Queries werden eine Query mit einem LEFT JOIN ausgeführt — der Symfony Profiler zeigt danach eine einzige Query mit angemessener Ausführungszeit. Für komplexere Szenarien empfiehlt sich das Doctrine EXTRA_LAZY-Loading für Collections, das nur bei tatsächlichem Zugriff lädt, und die Doctrine Second-Level-Cache-Integration, die bei häufig abgerufenen Entities die Datenbankzugriffe weiter reduziert. Der Profiler bleibt das zentrale Werkzeug, um die Wirkung dieser Optimierungen zu messen.

4. Event-Listener-Performance analysieren

Der Symfony Profiler zeigt im Events-Panel alle dispatched Events und die Zeit, die jeder Listener für seine Ausführung benötigt hat. Das ist besonders wertvoll in Symfony-Anwendungen, die viele Event-Listener nutzen — API-Platform-Projekte, Sicherheitsimplementierungen mit mehreren Listenern oder Custom-Geschäftslogik über Events. Ein Listener, der bei jedem Request eine Datenbankabfrage ausführt — etwa um Benutzerberechtigungen zu laden — erscheint im Events-Panel mit seiner Gesamtdauer und kann als Performance-Problem identifiziert werden.

Ein häufiger Befund im Symfony Profiler: Der security.firewall-Listener ist langsam, weil er bei jedem Request einen User aus der Datenbank lädt, obwohl er in Session gespeichert sein sollte. Oder der kernel.request-Listener führt eine unnötige Datenbankabfrage aus, bevor der Controller überhaupt ausgeführt wird. Der Symfony Profiler zeigt die Ausführungsreihenfolge aller Listener und ihren relativen Zeitanteil an der Gesamtanfragezeit. Events mit hoher Gesamtdauer sind Kandidaten für Optimierung: Caching der Listener-Ergebnisse, Lazy-Loading von Dependencies oder Verschieben der Arbeit in asynchrone Symfony-Messenger-Messages.

5. Cache-Misses und Cache-Hits im Profiler

Das Cache-Panel im Symfony Profiler zeigt alle Cache-Operationen: Lesezugriffe (get), Schreiboperationen (set), Deletes und ob der Zugriff ein Hit oder Miss war. Ein hoher Anteil an Cache-Misses bei einem bestimmten Cache-Key ist ein Hinweis darauf, dass entweder die Cache-TTL zu kurz ist, der Cache zu oft geleert wird, oder die Cache-Key-Generierung zu granular ist. Der Symfony Profiler zeigt nicht nur ob ein Cache-Miss aufgetreten ist, sondern auch den genauen Key — das ermöglicht die Analyse, ob Keys konsistent gebildet werden oder ob unbeabsichtigt unterschiedliche Keys für identische Daten erzeugt werden.

In Symfony-Anwendungen mit HTTP-Cache gibt das Performance-Panel im Symfony Profiler Auskunft über Cache-Control-Header und ESI-Fragmente. Wenn eine Seite theoretisch cachebar ist, aber Cache-Control-Header das Caching verhindern, zeigt der Profiler warum — etwa weil ein Event-Listener Response::setPrivate() aufgerufen hat. Das Twig-Panel zeigt zusätzlich, welche Templates kompiliert und welche aus dem Opcode-Cache geladen wurden. Für Symfony-Projekte mit vielen Twig-Templates ist der Kompilierungs-Overhead beim ersten Aufruf nach einem Cache-Clear im Profiler direkt messbar.


<?php

declare(strict_types=1);

namespace App\Repository;

use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * Product repository with N+1-prevention via eager loading.
 */
class ProductRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }

    /**
     * Load products with category in a single query — prevents N+1.
     * Before: 1 query for products + N queries for categories = N+1 problem
     * After:  1 query with LEFT JOIN = Symfony Profiler shows 1 query
     *
     * @return Product[]
     */
    public function findAllWithCategory(): array
    {
        return $this->createQueryBuilder('p')
            // Eager-load the category relation — fetched in the same SQL query
            ->addSelect('c')
            ->leftJoin('p.category', 'c')
            ->orderBy('p.name', 'ASC')
            ->getQuery()
            ->getResult();
    }

    /**
     * Use DQL with INDEX BY to build a lookup map — avoids repeated array searches.
     * Result: associative array keyed by product ID.
     *
     * @return array<int, Product>
     */
    public function findAllIndexedById(): array
    {
        return $this->createQueryBuilder('p', 'p.id')
            ->getQuery()
            ->getResult();
    }
}

6. Timeline: Bottlenecks visuell lokalisieren

Das Timeline-Panel im Symfony Profiler ist das mächtigste Werkzeug für die Analyse komplexer Performance-Probleme. Es zeigt den gesamten Request-Lifecycle als horizontale Zeitleiste: Kernel-Bootstrap, Routing, Controller-Ausführung, Template-Rendering, Response-Finalisierung. Jede Phase ist als farbiger Balken dargestellt, der sowohl die absolute Dauer als auch den relativen Zeitanteil zeigt. Wenn der Controller-Balken 80% der Gesamtzeit einnimmt, weiß man sofort, dass die Performance-Arbeit im Controller oder in den vom Controller aufgerufenen Services beginnen muss.

Die Timeline zeigt auch verschachtelte Span-Daten: Wenn ein Service einen anderen Service aufruft, der wiederum eine Datenbankabfrage auslöst, ist die Hierarchie im Symfony Profiler Timeline sichtbar. Das macht es möglich, in einem Request mit 500 ms Gesamtdauer exakt den 200-ms-Span zu identifizieren, der eine externe HTTP-Anfrage darstellt, die blockierend auf Antwort wartet. Für Custom-Instrumentation — eigene Services, deren Ausführungszeit im Timeline erscheinen soll — verwendet man den Symfony Stopwatch-Service, der direkt mit dem Symfony Profiler integriert ist und Spans zum Timeline hinzufügt.

7. Custom Data Collector schreiben

Der Symfony Profiler ist erweiterbar: Custom Data Collectors fügen eigene Panels zur Toolbar und zum Profiler hinzu. Das ist nützlich für anwendungsspezifische Metriken — die Anzahl verarbeiteter Business-Events, External-API-Aufrufe mit Latenzen, Feature-Flag-Zustände oder Custom-Cache-Statistiken. Ein Custom Collector implementiert das DataCollectorInterface und definiert welche Daten gesammelt werden, wie sie dargestellt werden und ob sie in der Toolbar erscheinen.

Für Teams, die eigene Services in Symfony-Projekten betreiben, ist ein Custom Collector ein wertvolles Debugging-Werkzeug. Wenn ein API-Client-Service fünf externe HTTP-Aufrufe macht, kann ein Custom Collector jeden Aufruf mit URL, Dauer, HTTP-Status und Response-Größe aufzeichnen. Im Symfony Profiler sieht man dann auf einen Blick, welcher externe Dienst den aktuellen Request verlangsamt. Die Alternative — Logging mit manueller Log-Analyse — ist deutlich zeitaufwändiger. Der Custom Collector speichert seine Daten serialisierbar für die persistierten Profile und macht sie auch für nachträgliche Analyse verfügbar.


<?php

declare(strict_types=1);

namespace App\DataCollector;

use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Custom Profiler panel for tracking external API calls.
 * Appears as a dedicated panel in the Symfony Profiler.
 */
final class ExternalApiCollector extends AbstractDataCollector
{
    /** @var array<array{url: string, duration: float, status: int}> */
    private array $calls = [];

    /**
     * Called by the external API client to record each request.
     */
    public function recordCall(string $url, float $durationMs, int $httpStatus): void
    {
        $this->calls[] = [
            'url'      => $url,
            'duration' => $durationMs,
            'status'   => $httpStatus,
        ];
    }

    /**
     * Collect and serialise data when the request finishes.
     */
    public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
    {
        // Data must be serialisable — store in $this->data for Profiler persistence
        $this->data = [
            'calls'         => $this->calls,
            'total_calls'   => count($this->calls),
            'total_duration' => array_sum(array_column($this->calls, 'duration')),
        ];
    }

    /** @return array<array{url: string, duration: float, status: int}> */
    public function getCalls(): array
    {
        return $this->data['calls'] ?? [];
    }

    public function getTotalCalls(): int
    {
        return $this->data['total_calls'] ?? 0;
    }

    public function getTotalDuration(): float
    {
        return $this->data['total_duration'] ?? 0.0;
    }

    // Name used as panel ID and URL segment in the Profiler
    public static function getTemplate(): ?string
    {
        return '@App/data_collector/external_api.html.twig';
    }
}

8. Profiler in PHPUnit-Tests nutzen

Der Symfony Profiler kann in PHPUnit-Tests genutzt werden, um Performance-Regressions automatisch zu erkennen. Wenn ein Feature-Branch plötzlich 50 statt 5 Datenbankabfragen für eine bestimmte Seite produziert, soll das im CI auffallen — nicht erst im Code-Review oder in der Produktion. Der KernelBrowser in Symfony-Tests hat eine enableProfiler()-Methode, die nach dem Request Zugriff auf das Profil ermöglicht. Mit $client->getProfile()->getCollector('db')->getQueryCount() testet man, dass die Anzahl der SQL-Queries unter einem bestimmten Threshold bleibt.

Performance-Tests mit dem Symfony Profiler sind kein Ersatz für Lastests, aber sie fangen die häufigsten Regressions auf Unit-Test-Ebene ab. Ein Test, der sicherstellt, dass die Produktliste mit 100 Produkten maximal 3 SQL-Queries ausführt, verhindert effektiv, dass ein N+1-Problem durch einen unaufmerksamen Commit eingeführt wird. Der Symfony Profiler macht diese Art von Assertions möglich, ohne externe Performance-Monitoring-Tools zu benötigen. In Kombination mit dem Custom-Collector aus dem vorherigen Abschnitt können auch anwendungsspezifische Metriken getestet werden.

9. Symfony Profiler vs. externe APM-Tools

Der Symfony Profiler ist für die Entwicklungsphase das richtige Werkzeug. Für die Produktion braucht man ein ergänzendes Monitoring-System. Die Frage ist, wo die Grenze zwischen Profiler und APM-Tool liegt.

Kriterium Symfony Profiler Datadog / New Relic Blackfire.io
Einsatzbereich Entwicklung, Test Produktion Dev + Produktion
Overhead Kein Produktions-Overhead Messbar (1–5%) Niedrig im Probe-Modus
Symfony-Integration Native, zero config Agent + PHP-Extension Symfony-Bundle verfügbar
SQL-Analyse Vollständig mit Stack-Trace Aggregiert über Zeit Profil-basiert
Kosten Kostenlos (Open Source) Kostenpflichtig Kostenpflichtig ab Produktion

Die optimale Strategie für Symfony-Projekte kombiniert beide Ansätze: Der Symfony Profiler für die Entwicklungsarbeit und zum Schreiben von Performance-Tests im CI, ein APM-Tool für die Produktionsüberwachung und Alerting bei Performance-Degradationen unter Last. Blackfire.io ist dabei die natürlichste Ergänzung zum Symfony Profiler, weil es von denselben Entwicklern gebaut wird und nahtlos mit dem Symfony-Ökosystem integriert. Für kleinere Projekte ist der Symfony Profiler in Kombination mit strukturiertem Logging und Grafana oft ausreichend — APM-Tools sind eine Investition, die sich erst ab einem gewissen Traffic-Niveau auszahlt.

Mironsoft

Symfony Performance-Analyse, Profiler-Auswertung und Optimierung

Symfony-Performance-Probleme systematisch lösen?

Wir analysieren Symfony-Anwendungen mit dem Profiler, identifizieren N+1-Queries, Cache-Misses und Event-Listener-Bottlenecks und implementieren die Optimierungen für messbaren Performance-Gewinn in eurem Projekt.

Profiler-Analyse

Systematische Auswertung von Symfony-Profiler-Daten und Identifikation von Performance-Bottlenecks

Query-Optimierung

N+1-Queries mit Doctrine Eager Loading und DQL-Optimierungen beheben

Performance-Tests

PHPUnit-Tests mit Profiler-Assertions schreiben, die Performance-Regressions in der CI verhindern

10. Zusammenfassung

Der Symfony Profiler ist das effektivste Diagnosewerkzeug für Performance-Arbeit in Symfony-Anwendungen. Das Doctrine-Panel deckt N+1-Queries mit Stack-Traces auf und zeigt genau, welcher Code-Pfad wie viele SQL-Abfragen auslöst. Das Events-Panel macht langsame Listener sichtbar. Das Cache-Panel zeigt Hit/Miss-Verhältnisse für alle Cache-Pools. Die Timeline visualisiert den gesamten Request-Lifecycle und macht Bottlenecks auf Anhieb sichtbar. Custom Data Collectors erweitern den Profiler um anwendungsspezifische Metriken.

Der größte Return on Investment liegt darin, den Symfony Profiler nicht nur reaktiv zu nutzen — wenn eine Seite langsam ist — sondern proaktiv als Teil des Entwicklungsprozesses. PHPUnit-Tests, die Query-Counts über den Profiler-API prüfen, verhindern Performance-Regressions in der CI, bevor sie die Produktion erreichen. Kombiniert mit dem Stopwatch-Service für Custom-Timing und einem APM-Tool für die Produktionsüberwachung entsteht eine vollständige Performance-Observability-Strategie, die in Symfony-Projekten ohne externe Werkzeuge machbar ist.

Symfony Profiler — Das Wichtigste auf einen Blick

N+1-Queries finden

Doctrine-Panel zeigt alle SQL-Queries mit Dauer und Stack-Trace. Mehrfach identische Queries mit unterschiedlichen IDs = N+1-Problem. Fix: Eager Loading via leftJoin + addSelect.

Timeline-Analyse

Timeline zeigt Request-Lifecycle als Zeitleiste. Großer Controller-Balken = Arbeit im Service. Stopwatch-Service für Custom-Spans im Timeline nutzen.

Custom Collector

DataCollectorInterface implementieren für eigene Panels. Anwendungsspezifische Metriken wie External-API-Calls im Profiler sichtbar machen.

Performance-Tests

$client->enableProfiler() in PHPUnit. Query-Count-Assertions verhindern N+1-Regressions in der CI. getCollector('db')->getQueryCount().

11. FAQ: Symfony Profiler und Performance

1Was ist der Symfony Profiler?
Eingebautes Diagnose-Werkzeug: SQL-Queries, Event-Zeiten, Cache-Ops, Memory-Peak. Über /_profiler zugänglich. Profile persistent gespeichert.
2Symfony Profiler aktivieren?
Im Dev-Modus standardmäßig aktiv via toolbar: true. Für Tests: $client->enableProfiler(). Nie im Prod-Modus ohne Zugriffskontrolle.
3N+1-Queries finden?
Doctrine-Panel: viele identische Queries mit unterschiedlichen IDs = N+1. Fix: leftJoin + addSelect im QueryBuilder für Eager Loading.
4Profiler in PHPUnit nutzen?
$client->enableProfiler(). Nach Request: getProfile()->getCollector('db')->getQueryCount(). N+1-Regressions in CI automatisch erkennen.
5Custom Data Collector schreiben?
DataCollectorInterface implementieren. Daten in collect() in $this->data serialisierbar speichern. Twig-Template für Panel-Layout. Service mit data_collector-Tag registrieren.
6Timeline-Panel nutzen?
Zeigt Request-Lifecycle als Zeitleiste. Breite Balken = viel Zeit. Stopwatch-Service für Custom-Timing nutzen — erscheint automatisch in der Timeline.
7Performance-Overhead des Profilers?
Im Dev-Modus ja, im Prod-Modus standardmäßig deaktiviert. only_exceptions-Modus für Staging: nur fehlerhafte Requests werden profiliert.
8Profiler für API-Requests?
Ja. API-Requests werden genauso profiliert. Profil unter /_profiler/latest oder via X-Debug-Token-Response-Header. Ideal für API-Platform-Endpunkte.
9Cache-Misses finden?
Cache-Panel listet alle Operationen mit Hit/Miss-Status. Hohe Miss-Rate = zu kurze TTL, zu häufiges Clearing oder inkonsistente Key-Generierung.
10Wann zusätzlich APM-Tool nutzen?
Für Produktion: Datadog, New Relic oder Blackfire.io für aggregierte Daten über Zeit, Alerting bei Degradation und Multi-Service-Tracing.