diff --git a/Console/Command/CacheEvict.php b/Console/Command/CacheEvict.php new file mode 100644 index 0000000..1db4911 --- /dev/null +++ b/Console/Command/CacheEvict.php @@ -0,0 +1,57 @@ +evictor = $evictor; + + parent::__construct('cache:evict'); + } + + /** + * @inheritDoc + */ + protected function configure() + { + $this->setDescription('Evicts unused keys by performing scan command'); + } + + /** + * @inheritDoc + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Begin scanning of cache keys'); + + $count = $this->evictor->evict(); + + $output->writeln(sprintf( + 'Total scanned keys: %s', + $count + )); + } +} diff --git a/Cron/Evict.php b/Cron/Evict.php new file mode 100644 index 0000000..389150d --- /dev/null +++ b/Cron/Evict.php @@ -0,0 +1,59 @@ +evictor = $evictor; + $this->deploymentConfig = $deploymentConfig; + $this->logger = $logger; + } + + /** + * Perform keys eviction. + */ + public function execute() + { + if (!$this->deploymentConfig->get(Evictor::CONFIG_PATH_ENABLED)) { + $this->logger->info('Keys eviction is disabled'); + + return; + } + + $this->evictor->evict(); + } +} diff --git a/Model/Cache/Evictor.php b/Model/Cache/Evictor.php new file mode 100644 index 0000000..0b0327a --- /dev/null +++ b/Model/Cache/Evictor.php @@ -0,0 +1,120 @@ +deploymentConfig = $deploymentConfig; + $this->logger = $logger; + } + + /** + * Evicts all keys using iterator. + * + * @return int + */ + public function evict(): int + { + $options = $this->deploymentConfig->getConfigData(FrontendPool::KEY_CACHE)[FrontendPool::KEY_FRONTEND_CACHE] + ?? []; + $evictedKeys = 0; + + foreach ($options as $name => $cacheConfig) { + $this->logger->info(sprintf( + 'Scanning keys for "%s" database', + $name + )); + + if (!isset( + $cacheConfig['backend_options']['server'], + $cacheConfig['backend_options']['port'], + $cacheConfig['backend_options']['database'] + )) { + $this->logger->debug(sprintf( + 'Cache config for database "%s" config is not valid', + $name + )); + + continue; + } + + $dbKeys = $this->run( + $cacheConfig['backend_options']['server'], + $cacheConfig['backend_options']['port'], + $cacheConfig['backend_options']['database'] + ); + $evictedKeys += $dbKeys; + + $this->logger->info(sprintf('Keys scanned: %s', $dbKeys)); + } + + return $evictedKeys; + } + + /** + * @param string $host + * @param int $port + * @param int $db + * @return int + */ + private function run(string $host, int $port, int $db): int + { + $client = new Client($host, $port, null, '', $db); + $evictedKeys = 0; + + do { + $keys = $client->scan( + $iterator, + Backend::PREFIX_KEY . '*', + (int)$this->deploymentConfig->get(self::CONFIG_PATH_LIMIT, self::DEFAULT_EVICTION_LIMIT) + ); + + if ($keys === false) { + $this->logger->debug('Reached end'); + } else { + $keysCount = count($keys); + $evictedKeys += $keysCount; + } + + /* Give Redis some time to handle other requests */ + usleep(self::DEFAULT_SLEEP_TIMEOUT); + } while ($iterator > 0); + + return $evictedKeys; + } +} diff --git a/composer.json b/composer.json index 5c6c6ec..616eeb0 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,8 @@ "require": { "php": "^7.0", "ext-json": "*", + "colinmollenhour/cache-backend-redis": "^1.9", + "colinmollenhour/credis": "^1.6", "newrelic/monolog-enricher": "^1.0" }, "suggest": { @@ -21,6 +23,9 @@ "phpunit/phpunit": "^6.2", "squizlabs/php_codesniffer": "^3.0" }, + "config": { + "sort-packages": true + }, "scripts": { "test": [ "@phpstan", diff --git a/etc/crontab.xml b/etc/crontab.xml new file mode 100644 index 0000000..878e9bd --- /dev/null +++ b/etc/crontab.xml @@ -0,0 +1,14 @@ + + + + + + 0 */12 * * * + + + diff --git a/etc/di.xml b/etc/di.xml index 2c931c6..3d69451 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -12,6 +12,7 @@ Magento\CloudComponents\Console\Command\ConfigShowStoreUrlCommand Magento\CloudComponents\Console\Command\ConfigShowEntityUrlsCommand Magento\CloudComponents\Console\Command\ConfigShowDefaultUrlCommand + Magento\CloudComponents\Console\Command\CacheEvict