Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot use 'DEL' with redis-cluster. #33733

Closed
Nowi5 opened this issue Aug 2, 2020 · 5 comments
Closed

Cannot use 'DEL' with redis-cluster. #33733

Nowi5 opened this issue Aug 2, 2020 · 5 comments

Comments

@Nowi5
Copy link

Nowi5 commented Aug 2, 2020

  • Laravel Version: laravel/framework: 7.22.4
  • predis/predis: 1.1.1 - AWS Redis CLuster with 3 nodes, 5.0.6
  • genealabs/laravel-model-caching: 0.9.0
  • PHP Version: >=7.2.5 (7.4.6)
  • Database Driver & Version:

Description:

When running migration & seed or run any other activities which will need to clear my model cache, I receive the issue that 'DEL' is not supported by my (AWS) redis-cluster.

image

Any idea where to start and how to solve it?
The only place I were able to identify 'DEL' were in https://github.com/laravel/framework/blob/7.x/src/Illuminate/Redis/Limiters/ConcurrencyLimiter.php

image

Config database.php

'redis' => [
        'client' => 'predis',
        'cluster' => env('REDIS_CLUSTER', false),
        'options' => [
            'cluster' => 'redis',
            'parameters' => [
                'password' => env('REDIS_PASSWORD', null),
                'scheme' => env('REDIS_SCHEME', 'tcp'),
                ],
            'ssl' => ['verify_peer' => false]
        ],
        'clusters' => [
            'default' => [
                [
                    'scheme'   => env('REDIS_SCHEME', 'tcp'),
                    'host' => env('REDIS_HOST', 'localhost'),
                    'password' => env('REDIS_PASSWORD', null),
                    'port' => env('REDIS_PORT', 6379),
                    'database' => env('REDIS_DATABASE', 0),
                ],
                'options' => [
                    // Clustering specific options
                    'cluster' => 'redis',
                    // This tells Redis Client lib to follow redirects (from cluster)
                ]
            ],
            // duplicate of default
            'cache' => [
                [
                    'scheme'   => env('REDIS_SCHEME', 'tcp'),
                    'host' => env('REDIS_HOST', 'localhost'),
                    'password' => env('REDIS_PASSWORD', null),
                    'port' => env('REDIS_PORT', 6379),
                    'database' => env('REDIS_DATABASE', 0),
                ],
                'options' => [
                    // Clustering specific options
                    'cluster' => 'redis',
                    // This tells Redis Client lib to follow redirects (from cluster)
                ]
            ],
        ],
    ],

Stacktrace

frame
	file nameartisan
	context-line new Symfony\Component\Console\Output\ConsoleOutput
frame
	file namekernel.php
	function Illuminate\Foundation\Console\Kernel::handle
	context-line return $this->getArtisan()->run($input, $output);
frame
	file nameapplication.php
	function Illuminate\Console\Application::run
	context-line $exitCode = parent::run($input, $output);
	[...]
frame
	file name[internal]
	function call_user_func_array
frame
	file nametenantcreate.php
	function App\Console\Commands\TenantCreate::handle
	context-line 'domain' => $this->argument('domain')
frame
	file namemodel.php
	function Illuminate\Database\Eloquent\Model::__callStatic
	context-line return (new static)->$method(...$parameters);
frame
	file namecaching.php
	function App\Models\Tenant::__call
	context-line $result = parent::__call($method, $parameters);
frame
	file namemodel.php
	function Illuminate\Database\Eloquent\Model::__call
	context-line return $this->forwardCallTo($this->newQuery(), $method, $parameters);
frame
	file nameforwardscalls.php
	function Illuminate\Database\Eloquent\Model::forwardCallTo
	context-line return $object->{$method}(...$parameters);
frame
	file namebuilder.php
	function Illuminate\Database\Eloquent\Builder::create
	context-line });
frame
	file namehelpers.php
	function tap
	context-line $callback($value);
frame
	file namebuilder.php
	function Illuminate\Database\Eloquent\Builder::Illuminate\Database\Eloquent\{closure}
	context-line $instance->save();
frame
	file namemodel.php
	function Illuminate\Database\Eloquent\Model::save
	context-line $saved = $this->performInsert($query);
frame
	file namemodel.php
	function Illuminate\Database\Eloquent\Model::performInsert
	context-line $this->fireModelEvent('created', false);
frame
	file nameextendfiremodeleventtrait.php
	function App\Models\Tenant::fireModelEvent
	context-line ->{$method}("eloquent.{$event}: " . static::class, $payload);
frame
	file namedispatcher.php
	function Illuminate\Events\Dispatcher::dispatch
	context-line $response = $listener($event, $payload);
frame
	file namedispatcher.php
	function Illuminate\Events\Dispatcher::Illuminate\Events\{closure}
	context-line return $listener(...array_values($payload));
frame
	file namemodelcaching.php
	function App\Models\Tenant::GeneaLabs\LaravelModelCaching\Traits\{closure}
	context-line $instance->checkCooldownAndFlushAfterPersisting($instance);
frame
	file namecaching.php
	function App\Models\Tenant::checkCooldownAndFlushAfterPersisting
	context-line $instance->flushCache();
frame
	file namecaching.php
	function App\Models\Tenant::flushCache
	context-line $this->cache($tags)->flush();
frame
	file nameredistaggedcache.php
	function Illuminate\Cache\RedisTaggedCache::flush
	context-line $this->deleteForeverKeys();
frame
	file nameredistaggedcache.php
	function Illuminate\Cache\RedisTaggedCache::deleteForeverKeys
	context-line $this->deleteKeysByReference(self::REFERENCE_KEY_FOREVER);
frame
	file nameredistaggedcache.php
	function Illuminate\Cache\RedisTaggedCache::deleteKeysByReference
	context-line $this->deleteValues($segment = $this->referenceKey($segment, $reference));
frame
	file nameredistaggedcache.php
	function Illuminate\Cache\RedisTaggedCache::deleteValues
	context-line call_user_func_array([$this->store->connection(), 'del'], $valuesChunk);
frame
	file name[internal]
	function call_user_func_array
frame
	file nameconnection.php
	function Illuminate\Redis\Connections\Connection::__call
	context-line return $this->command($method, $parameters);
frame
	file nameconnection.php
	function Illuminate\Redis\Connections\Connection::command
	context-line $result = $this->client->{$method}(...$parameters);
frame
	file nameclient.php
	function Predis\Client::__call
	context-line $this->createCommand($commandID, $arguments)
frame
	file nameclient.php
	function Predis\Client::executeCommand
	context-line $response = $this->connection->executeCommand($command);
frame
	file namerediscluster.php
	function Predis\Connection\Aggregate\RedisCluster::executeCommand
	context-line $response = $this->retryCommandOnFailure($command, __function __);
frame
	file namerediscluster.php
	function Predis\Connection\Aggregate\RedisCluster::retryCommandOnFailure
	context-line $response = $this->getConnection($command)->$method($command);
frame
	file namerediscluster.php
	function Predis\Connection\Aggregate\RedisCluster::getConnection
	context-line throw new NotSupportedException(
@Nowi5
Copy link
Author

Nowi5 commented Aug 2, 2020

Current bug fix; Created a own local version of RedigTaggedCache.

composer.json

    "autoload": {
        "exclude-from-classmap": [
            "vendor/laravel/framework/src/Illuminate/Cache/RedisTaggedCache.php"
        ],
        "files": [
            "vendors/laravel/framework/src/Illuminate/Cache/RedisTaggedCache.php"
        ],

vendors/laravel/framework/src/Illuminate/Cache/RedisTaggedCache.php

<?php

namespace Illuminate\Cache;


//Related to https://github.com/laravel/framework/pull/17792/files
class RedisTaggedCache extends TaggedCache
{
    /**
     * Forever reference key.
     *
     * @var string
     */
    const REFERENCE_KEY_FOREVER = 'forever_ref';
    /**
     * Standard reference key.
     *
     * @var string
     */
    const REFERENCE_KEY_STANDARD = 'standard_ref';

    /**
     * Store an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @param  \DateTimeInterface|\DateInterval|int|null  $ttl
     * @return bool
     */
    public function put($key, $value, $ttl = null)
    {
        if ($ttl === null) {
            return $this->forever($key, $value);
        }

        $this->pushStandardKeys($this->tags->getNamespace(), $key);

        return parent::put($key, $value, $ttl);
    }

    /**
     * Increment the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function increment($key, $value = 1)
    {
        $this->pushStandardKeys($this->tags->getNamespace(), $key);

        parent::increment($key, $value);
    }

    /**
     * Decrement the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function decrement($key, $value = 1)
    {
        $this->pushStandardKeys($this->tags->getNamespace(), $key);

        parent::decrement($key, $value);
    }

    /**
     * Store an item in the cache indefinitely.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return bool
     */
    public function forever($key, $value)
    {
        $this->pushForeverKeys($this->tags->getNamespace(), $key);

        return parent::forever($key, $value);
    }

    /**
     * Remove all items from the cache.
     *
     * @return bool
     */
    public function flush()
    {
        $this->deleteForeverKeys();
        $this->deleteStandardKeys();

        return parent::flush();
    }

    /**
     * Store standard key references into store.
     *
     * @param  string  $namespace
     * @param  string  $key
     * @return void
     */
    protected function pushStandardKeys($namespace, $key)
    {
        $this->pushKeys($namespace, $key, self::REFERENCE_KEY_STANDARD);
    }

    /**
     * Store forever key references into store.
     *
     * @param  string  $namespace
     * @param  string  $key
     * @return void
     */
    protected function pushForeverKeys($namespace, $key)
    {
        $this->pushKeys($namespace, $key, self::REFERENCE_KEY_FOREVER);
    }

    /**
     * Store a reference to the cache key against the reference key.
     *
     * @param  string  $namespace
     * @param  string  $key
     * @param  string  $reference
     * @return void
     */
    protected function pushKeys($namespace, $key, $reference)
    {
        //$fullKey = $this->store->getPrefix().sha1($namespace).':'.$key;
        $fullKey = $this->store->getPrefix().'{'.sha1($namespace).'}:'.$key;

        foreach (explode('|', $namespace) as $segment) {
            $this->store->connection()->sadd($this->referenceKey($segment, $reference), $fullKey);
        }
    }

    /**
     * Delete all of the items that were stored forever.
     *
     * @return void
     */
    protected function deleteForeverKeys()
    {
        $this->deleteKeysByReference(self::REFERENCE_KEY_FOREVER);
    }

    /**
     * Delete all standard items.
     *
     * @return void
     */
    protected function deleteStandardKeys()
    {
        $this->deleteKeysByReference(self::REFERENCE_KEY_STANDARD);
    }

    /**
     * Find and delete all of the items that were stored against a reference.
     *
     * @param  string  $reference
     * @return void
     */
    protected function deleteKeysByReference($reference)
    {
        foreach (explode('|', $this->tags->getNamespace()) as $segment) {
            $this->deleteValues($segment = $this->referenceKey($segment, $reference));

            $this->store->connection()->del($segment);
        }
    }

    /**
     * Delete item keys that have been stored against a reference.
     *
     * @param  string  $referenceKey
     * @return void
     */
    protected function deleteValues($referenceKey)
    {
        $values = array_unique($this->store->connection()->smembers($referenceKey));

        if (count($values) > 0) {
            foreach (array_chunk($values, 1000) as $valuesChunk) {
                call_user_func_array([$this->store->connection(), 'del'], $valuesChunk);
            }
        }
    }

    /**
     * Get the reference key for the segment.
     *
     * @param  string  $segment
     * @param  string  $suffix
     * @return string
     */
    protected function referenceKey($segment, $suffix)
    {
        return $this->store->getPrefix().$segment.':'.$suffix;
    }

    /**
     * Get a fully qualified key for a tagged item.
     *
     * @param string $key
     * @return string
     */
    public function taggedItemKey($key)
    {
        return '{'.sha1($this->tags->getNamespace()).'}:'.$key;
    }
}

See #17713 from Laravel 5.1.30 - I'm surprised that this still seems valid.
Can somebody please have a look and may create a PR?

@petar-djuric
Copy link

#17713 (comment)
#17713 (comment)

predis/predis is no longer maintained and all issues regarding it won't be solved.
The best option is to move over to phpredis/phpredis.

@GrahamCampbell
Copy link
Member

Yep, please move to ext-redis (aka phpredis). I'm sure we will be formally deprecating support for predis soon, since the project officially doesn't support PHP 7.4 or 8.0, and is no longer maintained.

@Nowi5
Copy link
Author

Nowi5 commented Aug 3, 2020

@GrahamCampbell @hyperalghorithm
Thank you for the hint. May we should adjust the documentation. By now it is written as supported:
https://laravel.com/docs/7.x/redis

Edit: Actually it turns for me out, that the Redis cluster of AWS can be configured as single instance instance of a cluster.
image

@zanderwar
Copy link

Yep, please move to ext-redis (aka phpredis). I'm sure we will be formally deprecating support for predis soon, since the project officially doesn't support PHP 7.4 or 8.0, and is no longer maintained.

no longer correct, predis supports 7.4+ and predis:^2 supports 8.1+

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants