# Shopware Performance-Probleme – bitte um Unterstützung

**URL:** <https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217>\
**Category:** Shopware 6 (German)\
**Created:** [15. Mai 2025 um 16:20 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217 "2025-05-15T16:20:18Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![nasad](https://avatars.discourse-cdn.com/v4/letter/n/8baadc/32.png) [@nasad](https://forum.shopware.com/u/nasad)\
**Post date:** [15. Mai 2025 um 16:20 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/1 "2025-05-15T16:20:18Z")

</div>

Hallo zusammen,

ich habe zwei Shopware-Installationen mit unterschiedlichem Umfang:

### Shop 1:

- 100 Kategorien
- 40.000 einfache Artikel
- 3 Eigenschaften (Properties)
- 5 Sprachen
- 3 Währungen

Die Ladezeiten sind gut, teilweise sogar sehr gut. Das ist auch nicht überraschend – 40.000 Artikel sind für ein leistungsfähiges Shopsystem eigentlich keine Herausforderung, besonders nicht bei der Server-Hardware, die ich verwende.

### Shop 2:

- 300 Kategorien
- 1.000.000 einfache Artikel
- 3 Eigenschaften
- Zunächst ebenfalls 5 Sprachen und 3 Währungen – mittlerweile reduziert auf **nur noch 1 Sprache**
- Gleicher Server, gleiche Konfiguration

Hier sind die Ladezeiten katastrophal. Ich bitte dringend um Hilfe – was habe ich falsch gemacht?

### Hintergrund:

Ich arbeite festangestellt in einer Firma, die aktuell einen Magento-1-Shop betreibt. Ich bin kein Freelancer oder Teil einer Agentur.  
Seit über 13 Jahren entwickle ich fast ausschließlich mit Magento, sowohl in Agenturen als auch in größeren Unternehmen gearbeitet. Ich bin auch Magento-zertifiziert.

Bitte keine Fragen nach URLs oder dem Firmennamen. Ich bin lediglich ein Entwickler, der die Aufgabe erhalten hat, zu prüfen, ob Shopware eine Alternative für uns darstellt.

Ich möchte hier keine Werbung für Magento machen oder die Systeme direkt vergleichen, aber als Referenz:  
Unser Magento 2 Test Shop kommt mit 1 Million Artikeln, 3 filterbaren Attributen und 15 Sprachen/StoreViews auf Ladezeiten unter 1 Sekunde.

### Fragen zum Shopware HTTP-Cache:

1. Wie genau funktioniert der HTTP-Cache in Shopware?  
Ist er vergleichbar mit einem Full Page Cache (FPC)?
2. Was genau wird gespeichert?  
Wird die komplette HTML-Seite gecacht?
3. Werden dynamische Blöcke (wie z. B. der Warenkorb) als Platzhalter eingefügt, ähnlich wie in Magento mit ESI-Tags oder ähnlichem?
4. Verstehe ich das richtig, dass der HTTP-Cache automatisch deaktiviert wird, sobald sich ein Benutzer einloggt oder einen Artikel in den Warenkorb legt?  
Gilt das dann auch für die Startseite, Kategorieseiten und Produktseiten?
5. Wo werden die Cache-Daten abgelegt?  
Ich vermute in `var/cache/prod_xxx/pools/app` – korrekt?  
Ich habe auch mit Redis getestet.
6. Hat jemand Erfahrungen mit einem Shop dieser Größenordnung (1 Mio Artikel) in Shopware? Gibt es dafür Best Practices oder Hinweise auf technische Grenzen?

### Server-Setup:

- Dedizierter Root-Server (ausschließlich für dieses Projekt)
- 14 CPU (20 Threads)
- 64 GB RAM
- 500 GB SSD
- Plesk
- NGINX
- PHP 8.3
- OPCache aktiviert
- `memory_limit = 20G`
- MariaDB 11.4.6

MariaDB 11.4.6 und Einstellungenfü DB:

[mysqld]

# GRUNDEINSTELLUNGEN

###################

# Zeichensatz für internationale Shops

character-set-server = utf8mb4  
collation-server = utf8mb4\_unicode\_ci

# Grundlegende Verbindungseinstellungen

max\_connections = 500  
max\_allowed\_packet = 64M  
connect\_timeout = 30  
wait\_timeout = 600  
interactive\_timeout = 600  
net\_read\_timeout = 120  
net\_write\_timeout = 120  
table\_open\_cache = 8192  
table\_definition\_cache = 4096  
open\_files\_limit = 65535

# SPEICHER-OPTIMIERUNG

###################  
innodb\_buffer\_pool\_size = 30G

# Buffer-Instanzen (1 pro CPU-Kern, ideal für 14 CPU-Kerne)

innodb\_buffer\_pool\_instances = 14

# Query-Cache (für Shopware/Magento mit vielen Produkten)

query\_cache\_type = 1  
query\_cache\_size = 256M  
query\_cache\_limit = 8M

# Thread-Cache

thread\_cache\_size = 32

# Temporäre Tabellen (erhöht für 64GB RAM)

tmp\_table\_size = 512M  
max\_heap\_table\_size = 512M

# Sortierung und Joins

sort\_buffer\_size = 8M  
join\_buffer\_size = 4M  
read\_buffer\_size = 3M  
read\_rnd\_buffer\_size = 4M

# STORAGE-ENGINE-EINSTELLUNGEN (InnoDB)

##################

# Transaktionseinstellungen

innodb\_flush\_method = O\_DIRECT  
innodb\_flush\_log\_at\_trx\_commit = 1 # Für ACID-Compliance

# Alternativ: innodb\_flush\_log\_at\_trx\_commit = 2 # Für höhere Performance

# Redo-Log

innodb\_log\_file\_size = 1G  
innodb\_log\_buffer\_size = 64M

# Concurrent Threads (angepasst für 14 CPU-Kerne)

innodb\_read\_io\_threads = 14  
innodb\_write\_io\_threads = 14  
innodb\_thread\_concurrency = 28

# Dateikonfiguration

innodb\_file\_per\_table = ON

# Performance-Schemaoptionen

performance\_schema = ON  
performance\_schema\_max\_table\_instances = 400  
performance\_schema\_max\_table\_handles = 256  
performance\_schema\_max\_mutex\_instances = 15000

# TRANSAKTIONEN UND LOCKS

###################

# Deadlock-Erkennung

innodb\_deadlock\_detect = ON  
innodb\_lock\_wait\_timeout = 120

# Isolation Level (für E-Commerce gut geeignet)

transaction\_isolation = READ-COMMITTED

# SPEZIELLE MAGENTO/SHOPWARE-OPTIMIERUNGEN

###################

# Magento kann viele temporäre Tabellen erstellen

max\_prepared\_stmt\_count = 32768  
binlog\_format = ROW  
sync\_binlog = 1

# Für lange Produktnamen etc.

innodb\_strict\_mode = OFF

# Für umfangreiche Indizierungen

sort\_buffer\_size = 16M  
myisam\_sort\_buffer\_size = 64M

# Für große Anzahl komplexer Abfragen

max\_length\_for\_sort\_data = 8192  
optimizer\_switch = ‚index\_merge\_intersection=on‘

# Verbesserte I/O-Handhabung

innodb\_io\_capacity = 2000  
innodb\_io\_capacity\_max = 4000  
innodb\_adaptive\_flushing = ON  
innodb\_lru\_scan\_depth = 4096

OpenSearch mit -Xms8g/-Xmx8g

Die Produkte habe durch eigenen Plugin erzeugt.

Die Einstellungen in .env.local

OPENSEARCH\_URL=[http://localhost:9200](http://localhost:9200)

SHOPWARE\_ES\_ENABLED=1  
SHOPWARE\_ES\_INDEXING\_ENABLED=1  
SHOPWARE\_ES\_INDEX\_PREFIX=sw6  
SHOPWARE\_ES\_THROW\_EXCEPTION=1  
SHOPWARE\_HTTP\_CACHE\_ENABLED=1  
SHOPWARE\_HTTP\_DEFAULT\_TTL=7200

Ich freue mich über jede fachliche Rückmeldung oder Hinweise, was ich optimieren oder beachten sollte.

Vielen Dank im Voraus!

---

<div class="post-metadata">

**Author:** ![nasad](https://avatars.discourse-cdn.com/v4/letter/n/8baadc/32.png) [@nasad](https://forum.shopware.com/u/nasad)\
**Post date:** [15. Mai 2025 um 17:06 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/2 "2025-05-15T17:06:29Z")

</div>

Zur Generierung von 1 Million Artikeln habe ich ein eigenes Plugin geschrieben.  
Vielleicht mache ich dabei etwas falsch – eventuell werden unnötige oder inkonsistente Daten erzeugt?

/src/custom/plugins/NasadImportProducts/Command/ImportProductsCommand.php

```auto
<?php declare(strict_types=1);

namespace NasadImportProducts\Command;

use NasadImportProducts\Core\Content\Product\ProductImportService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\InputOption;

class ImportProductsCommand extends Command
{
    protected static string $defaultName = 'nasad:import:products';

    public function __construct(
        private readonly ProductImportService $productImportService
    ) {
        parent::__construct(self::$defaultName);
    }

    protected function configure(): void
    {
        $this
            ->setName(self::$defaultName)
            ->setDescription('Import products with dimensions')
            ->addOption(
                'count',
                'c',
                InputOption::VALUE_OPTIONAL,
                'Number of products to import',
                100
            )
            ->addOption(
                'offset',
                'o',
                InputOption::VALUE_OPTIONAL,
                'Starting offset for product numbers',
                0
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $count = (int)$input->getOption('count');
        $offset = (int)$input->getOption('offset');

        try {
            $io->section(sprintf('Starting products import (Count: %d, Offset: %d)', $count, $offset));

            // Importiere Produkte
            $io->text('Importing products...');
            $this->productImportService->importProducts($count, $offset);

            $io->success(sprintf('%d products imported successfully', $count));
            return Command::SUCCESS;
        } catch (\Exception $e) {
            $io->error('Error during import: ' . $e->getMessage());
            return Command::FAILURE;
        }
    }
}
```

/src/custom/plugins/NasadImportProducts/Command/ImportCategoriesCommand.php

```auto
<?php declare(strict_types=1);

namespace NasadImportProducts\Command;

use NasadImportProducts\Core\Content\Category\CategoryImportService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class ImportCategoriesCommand extends Command
{
    protected static string $defaultName = 'nasad:import:categories';

    public function __construct(
        private readonly CategoryImportService $categoryImportService
    ) {
        parent::__construct(self::$defaultName);
    }

    protected function configure(): void
    {
        $this
            ->setName(self::$defaultName)
            ->setDescription('Import categories')
            ->addOption(
                'count',
                'c',
                InputOption::VALUE_OPTIONAL,
                'Number of categories to import',
                100
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $count = (int) $input->getOption('count');

        try {
            $io->section(sprintf('Starting categories import (Count: %d)', $count));
            $categoryIds = $this->categoryImportService->importCategories($count);
            $io->success(sprintf('%d categories imported successfully', $count));
            return Command::SUCCESS;
        } catch (\Exception $e) {
            $io->error('Error during import: ' . $e->getMessage());
            return Command::FAILURE;
        }
    }
}
```

/src/custom/plugins/NasadImportProducts/Command/ImportAttributesCommand.php

```auto
<?php declare(strict_types=1);

namespace NasadImportProducts\Command;

use NasadImportProducts\Core\Service\AttributeService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class ImportAttributesCommand extends Command
{
    protected static string $defaultName = 'nasad:import:attributes';

    public function __construct(
        private readonly AttributeService $attributeService
    ) {
        parent::__construct(self::$defaultName);
    }

    protected function configure(): void
    {
        $this
            ->setName(self::$defaultName)
            ->setDescription('Import attributes');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        try {
            $io->section('Starting attributes import');
            $this->attributeService->createAttributes();
            $io->success('Attributes created successfully');
            return Command::SUCCESS;
        } catch (\Exception $e) {
            $io->error('Error during import: ' . $e->getMessage());
            return Command::FAILURE;
        }
    }
}
```

/src/custom/plugins/NasadImportProducts/Core/Content/Category/CategoryImportService.php

```auto
<?php declare(strict_types=1);

namespace NasadImportProducts\Core\Content\Category;

use NasadImportProducts\Core\Service\DummyDataGenerator;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;

class CategoryImportService
{
    private DummyDataGenerator $dataGenerator;

    public function __construct(
        private readonly EntityRepository $categoryRepository,
        private readonly EntityRepository $salesChannelRepository
    ) {
        $this->dataGenerator = new DummyDataGenerator();
    }

    public function importCategories(int $count = 100): array
    {
        $context = Context::createDefaultContext();
        $categoryIds = [];

        // Finde den Standard-Storefront-Sales-Channel
        $criteria = new Criteria();

        $salesChannel = $this->salesChannelRepository->search($criteria, $context)->first();

        if (!$salesChannel) {
            throw new \RuntimeException('Default sales channel not found');
        }

        // Hole die Navigations-Kategorie
        $navigationCategoryId = $salesChannel->getNavigationCategoryId();
        $criteria = new Criteria([$navigationCategoryId]);
        $navigationCategory = $this->categoryRepository->search($criteria, $context)->first();

        if (!$navigationCategory) {
            throw new \RuntimeException('Navigation category not found');
        }

        // Begrenze die Anzahl auf mindestens 1 und maximal 100
        $count = max(1, min(100, $count));

        for ($i = 1; $i <= $count; $i++) {
            $categoryId = Uuid::randomHex();
            $categoryIds[] = $categoryId;

            $category = [
                'id' => $categoryId,
                'parentId' => $navigationCategory->getId(),
                'translations' => $this->getCategoryTranslations($i),
                'displayNestedProducts' => true,
                'active' => true,
                'visible' => true
            ];

            $this->categoryRepository->create([$category], $context);
        }

        return $categoryIds;
    }

    private function getCategoryTranslations(int $number): array
    {
        $translations = [];
        $supportedLocales = ['de-DE', 'en-GB', 'fr-FR', 'nl-NL', 'it-IT', 'es-ES'];

        foreach ($supportedLocales as $locale) {
            $translations[$locale] = [
                'name' => $this->dataGenerator->generateCategoryName($number, $locale),
                'description' => $this->dataGenerator->generateCategoryDescription($locale),
                'metaTitle' => $this->dataGenerator->generateMetaTitle(
                    $this->dataGenerator->generateCategoryName($number, $locale),
                    $locale
                ),
                'metaDescription' => $this->dataGenerator->generateMetaDescription($locale)
            ];
        }

        return $translations;
    }

    public function deleteAllCategories(): void
    {
        $context = Context::createDefaultContext();
        $categories = $this->categoryRepository->search(new Criteria(), $context);

        if ($categories->getTotal() === 0) {
            return;
        }

        $ids = array_map(function ($category) {
            return ['id' => $category->getId()];
        }, $categories->getElements());

        $this->categoryRepository->delete(array_values($ids), $context);
    }
}
```

/src/custom/plugins/NasadImportProducts/Core/Content/Product/ProductImportService.php

```auto
<?php declare(strict_types=1);

namespace NasadsImportProducts\Core\Content\Product;

use NasadImportProducts\Core\Service\DummyDataGenerator;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Uuid\Uuid;
use NasadImportProducts\Core\Service\AttributeService;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Indexing\EntityIndexerRegistry;

class ProductImportService
{
    private DummyDataGenerator $dataGenerator;
    private array $attributeIds;
    private array $categoryIds = [];
    private array $propertyOptionIds = [];

    public function __construct(
        private readonly EntityRepository $productRepository,
        private readonly EntityRepository $categoryRepository,
        private readonly AttributeService $attributeService,
        private readonly EntityRepository $propertyGroupOptionRepository
    ) {
        $this->dataGenerator = new DummyDataGenerator();
    }

    /**
     * Import products with random categories and attributes
     *
     * @param int $count Number of products to import (default: 100, max: 1000000)
     * @return void
     * @throws \RuntimeException when no categories are found
     */
    public function importProducts_old(int $count = 100): void
    {
        $context = Context::createDefaultContext();
        $products = [];
        $batchSize = 1000;

        // Lade alle verfügbaren Kategorien und Attribute
        $this->loadCategories($context);
        $this->loadAttributes();

        // Begrenze die Anzahl auf mindestens 1 und maximal 1.000.000
        $count = max(1, min(10, $count));

        for ($i = 1; $i <= $count; $i++) {
            $productId = Uuid::randomHex();

            // Zufällige Dimensionen zwischen 1 und 10
            $width = mt_rand(1, 10);
            $height = mt_rand(1, 10);
            $length = mt_rand(1, 10);

            $products[] = [
                'id' => $productId,
                'productNumber' => 'PROD-' . $width . '-' . $height . '-' . $length . '-' . $i,
                'stock' => 100,
                'translations' => $this->getProductTranslations($width, $height, $length),
                'price' => [['currencyId' => 'b7d2554b0ce847cd82f3ac9bd1c0dfca',
                    'gross' => (string)$this->dataGenerator->getRandomPrice(), // Explizit als String
                    'net' => (string)$this->dataGenerator->getRandomPrice(), // Explizit als String
                    'linked' => true]],
                'tax' => ['name' => '19%', 'taxRate' => 19],
                'categories' => $this->getRandomCategoryIds(),
                'properties' => [
                    ['id' => Uuid::randomHex(), 'groupId' => $this->attributeIds['width'], 'name' => $width . ' cm'],
                    ['id' => Uuid::randomHex(), 'groupId' => $this->attributeIds['height'], 'name' => $height . ' cm'],
                    ['id' => Uuid::randomHex(), 'groupId' => $this->attributeIds['length'], 'name' => $length . ' cm']
                ],
                'active' => true
            ];

            if (count($products) >= $batchSize) {
                $this->productRepository->create($products, $context);
                $products = [];
            }
        }

        if (!empty($products)) {
            $this->productRepository->create($products, $context);
        }
    }

    private function loadOrCreatePropertyOptions(Context $context): void
    {
        // Cache für Property-Optionen erstellen
        if (empty($this->propertyOptionIds)) {
            $this->propertyOptionIds = [
                'width' => [],
                'height' => [],
                'length' => []
            ];

            // Für jede Dimension (1-10 cm) Property-Optionen laden oder erstellen
            foreach (['width' => $this->attributeIds['width'],
                         'height' => $this->attributeIds['height'],
                         'length' => $this->attributeIds['length']] as $type => $groupId) {

                for ($value = 1; $value <= 10; $value++) {
                    $name = $value . ' cm';

                    // Prüfen, ob Option bereits existiert
                    $criteria = new Criteria();
                    $criteria->addFilter(new EqualsFilter('groupId', $groupId));
                    $criteria->addFilter(new EqualsFilter('name', $name));

                    $existing = $this->propertyGroupOptionRepository->search($criteria, $context)->first();

                    if ($existing) {
                        $this->propertyOptionIds[$type][$value] = $existing->getId();
                    } else {
                        // Neu erstellen wenn nicht vorhanden
                        $id = Uuid::randomHex();
                        $this->propertyGroupOptionRepository->create([
                            [
                                'id' => $id,
                                'groupId' => $groupId,
                                'name' => $name
                            ]
                        ], $context);
                        $this->propertyOptionIds[$type][$value] = $id;
                    }
                }
            }
        }
    }

    public function importProducts(int $count = 100, int $offset = 0): void
    {
        // Disable Indexing während des Imports
        $context = Context::createDefaultContext();
        $context->addState(EntityIndexerRegistry::DISABLE_INDEXING);
        $products = [];
        $batchSize = 5000;

        $this->loadCategories($context);
        $this->loadAttributes();
        // Lade oder erstelle Property-Optionen einmalig
        $this->loadOrCreatePropertyOptions($context);

        $count = max(1, min(40000, $count));
        // Zufälliger Prefix für diese Import-Session
        $importPrefix = substr(md5(uniqid()), 0, 6);

        for ($i = 1; $i <= $count; $i++) {
            $currentNumber = $i + $offset; // Verwendung des Offsets
            $productId = Uuid::randomHex();

            $width = mt_rand(1, 10);
            $height = mt_rand(1, 10);
            $length = mt_rand(1, 10);

            // Neue eindeutige Produktnummer
            $productNumber = sprintf('PROD-%s-%d-%d-%d-%06d',
                $importPrefix,
                $width,
                $height,
                $length,
                $currentNumber
            );

            // Korrekte Preisberechnung mit 19% MwSt
            $netPrice = $this->dataGenerator->getRandomPrice();
            $grossPrice = $netPrice * 1.19; // Netto + 19% MwSt

            $products[] = [
                'id' => $productId,
                'productNumber' => $productNumber, // Neue eindeutige Nummer,
                'stock' => 100,
                'translations' => $this->getProductTranslations($width, $height, $length),
                'price' => [[
                    'currencyId' => strtolower('B7D2554B0CE847CD82F3AC9BD1C0DFCA'),
                    'net' => (string)$netPrice,
                    'gross' => (string)$grossPrice,
                    'linked' => true
                ]],
                'tax' => ['name' => '19%', 'taxRate' => 19],
                'categories' => $this->getRandomCategoryIds(),

                'properties' => [
                    ['id' => $this->propertyOptionIds['width'][$width]],
                    ['id' => $this->propertyOptionIds['height'][$height]],
                    ['id' => $this->propertyOptionIds['length'][$length]]
                ],
                'active' => true,
                'visibilities' => [
                    [
                        //'salesChannelId' => strtolower('98432DEF39FC4624B33213A56B8C944D'), // Default Storefront
                        'salesChannelId' => strtolower('0196cd8ed19871fa846d4bfba2298364'),
                        'visibility' => 30 // Fully visible
                    ]
                ]
            ];

            if (count($products) >= $batchSize) {
                $this->productRepository->create($products, $context);
                $products = [];

                // Speicher freigeben
                gc_collect_cycles();

                // Fortschritt anzeigen
                echo sprintf("\rImportiere Produkt %d von %d (%d%%) [Offset: %d]",
                    $i,
                    $count,
                    round(($i/$count) * 100),
                    $offset
                );

            }
        }

        if (!empty($products)) {
            $this->productRepository->create($products, $context);
        }
    }

    private function loadAttributes(): void
    {
        // Prüfe ob die Attribute bereits existieren
        $this->attributeIds = $this->attributeService->getAttributeIds();

        if (empty($this->attributeIds) ||
            !isset($this->attributeIds['width']) ||
            !isset($this->attributeIds['height']) ||
            !isset($this->attributeIds['length'])) {
            throw new \RuntimeException('Required attributes (width, height, length) not found. Please create attributes first using the import:attributes command.');
        }
    }

    private function getRandomCategoryIds(): array
    {
        // Wähle 1-3 zufällige Kategorien aus
        $numCategories = mt_rand(1, 1);
        $selectedCategories = array_rand(array_flip($this->categoryIds), $numCategories);

        if (!is_array($selectedCategories)) {
            $selectedCategories = [$selectedCategories];
        }

        return array_map(function($categoryId) {
            return ['id' => $categoryId];
        }, $selectedCategories);
    }

    private function loadCategories(Context $context): void
    {
        $criteria = new Criteria();
        $categories = $this->categoryRepository->search($criteria, $context);

        if ($categories->getTotal() === 0) {
            throw new \RuntimeException('No categories found. Please create categories first using the import:categories command.');
        }

        foreach ($categories->getElements() as $category) {
            $this->categoryIds[] = $category->getId();
        }
    }

    private function getProductTranslations(int $width, int $height, int $length): array
    {
        $translations = [];
        $supportedLocales = ['de-DE', 'en-GB', 'fr-FR', 'nl-NL', 'it-IT', 'es-ES'];

        foreach ($supportedLocales as $locale) {
            $translations[$locale] = [
                'name' => $this->dataGenerator->generateProductName($width, $height, $length, $locale),
                'description' => $this->dataGenerator->generateProductDescription($locale),
                'metaTitle' => $this->dataGenerator->generateMetaTitle(
                    $this->dataGenerator->generateProductName($width, $height, $length, $locale),
                    $locale
                ),
                'metaDescription' => $this->dataGenerator->generateMetaDescription($locale)
            ];
        }

        return $translations;
    }
}
```

/src/custom/plugins/NasadImportProducts/Core/Service/AttributeService.php

```auto
<?php declare(strict_types=1);

namespace NasadImportProducts\Core\Service;

use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Uuid\Uuid;

class AttributeService
{
    public function __construct(
        private readonly EntityRepository $propertyGroupRepository,
        private readonly EntityRepository $propertyGroupOptionRepository
    ) {
    }

    public function getAttributeIds(): array
    {
        $context = Context::createDefaultContext();

        $widthCriteria = new Criteria();
        $widthCriteria->addFilter(new EqualsFilter('name', 'Breite'));
        $widthGroup = $this->propertyGroupRepository->search($widthCriteria, $context)->first();

        $heightCriteria = new Criteria();
        $heightCriteria->addFilter(new EqualsFilter('name', 'Hoehe'));
        $heightGroup = $this->propertyGroupRepository->search($heightCriteria, $context)->first();

        $lengthCriteria = new Criteria();
        $lengthCriteria->addFilter(new EqualsFilter('name', 'Laenge'));
        $lengthGroup = $this->propertyGroupRepository->search($lengthCriteria, $context)->first();

        if (!$widthGroup || !$heightGroup || !$lengthGroup) {
            throw new \RuntimeException('Required property groups not found. Please run import:attributes command first.');
        }

        return [
            'width' => $widthGroup->getId(),
            'height' => $heightGroup->getId(),
            'length' => $lengthGroup->getId(),
        ];
    }

    public function createAttributes(): void
    {
        $context = Context::createDefaultContext();

        $widthId = Uuid::randomHex();
        $heightId = Uuid::randomHex();
        $lengthId = Uuid::randomHex();

        // Erstelle die Basisgruppen
        $groups = [
            [
                'id' => $widthId,
                'name' => 'Breite',
                'displayType' => 'text',
                'sortingType' => 'numeric',
                'filterable' => true,
            ],
            [
                'id' => $heightId,
                'name' => 'Hoehe',
                'displayType' => 'text',
                'sortingType' => 'numeric',
                'filterable' => true,
            ],
            [
                'id' => $lengthId,
                'name' => 'Laenge',
                'displayType' => 'text',
                'sortingType' => 'numeric',
                'filterable' => true,
            ],
        ];

        // Erstelle die Gruppen
        $this->propertyGroupRepository->create($groups, $context);

        // Erstelle die Optionen für jede Gruppe
        $options = [];
        $groupIds = [$widthId, $heightId, $lengthId];

        foreach ($groupIds as $groupId) {
            for ($i = 1; $i <= 10; $i++) {
                $options[] = [
                    'id' => Uuid::randomHex(),
                    'groupId' => $groupId,
                    'name' => $i . ' cm',
                    'position' => $i,
                ];
            }
        }

        // Speichere die Optionen
        if (!empty($options)) {
            $this->propertyGroupOptionRepository->create($options, $context);
        }
    }

    public function deleteAttributes(): void
    {
        $context = Context::createDefaultContext();
        $attributeNames = ['Breite', 'Hoehe', 'Laenge'];

        foreach ($attributeNames as $name) {
            $criteria = new Criteria();
            $criteria->addFilter(new EqualsFilter('name', $name));

            $attribute = $this->propertyGroupRepository->search($criteria, $context)->first();

            if ($attribute) {
                $this->propertyGroupRepository->delete([['id' => $attribute->getId()]], $context);
            }
        }
    }
}
```

/src/custom/plugins/NasadImportProducts/Core/Service/DummyDataGenerator.php

```auto
<?php declare(strict_types=1);

namespace NasadImportProducts\Core\Service;

class DummyDataGenerator
{
    private const array LANGUAGES = ['de-DE', 'en-GB', 'fr-FR', 'nl-NL', 'it-IT', 'es-ES'];

    public function generateCategoryName(int $number, string $locale): string
    {
        $prefixes = [
            'de-DE' => 'Test',
            'en-GB' => 'Test',
            'fr-FR' => 'Test',
            'nl-NL' => 'Test',
            'it-IT' => 'Test',
            'es-ES' => 'Test'
        ];
        return $prefixes[$locale] . ' ' . $number;
    }

    public function generateCategoryDescription(string $locale): string
    {
        $descriptions = [
            'de-DE' => 'Testbeschreibung',
            'en-GB' => 'Description',
            'fr-FR' => 'Description',
            'nl-NL' => 'Beschrijving',
            'it-IT' => 'Descrizione',
            'es-ES' => 'Descripcion'
        ];

        return $descriptions[$locale];
    }

    public function generateProductName(int $width, int $height, int $length, string $locale): string
    {
        $prefixes = [
            'de-DE' => 'Produkt',
            'en-GB' => 'Product',
            'fr-FR' => 'Produit',
            'nl-NL' => 'Product',
            'it-IT' => 'Prodotto',
            'es-ES' => 'Producto'
        ];

        return sprintf('%s %dx%dx%d',
            $prefixes[$locale],
            $width, // Direkt die Werte verwenden
            $height, // ohne Modifikation
            $length
        );
    }

    public function generateProductDescription(string $locale): string
    {
        $descriptions = [
            'de-DE' => 'Test',
            'en-GB' => 'Test',
            'fr-FR' => 'Test',
            'nl-NL' => 'Test',
            'it-IT' => 'Test',
            'es-ES' => 'Test'
        ];

        return $descriptions[$locale];
    }

    public function generateMetaTitle(string $name, string $locale): string
    {
        return $name . ' | ' . $this->getStoreName($locale);
    }

    public function generateMetaDescription(string $locale): string
    {
        $descriptions = [
            'de-DE' => 'Test',
            'en-GB' => 'Test',
            'fr-FR' => 'Test',
            'nl-NL' => 'Test',
            'it-IT' => 'Test',
            'es-ES' => 'Test'
        ];

        return $descriptions[$locale];
    }

    private function getStoreName(string $locale): string
    {
        $names = [
            'de-DE' => 'Shop',
            'en-GB' => 'Shop',
            'fr-FR' => 'Shop',
            'nl-NL' => 'Shop',
            'it-IT' => 'Shop',
            'es-ES' => 'Shop'
        ];

        return $names[$locale];
    }

    public function getRandomPrice(): string
    {
        return number_format(mt_rand(1000, 99900) / 100, 2, '.', '');

    }
}
```

---

<div class="post-metadata">

**Author:** ![nasad](https://avatars.discourse-cdn.com/v4/letter/n/8baadc/32.png) [@nasad](https://forum.shopware.com/u/nasad)\
**Post date:** [15. Mai 2025 um 17:12 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/3 "2025-05-15T17:12:45Z")

</div>

und die Daten so erzeugt:

```auto
rm -rf var/cache/*
php bin/console cache:clear
php bin/console cache:warmup

# Neue Daten importieren
bin/console nasad:import:attributes
sleep 5
bin/console nasad:import:categories --count=300
```

Zuerst alle Importe ausführen:

```auto
bin/console nasad:import:products --count=250000 --offset=0 &
bin/console nasad:import:products --count=250000 --offset=250000 &
bin/console nasad:import:products --count=250000 --offset=500000 &
bin/console nasad:import:products --count=250000 --offset=750000 &
```

Warten bis alle Import-Prozesse abgeschlossen sind  
Dann die Indizierung in die Queue stellen:

```auto
bin/console dal:refresh:index --use-queue
```

die Queue-Worker starten:

```auto
bin/console messenger:consume async -vv --limit=1000 &
bin/console messenger:consume async -vv --limit=1000 &
bin/console messenger:consume async -vv --limit=1000 &
```

---

<div class="post-metadata">

**Author:** ![Max\_Shop](https://avatars.discourse-cdn.com/v4/letter/m/58f4c7/32.png) [@Max\_Shop](https://forum.shopware.com/u/Max_Shop)\
**Post date:** [16. Mai 2025 um 08:02 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/4 "2025-05-16T08:02:27Z")

</div>

> **[Performance Tweaks | Shopware Documentation](https://developer.shopware.com/docs/guides/hosting/performance/performance-tweaks.html)**
>
> Documentation for Shopware developers

---

<div class="post-metadata">

**Author:** ![Max\_Shop](https://avatars.discourse-cdn.com/v4/letter/m/58f4c7/32.png) [@Max\_Shop](https://forum.shopware.com/u/Max_Shop)\
**Post date:** [16. Mai 2025 um 08:09 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/5 "2025-05-16T08:09:30Z")

</div>

Ja, sobald sich der Shopware Context verändert, wird kein Cache mehr verwendet. Das trifft bspw. zu, wenn ein Artikel im Warenkorb ist oder ein Kunde sich eingeloggt hat.

Hier [Performance Tweaks | Shopware Documentation](https://developer.shopware.com/docs/guides/hosting/performance/performance-tweaks.html#logged-in-cart-filled) wird beschrieben, wie man das umgehen kann, wenn dies im spezifisches Projekt nicht notwendig ist. Der Warenkorb wird per XHR dynamisch nachgeladen.

Ab Shopware 6.7 wird ESI für einzelne Bereiche genutzt.

Die Anzahl der Produkte sind in der Regel selten das Problem. Erst die Verknüpfung mit Options, Properties, Rules, etc. treibt manche Queries in die Höhe.

Nutze den Symfony Profiler im dev Modus um herauszufinden, wo das Bottelneck liegt.

---

<div class="post-metadata">

**Author:** ![nasad](https://avatars.discourse-cdn.com/v4/letter/n/8baadc/32.png) [@nasad](https://forum.shopware.com/u/nasad)\
**Post date:** [16. Mai 2025 um 13:49 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/6 "2025-05-16T13:49:26Z")

</div>

Hallo,

vielen Dank für deine Vorschläge.

Ja, ich benutze den Symfony Profiler – genau deshalb habe ich nach dem Cache gefragt, weil die Ursache irgendwo dort liegen muss.  
Laut Query Metrics habe ich 16 Datenbankabfragen mit einer \*\*Gesamtlaufzeit von 1,57 ms:

 ![image](https://europe1.discourse-cdn.com/flex013/uploads/shopware/original/3X/c/e/ce5141f7b7da0331d3ccb6edb2f4be1dc319bb55.png)

Es liegt also nicht an der Datenbank.

Performance metrics

 ![image](https://europe1.discourse-cdn.com/flex013/uploads/shopware/original/3X/2/c/2c0e5d9205ce25613eb5b01cd67caf0448abd16c.png)

kernel.controller: 6107.1 ms / 4136 MiB  
ContextResolverListener: 6106.8 ms / 4136 MiB  
sales-channel-context: 6106.5 ms / 4136 MiB

Alle Rules habe ich gelöscht.

Ist die Verarbeitungsreihenfolge richtig von mir?  
Request → PHP Bootstrap → Kernel → Context Aufbau → Controller → HTTP Cache

---

<div class="post-metadata">

**Author:** ![nasad](https://avatars.discourse-cdn.com/v4/letter/n/8baadc/32.png) [@nasad](https://forum.shopware.com/u/nasad)\
**Post date:** [16. Mai 2025 um 13:51 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/7 "2025-05-16T13:51:49Z")

</div>

Noch was. Im Adminbereich habe ich sehr gute Ladezeiten.

---

<div class="post-metadata">

**Author:** ![Max\_Shop](https://avatars.discourse-cdn.com/v4/letter/m/58f4c7/32.png) [@Max\_Shop](https://forum.shopware.com/u/Max_Shop)\
**Post date:** [16. Mai 2025 um 18:41 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/8 "2025-05-16T18:41:51Z")

</div>

Der Sales-Chanel-Context sollte eigentlich nicht so komplex zu berechnen sein. Der scheint aber das Problem zu sein.

Würde an deiner Stelle versuchen entweder die SQL Queries zu analysieren oder die PHP Class zu finden und die PHP memory usage Funktion an diversen Stellen einzubauen und im error log ausgeben.

So solltest du herausfinden welcher Part so viel RAM benötigt.

---

<div class="post-metadata">

**Author:** ![frip-tech.de](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.shopware.com/frip-tech.de/32/24453_2.png) [@frip-tech.de](https://forum.shopware.com/u/frip-tech.de)\
**Post date:** [17. Mai 2025 um 08:07 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/9 "2025-05-17T08:07:47Z")

</div>

CustomFields nicht vergessen, sind auch ein Killer

---

<div class="post-metadata">

**Author:** ![Teddie](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.shopware.com/teddie/32/26013_2.png) [@Teddie](https://forum.shopware.com/u/Teddie)\
**Post date:** [19. Mai 2025 um 11:49 UTC](https://forum.shopware.com/t/shopware-performance-probleme-bitte-um-unterstuetzung/107217/10 "2025-05-19T11:49:54Z")

</div>

> [@nasad](#):
>
> Hat jemand Erfahrungen mit einem Shop dieser Größenordnung (1 Mio Artikel) in Shopware? Gibt es dafür Best Practices oder Hinweise auf technische Grenzen?

Jain, eigentlich gibt es da keine Grenze nur ein paar Bremsen mit steigender Komplexität.

> [@nasad](#):
>
> kernel.controller: 6107.1 ms / 4136 MiB  
> ContextResolverListener: 6106.8 ms / 4136 MiB  
> sales-channel-context: 6106.5 ms / 4136 MiB

Das ist schon eine ganz ordentliche Auslastung.

Auf welcher Seite hast du diese denn?

Solche Datenmengen kommen bei mir nur bei Produkten mit \> 10 k Varianten, auf Erlebniswelten mit vielen Produkt-Slidern oder riesigen Kategorien zustande.

Hast du den Cache deaktiviert?

> [@nasad](#):
>
> Noch was. Im Adminbereich habe ich sehr gute Ladezeiten.

Das ist normal.
