When you are using Horizon, caching and session together with a Redis store in Laravel, you might run into the same issue as we have.

We are running our Laravel application on multiple servers.

Every night, a couple of tasks need to be executed. To ensure they are only executed once, we apply the following pattern in the console kernel:

$schedule->command(ApiUsageCommand::class)
    ->onOneServer() // Only run on a single server
    ->dailyAt('04:00')
    ->skip(function () {
        return Carbon::now()->isWeekend();
    });

We however noticed that this wasn't flawless and sometimes, the task does get executed more than once.

After doing some research, I found a discussion on GitHub that might explain some things.

We were using the same Redis instance and database for caching, session data and Horizon. Turns out that wasn't the best approach. You will end up with strange things such as clearing the cache deleting the session data or Horizon data which you definitely don't want.

To avoid this, we first changed the settings for Redis in the config/database.php file by creating a separate connection for the session data, cache and Horizon. The trick here is to give each one of them a distinct database number.

return [

    /*
    |--------------------------------------------------------------------------
    | Redis Databases
    |--------------------------------------------------------------------------
    |
    | Redis is an open source, fast, and advanced key-value store that also
    | provides a richer set of commands than a typical key-value systems
    | such as APC or Memcached. Laravel makes it easy to dig right in.
    |
    */

    'redis' => [

        'client' => env('REDIS_CLIENT', 'phpredis'),

        'default' => [
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => env('REDIS_DATABASE_DEFAULT', 0),
        ],

        'session' => [
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => env('REDIS_DATABASE_SESSION', 1),
        ],

        'cache' => [
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => env('REDIS_DATABASE_CACHE', 2),
        ],
    ],

];

We then updated config/session.php with this new info:

return [
    /*
    |--------------------------------------------------------------------------
    | Session Database Connection
    |--------------------------------------------------------------------------
    |
    | When using the "database" or "redis" session drivers, you may specify a
    | connection that should be used to manage these sessions. This should
    | correspond to a connection in your database configuration options.
    |
    */

    'connection' => env('SESSION_CONNECTION', 'session'),
];

Then, we also updated config/cache.php:

return [

    /*
    |--------------------------------------------------------------------------
    | Cache Stores
    |--------------------------------------------------------------------------
    |
    | Here you may define all of the cache "stores" for your application as
    | well as their drivers. You may even define multiple stores for the
    | same cache driver to group types of items stored in your caches.
    |
    */

    'stores' => [

        'redis' => [
            'driver' => 'redis',
            'connection' => 'cache', // This was changed
        ],

    ],
]

For the sake of simplicity, we kept the default connection for Horizon.

It seems that this fixed the problem.