toporchillo

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

Рекомендуемые сообщения

toporchillo    437

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

 

В 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), так как это хорошо действует когда параллельные запросы.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
Vladzimir    115

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
toporchillo    437

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
Vladzimir    115

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
toporchillo    437

Классическим способом через 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];
}

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 494

  	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).

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

 

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
Vladzimir    115

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
Vladzimir    115

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
markimax    1 494

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
toporchillo    437

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
Vladzimir    115

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
Vladzimir    115

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
toporchillo    437

Вот уже кто-то протестировал 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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
Vladzimir    115

Вот уже кто-то протестировал 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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
toporchillo    437

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

100

1000

10000

100000

значений.

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

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

 

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

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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
freelancer    1 412

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
toporchillo    437

Спасибо, кэп.

Первое сообщение не читаем?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти


  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу