Jump to content
Sign in to follow this  
toporchillo

SEO_PRO, коллизии доступа к файлам и разделяемая память

Recommended Posts

Здравствуйте, коллеги

 

В ocStore используется кэширующий ЧПУ "SEO_PRO", который при загрузке сразу же пытается брать данные из кэша:

	private $cache_data = null;

	public function __construct($registry) {
		parent::__construct($registry);
		$this->cache_data = $this->cache->get('seo_pro');
		if (!$this->cache_data) {
			$query = $this->db->query("SELECT LOWER(`keyword`) as 'keyword', `query` FROM " . DB_PREFIX . "url_alias ORDER BY url_alias_id");
			$this->cache_data = array();
			foreach ($query->rows as $row) {
				if (isset($this->cache_data['keywords'][$row['keyword']])){
					$this->cache_data['keywords'][$row['query']] = $this->cache_data['keywords'][$row['keyword']];
					continue;
				}
				$this->cache_data['keywords'][$row['keyword']] = $row['query'];
				$this->cache_data['queries'][$row['query']] = $row['keyword'];
			}
			$this->cache->set('seo_pro', $this->cache_data);
		}
	}

Вызов любого контроллера в OpenCart приводит к обращению к файлу seo_pro. Получается, что доступ к ключу 'seo_pro' самый частый из всех ключей кэша.

В логах иногда видны ошибки вида:

2014-07-10 18:19:36 - PHP Warning: file_get_contents(/home/c/chernetsov/xxxxx.ru/public_html/system/cache/cache.seo_pro.1405001710): No such file or directory in /var/www/u4858429/data/www/xxxxx.ru/system/library/cache.php on line 45

Вот, что в cache.php:

  	public function delete($key) {
		$files = glob(DIR_CACHE . 'cache.' . preg_replace('/[^A-Z0-9\._-]/i', '', $key) . '.*');
		
		if ($files) {
    			foreach ($files as $file) {
      				if (file_exists($file)) {
					unlink($file);
				}
    			}
		}
  	}

Видимо из-за того, что проверка на существование file_exists($file) и unlink($file) - не атомарные операции, параллельный процесс успевает удалить файл между file_exists($file) и unlink($file).

Я могу ошибаться, возможно есть иные причины.

 

Я предлагаю использовать разделяемую память для дополнительного кэширования seo_pro. Возможна например такая примерная реализация конструктора seo_pro:

	private $cache_data = null;
	private $shmop_id = null;

	public function __construct($registry) {
		parent::__construct($registry);
		
		$this->shmop_id = ftok(DIR_APPLICATION, 's');
		$this->shmopLoad();
		
		if (!$this->cache_data) {
			$this->cache_data = $this->cache->get('seo_pro');
			if ($this->cache_data) {
				$this->shmopSave();
			}
		}
		if (!$this->cache_data) {
			$query = $this->db->query("SELECT LOWER(`keyword`) as 'keyword', `query` FROM " . DB_PREFIX . "url_alias");
			$this->cache_data = array();
			foreach ($query->rows as $row) {
				$this->cache_data['keywords'][$row['keyword']] = $row['query'];
				$this->cache_data['queries'][$row['query']] = $row['keyword'];
			}
			$this->shmopSave();
			$this->cache->set('seo_pro', $this->cache_data);
		}
	}

	protected function shmopLoad() {
		if (!function_exists('shmop_open')) {
			return;
		}
		//Попытка открыть несуществующий блок генерирует Warning.
		//Нашел способ его прибить - отключить обработчик ошибок OpenCart
		restore_error_handler(); 
		error_reporting(E_ALL ^ E_NOTICE);
		$shid = @shmop_open($this->shmop_id, "a", 0, 0);
		if ($shid) {
			$size = shmop_size($shid);
			$this->cache_data = unserialize(shmop_read($shid, 0, $size));
			shmop_close($shid);
		}
		set_error_handler('error_handler');
	}

	protected function shmopSave() {
		if (!function_exists('shmop_open')) {
			return;
		}
		$str = serialize($this->cache_data);
		$shid = shmop_open($this->shmop_id, "c", 0666, strlen($str));
		shmop_write($shid, $str, 0);
		shmop_close($shid);
	}

Это должно уменьшить обращение к дисковому кэшу.

Минус возможно в том, что пока нет удаления блока разделяемой памяти, и при большом аптайме он может пережить кэш. Хотя тоже могу ошибаться.

 

Что вы думаете, стоит копать в этом направлении, или это экономия на спичках? Есть ли у кого тестовый магазин с ЧПУ? Проверить например с помощью Apache Benchmark (ab), так как это хорошо действует когда параллельные запросы.

 

Share this post


Link to post
Share on other sites

У себя я кеш SEO_PRO переделал. Файл кеша загружается через include. Сам файл кеша генерируется через var_export. Время жизни файла стоит около суток.

Экономия идет колосальная из-за отсутсвия unserialize.

Share this post


Link to post
Share on other sites

У себя я кеш SEO_PRO переделал. Файл кеша загружается через include. Сам файл кеша генерируется через var_export. Время жизни файла стоит около суток.

Экономия идет колосальная из-за отсутсвия unserialize.

А как протухание кэша реализовано?

Share this post


Link to post
Share on other sites

А как протухание кэша реализовано?

Классическим способом через unlink и filemtime.

Хотя здесь желательно бы применять методику которая предотвращает эффект "стаи собак". Например как реализовано здесь http://fatfreeframework.com/base#mutex

Share this post


Link to post
Share on other sites

Классическим способом через unlink.

Ошибки такого рода возникают?

2014-07-10 18:19:36 - PHP Warning: file_get_contents(/home/c/chernetsov/xxxxx.ru/public_html/system/cache/cache.seo_pro.1405001710): No such file or directory in /var/www/u4858429/data/www/xxxxx.ru/system/library/cache.php on line 45

или из-за того, что время жизни кэша большое, не успели появиться?

 

 

Копнул про serialize - действительно тормозной способ для больших массивов.

 

А вот интересно, что быстрее PHP-parse инклуда, или может простейший

foreach(explode(file_get_contents($file), "\n") as $line) {
    $row = explode($line, '\');
    $this->cache_data[$row[0]] = $row[1];
}

будет быстрее

Share this post


Link to post
Share on other sites

  	public function delete($key) {
		$files = glob(DIR_CACHE . 'cache.' . preg_replace('/[^A-Z0-9\._-]/i', '', $key) . '.*');
		
		if ($files) {
    			foreach ($files as $file) {
      				if (file_exists($file)) {
					unlink($file);
				}
    			}
		}
  	}

Видимо из-за того, что проверка на существование file_exists($file) и unlink($file) - не атомарные операции, параллельный процесс успевает удалить файл между file_exists($file) и unlink($file).

Я могу ошибаться, возможно есть иные причины.

 

 

Из кеширования сервером файловых операций. Т е  файл реально удален, а в кеше сервера еще нет.

Share this post


Link to post
Share on other sites

Ошибки такого рода возникают?

2014-07-10 18:19:36 - PHP Warning: file_get_contents(/home/c/chernetsov/xxxxx.ru/public_html/system/cache/cache.seo_pro.1405001710): No such file or directory in /var/www/u4858429/data/www/xxxxx.ru/system/library/cache.php on line 45

или из-за того, что время жизни кэша большое, не успели появиться?

 

 

Копнул про serialize - действительно тормозной способ для больших массивов.

 

А вот интересно, что быстрее PHP-parse инклуда, или может простейший

foreach(explode(file_get_contents($file), "\n") as $line) {
    $row = explode($line, '\');
    $this->cache_data[$row[0]] = $row[1];
}

будет быстрее

Include будет быстрее, поскольку у вас присутствует foreach - а это прямая зависимость скорости от количества. Да и скорость file_get_contents чуть медленнее чем сам Include. Самый быстрый способ (быстрее даже инслуда) это парсинг .ini средствами php

Share this post


Link to post
Share on other sites

Из кеширования сервером файловых операций. Т е  файл реально удален, а в кеше сервера еще нет.

Так вроде же unlink делает clearstatcache или я ошибаюсь?

Share this post


Link to post
Share on other sites

Так вроде же unlink делает clearstatcache или я ошибаюсь?

На разных серверах по разному.

Принудительно надо делать. Но и это еще не панацея... на разных серверах по разному :) Многие сервера хостеров вообще отправляют папки диска, которым часто обращаются в оперативную память. Вот поэтому и такой кавардак.

Share this post


Link to post
Share on other sites

Include будет быстрее, поскольку у вас присутствует foreach - а это прямая зависимость скорости от количества. Да и скорость file_get_contents чуть медленнее чем сам Include. Самый быстрый способ (быстрее даже инслуда) это парсинг .ini средствами php

Аргумент "поскольку у вас присутствует foreach" так себе. Вы думаете внутри php-парсера нет циклов и скорость инклуда не зависит от имени файла? А за идею с .ini спасибо.

Короче, пора браться за тесты: измерять unserialize vs explode-foreach vs parse_ini_file vs include

Share this post


Link to post
Share on other sites

У вас foreach совмещен с file_get_contents, а его скорость ниже чем инклуд.

Share this post


Link to post
Share on other sites

Даю наводку. Можно еще протестировать file, а foreach заменить на array_map, должно выйти гораздо быстрее.

Хотя у реализации с ini-файлами есть проблема в скорости генерации самого файла, в отличии от var_export.

Share this post


Link to post
Share on other sites

Вот уже кто-то протестировал http://habrahabr.ru/post/112402/

Нас интересуют случаи на 1000 элементов:

 

parse_ini_file: 0.784

include: 0.902

json_decode: 0.495

unserialize: 0.309

explode-file_get_contents: 2.213

 

Так что все-таки unserialize

Share this post


Link to post
Share on other sites

Вот уже кто-то протестировал http://habrahabr.ru/post/112402/

Нас интересуют случаи на 1000 элементов:

 

parse_ini_file: 0.784

include: 0.902

json_decode: 0.495

unserialize: 0.309

explode-file_get_contents: 2.213

 

Так что все-таки unserialize

А вы видели формат тестируемых данных. Да и масштабируемость то необходимо учесть.

100

1000

10000

100000

значений.

Да и результат вас немного не тот интересует. Не простой парсинг, а конечный ассоциированный массив с данными длиной в несколько сотен знаков.

Поверьте мне на слово. Я тестировал реальные ЧПУ. И самый быстрый способ был у ini

 

Не забываем еще и про потребление памяти и скорость генерации данных для хранения.

Share this post


Link to post
Share on other sites

А вы видели формат тестируемых данных. Да и масштабируемость то необходимо учесть.

100

1000

10000

100000

значений.

Да и результат вас немного не тот интересует. Не простой парсинг, а конечный ассоциированный массив с данными длиной в несколько сотен знаков.

Поверьте мне на слово. Я тестировал реальные ЧПУ. И самый быстрый способ был у ini

 

Не забываем еще и про потребление памяти и скорость генерации данных для хранения.

Я в курсе, что unserialize и json_decode на очень больших массивах (под миллион элементов) тормозит. В нашем случае 10000-50000 - это требуемый размер для кэша ЧПУ. >50000 товаров - это уже не для магазинов, а для любителей попарсить таобао и прочих посредников.

 

Мой выбор - ini-файлы, но не include. Хотя бы потому, что не безопасно это, когда php-файлы открыты на запись.

Share this post


Link to post
Share on other sites

храните кеш в оперативной памяти.

проблемы описанные в теме я так полагаю больше к файлу cache.php нежели seo_pro.php?

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
You are posting as a guest. If you have an account, please sign in.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×

Important Information

On our site, cookies are used and personal data is processed to improve the user interface. To find out what and what personal data we are processing, please go to the link. If you click "I agree," it means that you understand and accept all the conditions specified in this Privacy Notice.