Skip to content

Performance degrades with hundreds of sites #14670

@msuphilipgales

Description

@msuphilipgales

Bug description

I am running Statamic with 800 sites and have hit some expected performance bottlenecks. These are three small changes that have helped improve my page load time.

  1. Nav::existsIn() and CoreNav load all trees to check one site.

existsIn($site) calls trees()->has($site), and trees() calls $this->in() for every site, then filters. Same thing happens in CoreNav via $nav->sites()->contains(), since sites() goes through trees() too.

src/Structures/Nav.php

 public function existsIn($site)
 {
-    return $this->trees()->has($site);
+    return $this->in($site) !== null;
 }

src/CP/Navigation/CoreNav.php (lines 114 and 118)

-$nav->sites()->contains(Site::selected()->handle())
+$nav->existsIn(Site::selected()->handle())

  1. Sites::authorized() gate overhead for super users. Our super users need everysite but most of our users have access to less than 30 sites.

authorized() runs can('view', $site) on every site. SitePolicy::before() already returns true for super users, but the gate framework still has per-call overhead.

src/Sites/Sites.php

 public function authorized()
 {
+    if (User::current()->isSuper()) {
+        return $this->sites;
+    }
+
     return $this->sites->filter(fn ($site) => User::current()->can('view', $site));
 }

  1. Site::resolveAntlersValue() parses plain strings through the Antlers runtime

Every site config value goes through Parse::config() and RuntimeParser, even plain strings like en_US or /about/.

src/Sites/Site.php (after the existing is_array guard)

+    if (! is_string($value) || ! str_contains($value, '{')) {
+        return $value;
+    }
+
     $value = Parse::config($value);

How to reproduce

I created a seeder to create a lot of sites. Seed the sites and then navigate the CP.

<?php

namespace Database\Seeders;

use App\Actions\CreateSiteAction;
use Faker\Factory as Faker;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;

class SiteSeeder extends Seeder
{
    public function run(): void
    {
        $action = new CreateSiteAction;
        $faker = Faker::create();

        for ($i = 1; $i <= 1500; $i++) {
            $name = $faker->unique()->company();
            $handle = Str::slug($name);

            $action->handle(
                handle: $handle,
                name: $name,
                url: "/{$handle}/",
            );
        }
    }
}

Our CreateSiteAction handles the creation of the site, role, asset container, etc.

Logs

Environment

Environment
Laravel Version: 13.8.0
PHP Version: 8.5.5
Composer Version: 2.9.5
Environment: local
Debug Mode: ENABLED
Maintenance Mode: OFF
Timezone: UTC
Locale: en

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: file
Database: mariadb
Logs: stack / daily
Mail: log
Queue: database
Session: file

Storage
public/storage: NOT LINKED

Statamic
Addons: 4
License Key: Set
Sites: 1522 (Johnson, Tillman and Cremin, Dach-Weber, Howell, Beier and Ortiz, and 1519 more)
Stache Watcher: Enabled (auto)
Static Caching: Disabled
Version: 6.18.0 PRO

Statamic Addons
ndx/statamic-simple-redirects: 1.1.0
statamic/audit-log: 1.1.0
statamic/collaboration: 2.0.1
statamic/eloquent-driver: 5.7.0

Statamic Eloquent Driver
Addon Settings: file
Asset Containers: eloquent
Assets: eloquent
Blueprints: eloquent
Collection Trees: eloquent
Collections: file
Entries: eloquent
Fieldsets: file
Form Submissions: eloquent
Forms: eloquent
Global Sets: eloquent
Global Variables: eloquent
Navigation Trees: eloquent
Navigations: eloquent
Revisions: eloquent
Sites: eloquent
Taxonomies: eloquent
Terms: eloquent
Tokens: eloquent

Installation

Existing Laravel app

Additional details

I recognize this is an atypical use case and that these changes (particularly #1) may have implications I'm not aware of.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions