From b9258df955e3f65f1357c44eae39f3304c901c21 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:19:53 +0000 Subject: [PATCH 1/4] Replace per-worker Config cache with shared Swoole Table for hostname cert tracking The hostname certificate cache was using Config::getParam/setParam which stores a plain PHP array local to each worker. This meant every worker independently hit the DB for the same hostnames. Replace with a Swoole Table shared across all workers via shared memory. Co-Authored-By: Claude Opus 4.6 --- app/controllers/general.php | 13 +++++-------- app/http.php | 4 ++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 2d3bde69d61..1efb9ab4b76 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1073,21 +1073,19 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S ->inject('queueForCertificates') ->inject('platform') ->inject('authorization') - ->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) { + ->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) use ($hostnames) { $hostname = $request->getHostname(); - $cache = Config::getParam('hostnames', []); $platformHostnames = $platform['hostnames'] ?? []; // 1. Cache hit - if (array_key_exists($hostname, $cache)) { + if ($hostnames->exists(md5($hostname))) { return; } // 2. Domain validation $domain = new Domain(!empty($hostname) ? $hostname : ''); if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) { - $cache[$domain->get()] = false; - Config::setParam('hostnames', $cache); + $hostnames->set(md5($domain->get()), ['value' => 0]); return; } @@ -1101,7 +1099,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S } // 4. Check/create rule (requires DB access) - $authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, &$cache) { + $authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, $hostnames) { try { // TODO: (@Meldiron) Remove after 1.7.x migration $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; @@ -1164,8 +1162,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S } catch (Duplicate $e) { Console::info('Certificate already exists'); } finally { - $cache[$domain->get()] = true; - Config::setParam('hostnames', $cache); + $hostnames->set(md5($domain->get()), ['value' => 1]); } }); }); diff --git a/app/http.php b/app/http.php index 1d7949da864..9012e821aef 100644 --- a/app/http.php +++ b/app/http.php @@ -43,6 +43,10 @@ $domains->column('value', Table::TYPE_INT, 1); $domains->create(); +$hostnames = new Table(100_000); +$hostnames->column('value', Table::TYPE_INT, 1); +$hostnames->create(); + $http = new Server( host: "0.0.0.0", port: System::getEnv('PORT', 80), From 56cbcba2b17de0e1e5561ae180e30340e9d88451 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:37:28 +0000 Subject: [PATCH 2/4] Resize domains Swoole Table from 1M to 100K rows Co-Authored-By: Claude Opus 4.6 --- app/http.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/http.php b/app/http.php index 7b12cfe1b47..be8bb051223 100644 --- a/app/http.php +++ b/app/http.php @@ -40,7 +40,7 @@ $files = new Files(); $files->load(__DIR__ . '/../public'); -$domains = new Table(1_000_000); // 1 million rows +$domains = new Table(100_000); $domains->column('value', Table::TYPE_INT, 1); $domains->create(); From b83e3ef36ea66d2217c52abb335e6c851e3351bb Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:15:25 +0000 Subject: [PATCH 3/4] Make Swoole Tables injectable Http resources Register both `domains` and `hostnames` tables as Http resources so they can be injected via the framework instead of relying on closures or globals. Co-Authored-By: Claude Opus 4.6 --- app/controllers/general.php | 4 +++- app/http.php | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 1efb9ab4b76..cc38a2ef440 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -34,6 +34,7 @@ use Executor\Executor; use MaxMind\Db\Reader; use Swoole\Http\Request as SwooleRequest; +use Swoole\Table; use Utopia\Config\Config; use Utopia\Console; use Utopia\Database\Database; @@ -1073,7 +1074,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S ->inject('queueForCertificates') ->inject('platform') ->inject('authorization') - ->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) use ($hostnames) { + ->inject('hostnames') + ->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization, Table $hostnames) { $hostname = $request->getHostname(); $platformHostnames = $platform['hostnames'] ?? []; diff --git a/app/http.php b/app/http.php index be8bb051223..e921b154deb 100644 --- a/app/http.php +++ b/app/http.php @@ -48,6 +48,9 @@ $hostnames->column('value', Table::TYPE_INT, 1); $hostnames->create(); +Http::setResource('domains', fn () => $domains); +Http::setResource('hostnames', fn () => $hostnames); + $http = new Server( host: "0.0.0.0", port: System::getEnv('PORT', 80), @@ -583,7 +586,7 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c }); // Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory -$http->on(Constant::EVENT_TASK, function () use ($register, $domains) { +$http->on(Constant::EVENT_TASK, function () use ($register) { $lastSyncUpdate = null; $pools = $register->get('pools'); Http::setResource('pools', fn () => $pools); @@ -592,6 +595,9 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c /** @var Utopia\Database\Database $dbForPlatform */ $dbForPlatform = $app->getResource('dbForPlatform'); + /** @var Table $domains */ + $domains = $app->getResource('domains'); + Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) { try { $time = DateTime::now(); From f1501d139bbf9a7c153d31b1223e53f8aa7fc76f Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:22:07 +0000 Subject: [PATCH 4/4] Rename Swoole Tables: domains -> riskyDomains, hostnames -> certifiedDomains Co-Authored-By: Claude Opus 4.6 --- app/controllers/general.php | 12 ++++++------ app/http.php | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index cc38a2ef440..7fdbeb1855a 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1074,20 +1074,20 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S ->inject('queueForCertificates') ->inject('platform') ->inject('authorization') - ->inject('hostnames') - ->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization, Table $hostnames) { + ->inject('certifiedDomains') + ->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization, Table $certifiedDomains) { $hostname = $request->getHostname(); $platformHostnames = $platform['hostnames'] ?? []; // 1. Cache hit - if ($hostnames->exists(md5($hostname))) { + if ($certifiedDomains->exists(md5($hostname))) { return; } // 2. Domain validation $domain = new Domain(!empty($hostname) ? $hostname : ''); if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) { - $hostnames->set(md5($domain->get()), ['value' => 0]); + $certifiedDomains->set(md5($domain->get()), ['value' => 0]); return; } @@ -1101,7 +1101,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S } // 4. Check/create rule (requires DB access) - $authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, $hostnames) { + $authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, $certifiedDomains) { try { // TODO: (@Meldiron) Remove after 1.7.x migration $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; @@ -1164,7 +1164,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S } catch (Duplicate $e) { Console::info('Certificate already exists'); } finally { - $hostnames->set(md5($domain->get()), ['value' => 1]); + $certifiedDomains->set(md5($domain->get()), ['value' => 1]); } }); }); diff --git a/app/http.php b/app/http.php index e921b154deb..d771796a57f 100644 --- a/app/http.php +++ b/app/http.php @@ -40,16 +40,16 @@ $files = new Files(); $files->load(__DIR__ . '/../public'); -$domains = new Table(100_000); -$domains->column('value', Table::TYPE_INT, 1); -$domains->create(); +$riskyDomains = new Table(100_000); +$riskyDomains->column('value', Table::TYPE_INT, 1); +$riskyDomains->create(); -$hostnames = new Table(100_000); -$hostnames->column('value', Table::TYPE_INT, 1); -$hostnames->create(); +$certifiedDomains = new Table(100_000); +$certifiedDomains->column('value', Table::TYPE_INT, 1); +$certifiedDomains->create(); -Http::setResource('domains', fn () => $domains); -Http::setResource('hostnames', fn () => $hostnames); +Http::setResource('riskyDomains', fn () => $riskyDomains); +Http::setResource('certifiedDomains', fn () => $certifiedDomains); $http = new Server( host: "0.0.0.0", @@ -78,7 +78,7 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int { $resolveWorkerId = function (Server $server, $data = null) { - global $totalWorkers, $domains; + global $totalWorkers, $riskyDomains; // If data is not set we can send request to any worker // first we try to pick idle worker, if not we randomly pick a worker @@ -110,7 +110,7 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int $risky = false; if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) { $risky = true; - } elseif ($domains->get(md5($domain), 'value') === 1) { + } elseif ($riskyDomains->get(md5($domain), 'value') === 1) { // executions request coming from custom domain $risky = true; } else { @@ -595,10 +595,10 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c /** @var Utopia\Database\Database $dbForPlatform */ $dbForPlatform = $app->getResource('dbForPlatform'); - /** @var Table $domains */ - $domains = $app->getResource('domains'); + /** @var Table $riskyDomains */ + $riskyDomains = $app->getResource('riskyDomains'); - Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) { + Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $app) { try { $time = DateTime::now(); $limit = 1000; @@ -651,7 +651,7 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c continue; } - $domains->set(md5($domain), ['value' => 1]); + $riskyDomains->set(md5($domain), ['value' => 1]); } $latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null; }