Queuety
Workflows

Parallel Steps

Sometimes multiple steps can run at the same time. The ->parallel() method dispatches a group of handlers concurrently. The workflow only advances when all parallel handlers have completed.

The ->parallel() method

use Queuety\Queuety;

Queuety::workflow( 'enrich_profile' )
    ->then( FetchUserHandler::class )
    ->parallel( [
        FetchSocialProfileHandler::class,
        FetchPaymentHistoryHandler::class,
        FetchActivityLogHandler::class,
    ] )
    ->then( MergeAndSaveHandler::class )
    ->dispatch( [ 'user_id' => 42 ] );

In this example:

  1. FetchUserHandler runs first (sequential)
  2. Three handlers run concurrently (parallel)
  3. MergeAndSaveHandler runs after all three complete (sequential)

How it works

When a parallel step is reached, Queuety dispatches one job per handler in the group. Each job receives the current workflow state and runs independently. The workflow tracks how many parallel jobs are outstanding and only advances to the next step once all have completed.

Each parallel handler's return value is merged into the workflow state. If multiple handlers return the same key, the last one to complete wins.

State merging

Each parallel handler returns data that gets merged into the shared workflow state:

class FetchSocialProfileHandler implements Step {
    public function handle( array $state ): array {
        $profile = fetch_social_api( $state['user_id'] );
        return [ 'social_profile' => $profile ];
    }

    public function config(): array {
        return [];
    }
}

class FetchPaymentHistoryHandler implements Step {
    public function handle( array $state ): array {
        $payments = fetch_payment_api( $state['user_id'] );
        return [ 'payment_history' => $payments ];
    }

    public function config(): array {
        return [];
    }
}

After the parallel step completes, the state contains both social_profile and payment_history, and the next sequential step can access all of it.

Mixing sequential and parallel

You can freely mix ->then() and ->parallel() calls:

Queuety::workflow( 'complex_pipeline' )
    ->then( SetupHandler::class )               // sequential
    ->parallel( [ TaskA::class, TaskB::class ] ) // parallel
    ->then( IntermediateHandler::class )         // sequential
    ->parallel( [ TaskC::class, TaskD::class ] ) // parallel
    ->then( FinalizeHandler::class )             // sequential
    ->dispatch( $payload );

Error handling

If any handler in a parallel group fails, the workflow follows the normal retry logic for that handler. Other parallel handlers that have already completed are not re-run. Once the failed handler succeeds (or is buried), the workflow proceeds.

On this page