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