DEV Community

Magevanta
Magevanta

Posted on • Originally published at magevanta.com

Magento 2 Multistore Performance: Architecture & Optimization Guide

Running multiple stores, websites, or store views on a single Magento 2 instance is one of the platform's biggest strengths. One codebase, one admin, one database — shared infrastructure for multiple brands, languages, or regions.

But multistore setups come with hidden performance traps. The same features that make them flexible — scoped configuration, separate price rules, store-specific catalog data — can silently murder your response times if you're not careful.

This guide covers the architecture decisions and practical optimizations that keep a multistore Magento 2 installation running fast.

How Magento 2 Resolves Store Context

Before optimizing, you need to understand the performance cost of store resolution itself.

Every request, Magento determines the current store via one of these mechanisms:

  1. Server Name — the default and recommended approach (each store on its own domain/subdomain)
  2. URL prefix — store code in the URL path (/uk/, /de/)
  3. Cookie — least performant, not recommended for production

The MAGE_RUN_CODE and MAGE_RUN_TYPE environment variables (set in Nginx or .htaccess) tell Magento which store to bootstrap. Getting this wrong — or relying on runtime detection — adds unnecessary overhead to every single request.

Set it at the web server level, not in PHP:

# Nginx example: domain-based store routing
server {
    server_name store-uk.example.com;
    fastcgi_param MAGE_RUN_CODE uk_store;
    fastcgi_param MAGE_RUN_TYPE store;
}

server {
    server_name store-de.example.com;
    fastcgi_param MAGE_RUN_CODE de_store;
    fastcgi_param MAGE_RUN_TYPE store;
}
Enter fullscreen mode Exit fullscreen mode

This avoids the \Magento\Store\Model\StoreResolver doing expensive database lookups on every request.

Cache Segmentation: The Biggest Pitfall

By default, Magento stores cache entries with a cache key that includes the store context. But if you're not careful about cache tags, pages from one store can bleed into another — or worse, the cache hit rate tanks because every store generates separate entries for largely identical content.

Full Page Cache (FPC) Strategy

Each store view should have a clean FPC namespace. With Varnish, this means:

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}
Enter fullscreen mode Exit fullscreen mode

The req.http.host inclusion ensures store A's cached homepage doesn't serve as store B's cache. Without it, you'll get cross-store cache pollution.

Redis Cache Separation

If you're running Redis for caching (and you should be), use separate Redis databases per logical cache type — but don't over-segment by store. Magento's cache keys already include store scope:

// app/etc/env.php
'cache' => [
    'frontend' => [
        'default' => [
            'backend' => 'Cm_Cache_Backend_Redis',
            'backend_options' => [
                'server' => '127.0.0.1',
                'database' => '0',
            ],
        ],
        'page_cache' => [
            'backend' => 'Cm_Cache_Backend_Redis',
            'backend_options' => [
                'server' => '127.0.0.1',
                'database' => '1',
            ],
        ],
    ],
],
Enter fullscreen mode Exit fullscreen mode

Separating default and page_cache into different Redis databases prevents FPC flushes from wiping your config/block cache.

Configuration Scoping & Database Queries

Magento's EAV-based configuration system is scope-aware. Every core_config_data value can exist at global, website, or store level — and Magento merges them at runtime.

In a multistore setup, this merge process runs on every request (unless cached). With 5 stores × hundreds of config keys, you're looking at significant overhead.

Profile it:

bin/magento dev:query-log:enable
# Run a few pages, then inspect var/debug/db.log
grep "core_config_data" var/debug/db.log | wc -l
Enter fullscreen mode Exit fullscreen mode

Mitigate it:

  • Keep config values at the global scope whenever possible. Only override at store level when genuinely necessary.
  • Avoid creating store-specific config values "just in case" — every scope override adds to the merge cost.
  • Make sure config cache type is always enabled in production.

Indexer Load in Multistore Environments

Indexers are one of the biggest hidden costs in multistore Magento. The catalog price indexer, in particular, generates a price row for every product × customer group × website combination.

With 3 websites, 4 customer groups, and 50,000 products:

3 × 4 × 50,000 = 600,000 rows in catalog_product_index_price
Enter fullscreen mode Exit fullscreen mode

Compare that to a single-store setup: 4 × 50,000 = 200,000 rows. You've tripled the index size just by adding two more websites.

Strategies to reduce indexer bloat:

1. Limit Customer Groups

Every extra customer group multiplies your price index. Audit and remove unused groups via:

SELECT cg.customer_group_id, cg.customer_group_code, COUNT(c.entity_id) as customers
FROM customer_group cg
LEFT JOIN customer_entity c ON c.group_id = cg.customer_group_id
GROUP BY cg.customer_group_id
ORDER BY customers ASC;
Enter fullscreen mode Exit fullscreen mode

Groups with zero customers are candidates for removal.

2. Use Shared Catalogs Carefully

B2B shared catalogs create additional price index entries. If you don't need per-company pricing, a simpler tier price setup is more performant.

3. Schedule Indexers Wisely

In multistore setups, indexer runs take longer. Use Update by Schedule mode but stagger your cron windows so the price indexer doesn't fire simultaneously across all websites:

# bin/magento indexer:set-mode schedule catalog_product_price
# Then customize mview cron schedule in a custom module
Enter fullscreen mode Exit fullscreen mode

Store-Specific vs. Shared Catalog Data

One of the biggest architectural decisions in multistore Magento is how much catalog data you share vs. scope.

Shared (global scope) — fast:

  • Product names, descriptions, prices (if not localized)
  • Categories with same URL structure

Store-scoped — slower:

  • Translated attribute values (name, description per store)
  • Store-specific URL rewrites
  • Custom prices per website

Every store-scoped attribute value multiplies your EAV table size. A product with 10 text attributes across 5 store views = 50 rows in catalog_product_entity_varchar vs. 10 for a single-store setup.

Practical rule: Only create store-scoped overrides when there's a real business requirement. "We might translate this later" is not a reason to scope everything to store level today.

URL Rewrite Management

URL rewrites are a notorious performance bottleneck, and it gets worse with multiple stores. Magento generates a URL rewrite record for every product × store view × category path combination.

Check your current rewrite count:

SELECT store_id, COUNT(*) as rewrites 
FROM url_rewrite 
GROUP BY store_id 
ORDER BY rewrites DESC;
Enter fullscreen mode Exit fullscreen mode

If you see millions of rows, you have a problem. Common causes:

  • Categories nested too deeply — URL paths include all parent categories, so moving a category regenerates thousands of rewrites
  • Unnecessary store views — disabled store views still have URL rewrites generated for them
  • Product-category rewrites enabled — the most common culprit

Disable product-category URL rewrites if you don't need them:

Stores → Configuration → Catalog → Search Engine Optimization
→ Use Categories Path for Product URLs → No
Enter fullscreen mode Exit fullscreen mode

This single change can reduce your url_rewrite table by 60-80% in large catalogs.

Session Storage for Multiple Stores

With multiple stores (especially across domains), session handling needs extra attention.

  • Use Redis for sessions — never filesystem sessions at scale
  • If stores are on different domains, you cannot share sessions via cookie — each domain needs its own session namespace
  • Set path and domain in session config per store if needed
// app/etc/env.php
'session' => [
    'save' => 'redis',
    'redis' => [
        'host' => '127.0.0.1',
        'port' => '6379',
        'database' => '2',
        'disable_locking' => '1',
    ],
],
Enter fullscreen mode Exit fullscreen mode

disable_locking => 1 is important for performance — default session locking serializes concurrent requests from the same user.

Monitoring Multistore Performance

Set up per-store performance baselines. A single slow store can be hard to detect in aggregate metrics.

New Relic / Datadog: tag transactions with store code. In Magento, you can do this via a plugin on \Magento\Store\Model\StoreManagerInterface::getStore().

Simple approach — separate log files per store:

access_log /var/log/nginx/store_uk_access.log combined;
Enter fullscreen mode Exit fullscreen mode

Then track TTFB per store with a log parser. A sudden increase in one store's TTFB while others stay flat tells you exactly where to dig.

Quick Wins Checklist

Before diving into deep architecture changes, confirm these basics:

  • [ ] MAGE_RUN_CODE / MAGE_RUN_TYPE set at Nginx level, not PHP
  • [ ] Separate Redis databases for default and page_cache
  • [ ] All cache types enabled: bin/magento cache:status
  • [ ] Product-category URL rewrites disabled if not needed
  • [ ] Unused customer groups removed
  • [ ] Indexers in "Update by Schedule" mode
  • [ ] Config values at global scope where possible
  • [ ] Session locking disabled in Redis session config
  • [ ] Each domain resolves via server_name, not URL prefix

Conclusion

Multistore Magento is not inherently slow — but it amplifies every architectural mistake. The same configuration bloat, indexer inefficiency, or cache misconfiguration that causes minor issues on a single store becomes a serious problem at 3× or 5× scale.

The good news: most of the wins are configuration changes, not code. Set your store resolution at the web server level, keep your config scope lean, manage your URL rewrites, and keep an eye on indexer growth as your catalog scales.

Get these right and a multistore Magento setup can serve millions of requests efficiently — no extra infrastructure required.

Top comments (1)

Collapse
 
toshihiro_shishido profile image
toshihiro shishido

Performance directly hits CVR but it's surprisingly weak on AOV. Faster pages convert more sessions but the basket size barely moves. If AOV is the goal, performance work is downstream of merchandising — fix bundles and recommendations first, optimize TTFB second.