diff --git a/app/app/Jobs/Autopodbor/RunAutopodborResolveJob.php b/app/app/Jobs/Autopodbor/RunAutopodborResolveJob.php index f8e36c9f..eb6d0ff5 100644 --- a/app/app/Jobs/Autopodbor/RunAutopodborResolveJob.php +++ b/app/app/Jobs/Autopodbor/RunAutopodborResolveJob.php @@ -27,8 +27,8 @@ class RunAutopodborResolveJob implements ShouldQueue public function __construct(public int $runId) { - // Та же выделенная очередь, что и поиск — единый глобальный потолок внешних сервисов. - $this->onQueue('autopodbor'); + // Резолв по имени — тоже шаг 1 (ИИ-поиск кандидатов), поэтому на одной очереди с поиском. + $this->onQueue('autopodbor-find'); } /** diff --git a/app/app/Jobs/Autopodbor/RunAutopodborSearchJob.php b/app/app/Jobs/Autopodbor/RunAutopodborSearchJob.php index 260c0d9e..bd4454aa 100644 --- a/app/app/Jobs/Autopodbor/RunAutopodborSearchJob.php +++ b/app/app/Jobs/Autopodbor/RunAutopodborSearchJob.php @@ -40,9 +40,9 @@ class RunAutopodborSearchJob implements ShouldQueue public function __construct(public int $runId) { - // Выделенная очередь: число её воркеров = глобальный потолок одновременных подборов - // (лимиты EXA/xfetch/AITUNNEL — на КЛЮЧ, общий на всех клиентов). - $this->onQueue('autopodbor'); + // Шаг 1 «собрать поле» (ИИ + внешние сервисы) — своя очередь, отдельный воркер-процесс, + // чтобы тяжёлый поиск не стоял в одной линии с быстрым шагом 2 (изучением) другого клиента. + $this->onQueue('autopodbor-find'); } /** diff --git a/app/app/Jobs/Autopodbor/RunAutopodborStudyJob.php b/app/app/Jobs/Autopodbor/RunAutopodborStudyJob.php index 478bc034..dce65c1f 100644 --- a/app/app/Jobs/Autopodbor/RunAutopodborStudyJob.php +++ b/app/app/Jobs/Autopodbor/RunAutopodborStudyJob.php @@ -32,8 +32,9 @@ class RunAutopodborStudyJob implements ShouldQueue public function __construct(public int $runId) { - // Та же выделенная очередь, что и поиск — единый глобальный потолок внешних сервисов. - $this->onQueue('autopodbor'); + // Шаг 2 «изучение / дайте источники» — тупой слой (без ИИ). ОТДЕЛЬНАЯ очередь + свой воркер, + // чтобы изучение шло параллельно поиску, а не ждало его в общей линии. + $this->onQueue('autopodbor-study'); } /** diff --git a/app/tests/Feature/Autopodbor/AutopodborQueueThrottleTest.php b/app/tests/Feature/Autopodbor/AutopodborQueueThrottleTest.php index b787e079..6c138160 100644 --- a/app/tests/Feature/Autopodbor/AutopodborQueueThrottleTest.php +++ b/app/tests/Feature/Autopodbor/AutopodborQueueThrottleTest.php @@ -8,33 +8,39 @@ use App\Jobs\Autopodbor\RunAutopodborStudyJob; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Support\Facades\Queue; -// Масштаб: лимиты внешних сервисов — на КЛЮЧ (общий на всех клиентов). Джобы автоподбора идут на -// ВЫДЕЛЕННУЮ очередь autopodbor — число её воркеров = глобальный потолок одновременных подборов. +// Масштаб: два шага разнесены по РАЗНЫМ очередям (разные процессы-воркеры), чтобы не толкались. +// Шаг 1 «собрать поле» с ИИ (поиск + резолв по имени) → очередь autopodbor-find. +// Шаг 2 «изучение / дайте источники» — тупой слой → очередь autopodbor-study. +// Так быстрый шаг 2 одного клиента не стоит за тяжёлым шагом 1 другого. // Плюс защита от нахлёста (WithoutOverlapping) на случай ретрая/повторного dispatch. -it('поиск уходит на выделенную очередь autopodbor', function () { +it('шаг 1 (поиск) уходит на очередь autopodbor-find', function () { Queue::fake(); RunAutopodborSearchJob::dispatch(123); - Queue::assertPushedOn('autopodbor', RunAutopodborSearchJob::class); + Queue::assertPushedOn('autopodbor-find', RunAutopodborSearchJob::class); }); -it('поиск защищён от нахлёста — middleware WithoutOverlapping по runId', function () { - $mw = (new RunAutopodborSearchJob(123))->middleware(); - - expect($mw)->toHaveCount(1) - ->and($mw[0])->toBeInstanceOf(WithoutOverlapping::class); -}); - -it('шаг 2 (изучение) и резолв — на той же очереди и защищены от нахлёста', function () { +it('резолв по имени — тоже шаг 1, на очереди autopodbor-find', function () { Queue::fake(); - RunAutopodborStudyJob::dispatch(7); + RunAutopodborResolveJob::dispatch(8); - Queue::assertPushedOn('autopodbor', RunAutopodborStudyJob::class); - Queue::assertPushedOn('autopodbor', RunAutopodborResolveJob::class); + Queue::assertPushedOn('autopodbor-find', RunAutopodborResolveJob::class); +}); - expect((new RunAutopodborStudyJob(7))->middleware()[0])->toBeInstanceOf(WithoutOverlapping::class) +it('шаг 2 (изучение) уходит на ОТДЕЛЬНУЮ очередь autopodbor-study', function () { + Queue::fake(); + + RunAutopodborStudyJob::dispatch(7); + + Queue::assertPushedOn('autopodbor-study', RunAutopodborStudyJob::class); +}); + +it('все три задания защищены от нахлёста — middleware WithoutOverlapping по runId', function () { + expect((new RunAutopodborSearchJob(123))->middleware())->toHaveCount(1) + ->and((new RunAutopodborSearchJob(123))->middleware()[0])->toBeInstanceOf(WithoutOverlapping::class) + ->and((new RunAutopodborStudyJob(7))->middleware()[0])->toBeInstanceOf(WithoutOverlapping::class) ->and((new RunAutopodborResolveJob(8))->middleware()[0])->toBeInstanceOf(WithoutOverlapping::class); });