perf(horizon): skip queues for platforms that are disabled (#321)

* perf(horizon): skip queues for platforms that are disabled

The social-publishing supervisor listens on Platform::allQueues(), which maps
over every enum case regardless of the per-platform *_ENABLED toggles. With
minProcesses => 1 that means one worker per supported platform - even on an
installation that only ever connects two or three of them.

On a single-workspace self-host that was 19 Horizon workers at roughly 75 MB
each; filtering by the toggles brought it to 9 and cut container memory from
1.66 GB to 1.06 GB, with no change in publishing behaviour.

Note on the implementation: the filter reads env() directly rather than calling
Platform::isEnabled(). Config files load alphabetically, so config('trypost.*')
does not exist yet while horizon.php is evaluated - isEnabled() would silently
return its default of true and the filter would be a no-op. This bites at
config:cache time, so it is invisible in tinker.

* refactor(horizon): filter disabled platform queues via enum

Move queue filtering to Platform::enabledQueues() and apply it from
AppServiceProvider after config has loaded, avoiding env() parsing in
horizon.php while keeping isEnabled() as the single source of truth.

* refactor(horizon): use enabledQueues directly in horizon config

Remove AppServiceProvider boot override and let isEnabled() fall back
to env when trypost config is not loaded yet.

* chore: enable Eloquent strict mode in all environments

* refactor(platform): replace enabled env key derivation with explicit match

* refactor(platform): simplify isEnabled using config default fallback

* test(platform): cover publishing queues and enabled toggles exhaustively

* refactor(platform): collapse isEnabled env fallback into one method

* refactor(platform): drop filter_var and rely on env boolean casting

* revert: keep Eloquent strict mode out of production

shouldBeStrict() in production would throw on lazy loads in queued
publish jobs and can stop posting. Restore the Laravel default.

---------

Co-authored-by: Paulo Castellano <paulo@castellanos.llc>
This commit is contained in:
Maciej Dzierżek 2026-09-03 18:51:03 +02:00 committed by GitHub
parent abe687d0fa
commit ca497e1f3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 214 additions and 8 deletions

View file

@ -380,6 +380,18 @@ public static function allQueues(): array
return array_map(fn (self $platform) => $platform->queue(), self::cases());
}
/**
* @return array<string>
*/
public static function enabledQueues(): array
{
return collect(self::cases())
->filter(fn (self $platform): bool => $platform->isEnabled())
->map(fn (self $platform): string => $platform->queue())
->values()
->all();
}
public function instagramGraphBaseUrl(): string
{
return match ($this) {
@ -390,7 +402,25 @@ public function instagramGraphBaseUrl(): string
public function isEnabled(): bool
{
return config("trypost.platforms.{$this->value}.enabled", true);
return (bool) config(
"trypost.platforms.{$this->value}.enabled",
env(match ($this) {
self::LinkedIn => 'LINKEDIN_ENABLED',
self::LinkedInPage => 'LINKEDIN_PAGE_ENABLED',
self::X => 'X_ENABLED',
self::TikTok => 'TIKTOK_ENABLED',
self::YouTube => 'YOUTUBE_ENABLED',
self::Facebook => 'FACEBOOK_ENABLED',
self::Instagram => 'INSTAGRAM_ENABLED',
self::InstagramFacebook => 'INSTAGRAM_FACEBOOK_ENABLED',
self::Threads => 'THREADS_ENABLED',
self::Pinterest => 'PINTEREST_ENABLED',
self::Bluesky => 'BLUESKY_ENABLED',
self::Mastodon => 'MASTODON_ENABLED',
self::Telegram => 'TELEGRAM_ENABLED',
self::Discord => 'DISCORD_ENABLED',
}, true),
);
}
/**

View file

@ -228,7 +228,7 @@
'social-publishing' => [
'connection' => 'redis',
'queue' => Platform::allQueues(),
'queue' => Platform::enabledQueues(),
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'minProcesses' => 1,

View file

@ -103,17 +103,193 @@
expect(Platform::Facebook->defaultTokenTtlSeconds())->toBeNull();
});
test('platform is enabled by default', function () {
expect(Platform::LinkedIn->isEnabled())->toBeTrue();
expect(Platform::Instagram->isEnabled())->toBeTrue();
test('platform is enabled by default for every platform', function (Platform $platform) {
expect($platform->isEnabled())->toBeTrue();
})->with([
Platform::LinkedIn,
Platform::LinkedInPage,
Platform::X,
Platform::TikTok,
Platform::YouTube,
Platform::Facebook,
Platform::Instagram,
Platform::InstagramFacebook,
Platform::Threads,
Platform::Pinterest,
Platform::Bluesky,
Platform::Mastodon,
Platform::Telegram,
Platform::Discord,
]);
test('each platform can be disabled via config', function (Platform $platform) {
config(["trypost.platforms.{$platform->value}.enabled" => false]);
expect($platform->isEnabled())->toBeFalse();
})->with([
Platform::LinkedIn,
Platform::LinkedInPage,
Platform::X,
Platform::TikTok,
Platform::YouTube,
Platform::Facebook,
Platform::Instagram,
Platform::InstagramFacebook,
Platform::Threads,
Platform::Pinterest,
Platform::Bluesky,
Platform::Mastodon,
Platform::Telegram,
Platform::Discord,
]);
test('each platform maps to its publishing queue', function (Platform $platform, string $queue) {
expect($platform->queue())->toBe($queue);
})->with([
[Platform::LinkedIn, 'social-linkedin'],
[Platform::LinkedInPage, 'social-linkedin-page'],
[Platform::X, 'social-x'],
[Platform::TikTok, 'social-tiktok'],
[Platform::YouTube, 'social-youtube'],
[Platform::Facebook, 'social-facebook'],
[Platform::Instagram, 'social-instagram'],
[Platform::InstagramFacebook, 'social-instagram-facebook'],
[Platform::Threads, 'social-threads'],
[Platform::Pinterest, 'social-pinterest'],
[Platform::Bluesky, 'social-bluesky'],
[Platform::Mastodon, 'social-mastodon'],
[Platform::Telegram, 'social-telegram'],
[Platform::Discord, 'social-discord'],
]);
test('allQueues lists every platform publishing queue in enum order', function () {
expect(Platform::allQueues())->toBe([
'social-linkedin',
'social-linkedin-page',
'social-x',
'social-tiktok',
'social-youtube',
'social-facebook',
'social-instagram',
'social-instagram-facebook',
'social-threads',
'social-pinterest',
'social-bluesky',
'social-mastodon',
'social-telegram',
'social-discord',
])->and(Platform::allQueues())->toHaveCount(count(Platform::cases()));
});
test('platform can be disabled via config', function () {
config(['trypost.platforms.linkedin.enabled' => false]);
test('enabledQueues matches allQueues when every platform is enabled', function () {
foreach (Platform::cases() as $platform) {
config(["trypost.platforms.{$platform->value}.enabled" => true]);
}
expect(Platform::LinkedIn->isEnabled())->toBeFalse();
expect(Platform::enabledQueues())->toBe(Platform::allQueues());
});
test('disabling a platform removes only its queue from enabledQueues', function (Platform $disabled) {
foreach (Platform::cases() as $platform) {
config(["trypost.platforms.{$platform->value}.enabled" => true]);
}
config(["trypost.platforms.{$disabled->value}.enabled" => false]);
$enabledQueues = Platform::enabledQueues();
expect($enabledQueues)
->not->toContain($disabled->queue())
->toHaveCount(count(Platform::cases()) - 1);
foreach (Platform::cases() as $platform) {
if ($platform === $disabled) {
continue;
}
expect($enabledQueues)->toContain($platform->queue());
}
expect(Platform::allQueues())->toContain($disabled->queue());
})->with([
Platform::LinkedIn,
Platform::LinkedInPage,
Platform::X,
Platform::TikTok,
Platform::YouTube,
Platform::Facebook,
Platform::Instagram,
Platform::InstagramFacebook,
Platform::Threads,
Platform::Pinterest,
Platform::Bluesky,
Platform::Mastodon,
Platform::Telegram,
Platform::Discord,
]);
test('disabling every platform yields no enabled queues', function () {
foreach (Platform::cases() as $platform) {
config(["trypost.platforms.{$platform->value}.enabled" => false]);
}
expect(Platform::enabledQueues())->toBe([]);
});
test('enabledQueues is always a subset of allQueues', function () {
config([
'trypost.platforms.linkedin.enabled' => true,
'trypost.platforms.linkedin-page.enabled' => false,
'trypost.platforms.x.enabled' => false,
'trypost.platforms.tiktok.enabled' => true,
'trypost.platforms.youtube.enabled' => false,
'trypost.platforms.facebook.enabled' => true,
'trypost.platforms.instagram.enabled' => false,
'trypost.platforms.instagram-facebook.enabled' => true,
'trypost.platforms.threads.enabled' => false,
'trypost.platforms.pinterest.enabled' => true,
'trypost.platforms.bluesky.enabled' => false,
'trypost.platforms.mastodon.enabled' => true,
'trypost.platforms.telegram.enabled' => false,
'trypost.platforms.discord.enabled' => true,
]);
$enabledQueues = Platform::enabledQueues();
expect($enabledQueues)->toEqual(array_values(array_intersect(Platform::allQueues(), $enabledQueues)))
->and(array_diff($enabledQueues, Platform::allQueues()))->toBe([]);
});
test('horizon social publishing queues match enabledQueues', function () {
expect(config('horizon.defaults.social-publishing.queue'))->toBe(Platform::enabledQueues());
});
test('isEnabled falls back to env when enabled config is missing', function (Platform $platform, string $envKey) {
$platforms = config('trypost.platforms');
unset($platforms[$platform->value]['enabled']);
config(['trypost.platforms' => $platforms]);
$original = getenv($envKey);
putenv("{$envKey}=false");
try {
expect($platform->isEnabled())->toBeFalse();
} finally {
if ($original === false) {
putenv($envKey);
} else {
putenv("{$envKey}={$original}");
}
}
})->with([
[Platform::LinkedIn, 'LINKEDIN_ENABLED'],
[Platform::LinkedInPage, 'LINKEDIN_PAGE_ENABLED'],
[Platform::X, 'X_ENABLED'],
[Platform::InstagramFacebook, 'INSTAGRAM_FACEBOOK_ENABLED'],
[Platform::Telegram, 'TELEGRAM_ENABLED'],
[Platform::Discord, 'DISCORD_ENABLED'],
]);
test('linkedin pages and instagram facebook are not directly connectable', function () {
expect(Platform::LinkedInPage->isConnectable())->toBeFalse();
expect(Platform::LinkedIn->isConnectable())->toBeTrue();