Skip to content

Commit

Permalink
bug #33921 [Cache] improve perf of pruning for fs-based adapters (nic…
Browse files Browse the repository at this point in the history
…olas-grekas)

This PR was merged into the 4.4 branch.

Discussion
----------

[Cache] improve perf of pruning for fs-based adapters

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #32701
| License       | MIT
| Doc PR        | -

Thank you [Blackfire.io](https://blackfire.io)

https://blackfire.io/profiles/compare/8044c3cf-1541-4a24-9804-5670c63fcb4c/graph

![image](https://user-images.githubusercontent.com/243674/66465435-22f9fc80-ea81-11e9-83fc-2444434a33e6.png)

![image](https://user-images.githubusercontent.com/243674/66465462-2d1bfb00-ea81-11e9-827a-4c0502cb5f4a.png)

Commits
-------

0302b1d [Cache] improve perf of pruning for fs-based adapters
  • Loading branch information
nicolas-grekas committed Oct 9, 2019
2 parents 1010910 + 0302b1d commit 41dfd98
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 7 deletions.
54 changes: 52 additions & 2 deletions src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php
Expand Up @@ -25,6 +25,7 @@
class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
{
use FilesystemTrait {
doClear as private doClearCache;
doSave as private doSaveCache;
doDelete as private doDeleteCache;
}
Expand All @@ -41,6 +42,55 @@ public function __construct(string $namespace = '', int $defaultLifetime = 0, st
$this->init($namespace, $directory);
}

/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
$ok = $this->doClearCache($namespace);

if ('' !== $namespace) {
return $ok;
}

set_error_handler(static function () {});
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

try {
foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) {
if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) {
$dir = $renamed.\DIRECTORY_SEPARATOR;
} else {
$dir .= \DIRECTORY_SEPARATOR;
$renamed = null;
}

for ($i = 0; $i < 38; ++$i) {
if (!file_exists($dir.$chars[$i])) {
continue;
}
for ($j = 0; $j < 38; ++$j) {
if (!file_exists($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
continue;
}
foreach (scandir($d, SCANDIR_SORT_NONE) ?: [] as $link) {
if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) {
unlink($d.\DIRECTORY_SEPARATOR.$link);
}
}
null === $renamed ?: rmdir($d);
}
null === $renamed ?: rmdir($dir.$chars[$i]);
}
null === $renamed ?: rmdir($renamed);
}
} finally {
restore_error_handler();
}

return $ok;
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -111,13 +161,13 @@ protected function doInvalidate(array $tagIds): bool
set_error_handler(static function () {});

try {
if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -1))) {
if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) {
$tagFolder = $renamed.\DIRECTORY_SEPARATOR;
} else {
$renamed = null;
}

foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($tagFolder, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $itemLink) {
foreach ($this->scanHashDir($tagFolder) as $itemLink) {
unlink(realpath($itemLink) ?: $itemLink);
unlink($itemLink);
}
Expand Down
33 changes: 30 additions & 3 deletions src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php
Expand Up @@ -26,7 +26,7 @@ trait FilesystemCommonTrait
private function init(string $namespace, ?string $directory)
{
if (!isset($directory[0])) {
$directory = sys_get_temp_dir().'/symfony-cache';
$directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
} else {
$directory = realpath($directory) ?: $directory;
}
Expand Down Expand Up @@ -55,12 +55,12 @@ protected function doClear($namespace)
{
$ok = true;

foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) {
foreach ($this->scanHashDir($this->directory) as $file) {
if ('' !== $namespace && 0 !== strpos($this->getFileKey($file), $namespace)) {
continue;
}

$ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok;
$ok = ($this->doUnlink($file) || !file_exists($file)) && $ok;
}

return $ok;
Expand Down Expand Up @@ -123,6 +123,33 @@ private function getFileKey(string $file): string
return '';
}

private function scanHashDir(string $directory): \Generator
{
if (!file_exists($directory)) {
return;
}

$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

for ($i = 0; $i < 38; ++$i) {
if (!file_exists($directory.$chars[$i])) {
continue;
}

for ($j = 0; $j < 38; ++$j) {
if (!file_exists($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
continue;
}

foreach (@scandir($dir, SCANDIR_SORT_NONE) ?: [] as $file) {
if ('.' !== $file && '..' !== $file) {
yield $dir.\DIRECTORY_SEPARATOR.$file;
}
}
}
}
}

/**
* @internal
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Cache/Traits/FilesystemTrait.php
Expand Up @@ -33,7 +33,7 @@ public function prune()
$time = time();
$pruned = true;

foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
foreach ($this->scanHashDir($this->directory) as $file) {
if (!$h = @fopen($file, 'rb')) {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Cache/Traits/PhpFilesTrait.php
Expand Up @@ -54,7 +54,7 @@ public function prune()

set_error_handler($this->includeHandler);
try {
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
foreach ($this->scanHashDir($this->directory) as $file) {
try {
if (\is_array($expiresAt = include $file)) {
$expiresAt = $expiresAt[0];
Expand Down

0 comments on commit 41dfd98

Please sign in to comment.