LINUX.ORG.RU
ФорумAdmin

Медленные файловые операции PHP

 , , , ,


0

4

Приветствую!

Столкнулся с крайне непонятной ситуацией и нуждаюсь в вашей помощи или хотя-бы каких-то предположениях :)

Исходные данные: Жил себе сайт, написанный довольно давно на PHP в стилях «говнокод» и «спагетти», в чем-то неудобный в чем-то устаревший, но рабочий. (Параллельно с функционирование сайта шло, и до сих пор идет его переписывание на популярном фреймворке с использованием годных практик программировани, так что совет «выкинуть и переписать» потихоньку релизуется)

В какой-то момент сайт был перенесён на виртуальный сервер от Selectel, и стал жить там. После более года жизни на сервере, неделю назад начались симптомы болезни: - В основном все работает хорошо - Иногда, примерно каждый 10-й раз, но вообще как повезёт (иногда и 10 раз подряд) случайные страницы грузятся ОЧЕНЬ долго, иногда этот процесс заканчивается успешной загрузкой, но часто - превышается максимальный период выполнения PHP-скрипта и взоваращается пустая страница.

Система: debian squeeze, тип виртуализации неизвестен (но скорее всего что-то типа OpenVZ) - буду благодарен если подскажете как изнутри виртуалки проверить тип виртуального сервера.

Сервер: apache2, php5.3 конкретные версии не особо важны, далее объясню почему.

Что было предпринято: (извиняюсь за долгое расписывание, просто хочу чтобы сразу отпало много вопросов)

0) Нагрузочное тестирование через Apache Benchmark при определенном количестве запросов (например, 50 в секунду) приводило к тому что максимальное время запроса становилось как раз 60 секунд, и большая часть запросов не была обработана (по результатам ab)

1) Мониторинг нагрузки: во время таких «зависаний» top и htop показывали что система не особо то и загружена. 10% CPU, 40% RAM

2) Мониторинг SQL: long queries log в mysql были включены, и показали что за полдня таких запросов набарлось 2 штуки, по 2 секунды каждый.

3) Мониторинг подключений к SQL: их всего штук 10-20 набиралось от силы, с учетом ожидающих

4) Полное обновление системы (ПО не обновлялось уже год наверное) - ничего не изменилось

5) Установка apache и php из DotDeb - ничего не изменилось

6) Установка nginx + php5-fpm, настройка их на 81-м порту (для проверки) - опять же ничего не изменилось, под nginx конечно все стало работать чуть побыстрее, но тормозные страницы так и открывались тормозно! Вывод - дело не в апаче и пхп, и не в их версии,а в чем-то другом.

7) Оптимизация работы с базой путём добавления memcache - с помощью XDEBUG_PROFILE посмотрел все узкие места работы с базой, и добавил туда кэширование (где было допустимо). Страницы стали грузиться со скоростью света! Но те, которым выпадала судьба тормозить - тормозили как и раньше по 30-60 секунд.

8) Поставил xdebug_profile в ВЕЧНО-Включенное состояние, и стал анализировать самые жирные логи. И вот что выяснилось: На тех страницах, которые случайным образом начинали тормозить большую часть времени занимали файловые операции. А конкретно:

file_get_contents - считывающий файл с диска а не откуда-то из интернета по 20-60 секунд (упирается в max_execution_time)

include, require, require_once - да, по 25 секунд на каждый! А иногда и по 60!

session_start() - тоже от 20 до 60 секунд.

На счёт session_start() знаю, что можно перенести их в БД или в Мемкэш или ещё куда, и проблема решится, но зато не решатся остальные проблемы с файлами. И никаких «одновременных запросов» к сессии одного юзера не производится. На сайте нет никакого аякса, никаких фоновых работ, просто странички сгенерированные PHP.

Вся эта ситуация выглядит так, как будто доступ к файлам блокируется разными процессами, т.е. один получил доступ, другой его ждет.

9) Последнее что я предпринял - это попытался посмотреть количество доступных файловых дескрипторов (дескрипторов открытых файлов), через

# cat /proc/sys/fs/file-nr


Результат:
1376 0 6592120
то есть, как я понял, всего у меня есть 6.5 миллионов а из них использовано 1376.
Я знаю о том, что на некоторых системах пара-виртуализации можно «насильно» ограничивать какие-то параметры гостевой системы, поэтому решил проверить, каким вообще бывает это число занятых дескрипторов. Снова запустил apache benchmark, и стал с помощью watch смотреть на это число. Больше чем до 1440 оно так и не поднялось!
Последняя догадка в том что админы Selectel снизили количество открываемых файлов до такого количества (а может это админ данного сервера - если там не OpenVZ как-то умудрился это сделать), но для проверки этого у меня к сожалению не хватает опыта и знаний. (Тикет в Селектел я написал но ответа нет.)

Также, к сожалению не удалось воспользоваться утилитой iotop, которая должна показывать нагрузку дисковых операций.
Её выдача: «OSError: Netlink error: No such file or directory (2)», на просторах гугла нашел, что это значит, что, скорее всего у меня именно паравиртуализация которая не даёт делать некоторые вещи с ФС и ядром.

Может среди благородных донов на LORе найдется кто-то с подобным жизненным опытом...

Заранее спасибо за то что думали в направлении моего вопроса :)

Selectel

Система: debian squeeze, тип виртуализации неизвестен (но скорее всего что-то типа OpenVZ)

Насколько я знаю, Селектел использует Xen

Тикет в Селектел я написал но ответа нет

ИМХО, это и есть правильный способ

router ★★★★★
()

У них там случаем не SSD?

LeoK
()
Ответ на: комментарий от LeoK

Ну я к тому, что как бе... затыки у file_get_contents идут к перезаписываемым файлам?

Затыки идут при обращении с файлами которые просто статично лежат. То есть запись туда не производится... К примеру, шаблонизатор Smarty тоже временами подвисает, и именно в тот момент когда пытается загрузить файл *.tpl с помощью file_get_contents, либо когда делает require скомпилированного файла шаблона.

Насколько я знаю, Селектел использует Xen

Тогда значит они сейчас ответят «извините, это вы своими кривыми руками сервер так настроили» :) А я даже и не знаю куда копать дальше...
Сейчас выяснил что количество дескрипторов и количество открытых файлов никак не коррелируют.
С помощью

lsof | wc -l
посчитал количество открытых файлов - получилось около 3800, запустил снова ab на 50 запросов в секунду, число растёт до 6500 без проблем...

MihanEntalpo
() автор топика

На каком файле все тормозит? Что в файле, какого он размера? Сколько файлов в каталоге? Тормозит ли эта операция, выполненная из шелла?

goingUp ★★★★★
()
Ответ на: комментарий от MihanEntalpo

Я просто предположил. Сам не знаю, какие симптомы у умирающего ССД

LeoK
()
Ответ на: комментарий от goingUp

На каком файле все тормозит?

На совершенно разных файлах.
Иногда на файлах сессии (то есть вызов session_start() встаёт колом), иногда на шаблонах смарти (*.tpl)
иногда вообще на базовом скрипте mod_core.php - который вообще всегда инклюдится при каждом запуске, но ошибка проявляется случайным образом

Что в файле, какого он размера?

Файлы понятное дело разного размера, но больше, скажем, 100 килобайт там нету.
На машине с 2 GHz и 2 Gb RAM странно было бы об это спотыкаться... да и все top'ы показывают что нет особой нагрузки в такие моменты зависаний.

Сколько файлов в каталоге?

По-разному, но разумное количество, в худшем случае сотня.

Тормозит ли эта операция, выполненная из шелла?

Нет, не тормозит, во всяком случае повторить не удалось. Но она и из браузера тормозит не каждый раз, а как повезет, примерно в 10% случаев. Удалось только заметить что это происходит и в apache+mod_php и в nginx+php-fpm

MihanEntalpo
() автор топика
Ответ на: комментарий от MihanEntalpo

попробуй все таки написать скрипт быстренько cli-шный который пробегает по всему дереву в цикле и file_get_contents делает - 10 мин

LeoK
()
Ответ на: комментарий от LeoK

ну и ессно профайль его, выводи microtime там или что. дай нагрузку. не секунду его держи, а пусть полминуты там

LeoK
()
Ответ на: комментарий от LeoK

добавлю, желательно не на пыхе... можт пых что замалчивает

LeoK
()
Ответ на: комментарий от LeoK

Сделал скрипт который пока запускается из веба, создаёт 1000 файлов, потом эту 1000 файлов инклюдит, реквайрит, а также делает file_get_contents
запустил 50 потоков одновременно - и все логи профайлера одинаковые, нигде никаких превышений, всюду все отработало так как должно было.
Возможно дело действительно в конкретных файлах, а не в файлах вообще...
Попробую сделать тест именно по файлам проекта.

MihanEntalpo
() автор топика
Ответ на: комментарий от MihanEntalpo

php блокирует файл сессии, если он открыт в другом скрипте, чтобы не получилось, что он перезапишется 2 раза. Возможно, с шаблонами смарти то же самое, если они все-таки записываются.

goingUp ★★★★★
()

Посмотри в сторону сборки мусора - если в этих скриптах у тебя много всяких циклических ссылок и прочего такого то GC замедлит их работу (хотя, конечно, такого падения производительности быть не должно).

anonymous
()
Ответ на: комментарий от goingUp

php блокирует файл сессии, если он открыт в другом скрипте, чтобы не получилось, что он перезапишется 2 раза. Возможно, с шаблонами смарти то же самое, если они все-таки записываются.

Как я уже упоминал, я знаю о том что файлы сессии блокируются, и в одном проекте, где нужно было сделать без особых заморочек, чтобы в фоне запускалась долгая процедура, я просто использовал session_write_close(), чтобы не задерживать сессию.
Однако, неделю назад на этом сайте не было никаких изменений (в коде точно. В настройках сервера - возможно и были, не может же быть что это «непорочное зачатие» :) ) но с сессиями проблем также не было.

да и кстати перенеси session_handler на memcache

На самом деле, не очень хочется это делать по двум причинам: 1) В мемкэше может закончится память, и тогда он начнет убивать старые данные и занимать их новыми - в результате сессии потеряются 2) Проект сделан ужасно, в нем нет единой точки входа, есть куча php-файлов, каждый из которых работает с сессиями (в том числе и вызывает session_start()) по своей собственной логике. Соответственно, придется написать некий скрипт для подключения хэндлера, а потом лазать по всем файлам, и искать, куда же его подключить то?

Так что я до последнего буду надеяться на то, что сессии и в файлах отлично будут работать:)

Посмотри в сторону сборки мусора

Я использую профайлер, встроенный в xdebug, и в случае со сборкой мусора он скажет что столько-то времени потрачено на функцию php::gc_collect_cycles() или php_gc_something_else() и я бы точно это не пропустил.

попробуй все таки написать скрипт быстренько cli-шный который пробегает по всему дереву в цикле

Попробую написать скрипт, потестить его, в принципе даже профайлер xdebug'овый можно на него натравить... Позже напишу что из этого получилось...

MihanEntalpo
() автор топика
Ответ на: комментарий от LeoK

и так как файлы сессий как правило очень задействованы, механизм вытеснения мемкеша не будет их грохать, поверь. ну или быстренько редис подними для них. и вообще какая нагрузка у тебя, скока кликов?

LeoK
()
Ответ на: комментарий от LeoK

и вообще какая нагрузка у тебя, скока кликов?

Да нагрузка не очень большая.

В кликах не скажу, но в среднем держится около 80 чайлдов апача. При максимуме в 256.

В общем не сильная нагрузка.

На счёт сессий в мемкэше я еще подумаю. Но на самом деле лучше уж тогда сессии в мускуле, потому что при переносе этого древнего сайта и так смотришь как бы чего не отвалилось, а тут еще и «мемкэш как обязательное требование» - не хотелось бы :)

Редис... не такая там нагрузка чтобы редис ради неё поднимать. Больше разнородных компонентов системы - больше мест где может притаиться жопа :)

В любом случае, спасибо за советы.

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

MihanEntalpo
() автор топика

Была похожая проблема на masterhost'е. Скрипт создания резервной копии сайта падал на произвольном файле, но в некотором ограниченном диапазоне (все время в одной и той же директории от корня сайта). Выяснилось что на данном тарифе у masterhost ограничение на количество файловых операций... только вот не могу вспомнить - по времени или для сессии. Но факт остается фактом - вырубалось по таймауту, потому что не могло считать файл. Может быть здесь что-то подобное?

Suntechnic ★★★★★
()
Ответ на: комментарий от Suntechnic

Поскольку это выделенный сервер, не думаю что подобное возможно, однако если виртуализация OpenVZ (что еще неизвестно), то часто делают ограничения на разные количества - открытых файлов, открытых сетевых портов, и прочего. Но данная проблема возникает на совершенно произвольных файлах, в произвольные моменты...

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

MihanEntalpo
() автор топика
Ответ на: комментарий от MihanEntalpo

Выделенный сервер - это когда физическое железо 100% отдано вам все остальное это уже не выделенный.

hidden_4003
()
Ответ на: комментарий от MihanEntalpo

Была у меня похожая проблема набиралось куча процессов апача и сервер начинал внезапно скидывать память в swap и все вставало колом, хотя было полно свободной памяти, подкрутил vm.swappiness стало получше.

hidden_4003
()
Ответ на: комментарий от MihanEntalpo

Поскольку это выделенный сервер

Ты же говорил виртуалка.

А то что на произвольных файлах... А если ограничение не у тебя а у физического сервера и общее на все виртуалки? Тогда все может зависеть от соседей и проблему ты в принципе не сможешь локализовать.

Suntechnic ★★★★★
()
Ответ на: комментарий от Suntechnic

набиралось куча процессов апача и сервер начинал внезапно скидывать память в swap

У меня с памятью все более-менее нормально. Даже когда я запускаю nginx+php-frm, и делаю 200 запросов в секунду, то он съедает весь процессор, но далеко не всю память, около 800 мегов (из 2-х гигов) остаётся.

Ты же говорил виртуалка.

Да, я имел ввиду виртуалку, то есть НЕ «шаред хостинг». Почему-то у меня в голове слово «Мастерхост» ассоциируется именно с шаред хостингом (так как имел несчастье с ним работать у мастерхоста)

Выделенный сервер - имеется ввиду выделенный Виртуальный сервер.

Не представляю себе, как можно сделать ограничение на выполнение моих файловых операций на уровне виртуальной машины (хотя предполагаю что я просто могу многого не знать).

А если ограничение не у тебя а у физического сервера и общее на все виртуалки?

Да, я как раз думаю что-то подобное, но
1) Сказать об этом точно смогут только сотрудники Selectel, которые пока ответили что «вопрос передан специалистам» и все.
2) Пока не удалось подтвердить догадку с файлами путём тестирования большого количества файловых операций (чуть выше я про это писал)

Видимо попробую взять новую «голую» виртуалку и перенести всё туда. Слишком много там факторов которые я могу не учесть...
Например полудохлый ISPmanager оставшийся из прошлого :)

MihanEntalpo
() автор топика
Ответ на: комментарий от LeoK

Спасибо за советы, но у меня есть пара виртуалок в DigitalOcean, и на них меня все устраивает :)

MihanEntalpo
() автор топика
Ответ на: комментарий от Suntechnic

В ТП можно звонить по скайпу (там какие-то копейки), а плюсов больше - SSD, всякие плюшки, возможность оплаты посуточно, внутрянняя локальная сеть между ВДС-ками... Посмотрим как быстро на нём PHP файлы читает :)

MihanEntalpo
() автор топика

Приятно такие вопросы читать, спасибо.

виртуальный сервер от Selectel

Пфф.
-----
Чтобы точно понять нужное направление действий я бы слил образ, запустил бы на любой локальной машине, вплоть до рабочей (в виртуалке ессно), повесил бы альтернативное DNS имя и прогнал бы тесты.

Если бы тесты показали полностью аналогичную ситуацию, значит дело 100% в ПО/настройках. Если нет - то проблемы железа/провайдера. Рассматривать ситуацию когда виновно и то и другое пока смысла нет.

После этого даже большая квалификация не нужна - так и пишешь, ребят на другом хостере/тестовой машине ваша система работает нормально, у вас - нет. Пусть у них голова болит.

система не особо то и загружена. 10% CPU, 40% RAM

сколько ядер CPU в системе?

Есть в системе директории с большим количеством файлов?
tmp для php или session dir?

zgen ★★★★★
()
Последнее исправление: zgen (всего исправлений: 1)
Ответ на: комментарий от zgen

Чтобы точно понять нужное направление действий я бы слил образ, запустил бы на любой локальной машине, вплоть до рабочей

Спасибо за подсказку :)
Пока не уверен что у Selectel можно запросто слить образ, но попробую выяснить этот вопрос.
С другой стороны, ситуация такая, что уже не до «расследования», а надо просто чтобы всё заработало, поэтому я уже взял новую виртуалку, и сегодня скорее всего подниму сайт там.
Сразу же станет понятно, было ли дело в настройках сервера (они будут делаться с нуля) или самой системе виртуализации (обе эти проблемы по идее должны отвалиться), либо же проблема где-то в коде и окружающей его инфраструктуре...

сколько ядер CPU в системе?

Одно ядро, на 2 GHz

Есть в системе директории с большим количеством файлов?
tmp для php или session dir?

В самом сайте папок со слишком уж большим количеством файлов нет, в папке с сессиями лежит около 18 тысяч файлов, но система выдает их список практически мгновенно.
Я конечно сейчас поковыряю настройки сессий, чтобы они не плодились в таких количествах, но всё же, на моём опыте система начинала заметно тормозить при количестве файлов более 100 тысяч...

Да, и я проверил - эти все файлы сессий были созданы в последние два дня, видимо в процессе тестирования Apache Benchmark'ом.

MihanEntalpo
() автор топика
Ответ на: комментарий от MihanEntalpo

поэтому я уже взял новую виртуалку, и сегодня скорее всего подниму сайт там.

Уверен, что все заработает. Надеюсь новая виртуалка не на том же селектеле? :)

zgen ★★★★★
()
Последнее исправление: zgen (всего исправлений: 1)
Ответ на: комментарий от zgen

Надеюсь новая виртуалка не на том же селектеле? :)

Нет, DigitalOcean. С селектелом я сам раньше не работал но путешествую по интерету начитался о них разного :)

MihanEntalpo
() автор топика
Ответ на: комментарий от MihanEntalpo

Итак, свершилось!

Всем спасибо за то что напрягали свои умы в попытках помочь мне решить проблему!

Развязка истории:

После установки новой виртуалки и настройки в ней свежего nginx+php-fpm, а также битвы с сообщениями

Fatal error: Call-time pass-by-reference has been removed

удалось запустить сайт.

И сразу же стало очевидно что всё тормозит ровно точно также, как и раньше. Также как и на старом сервере.

Но на этот раз, в ходе анализа выяснилось, что торможение вызывается ТОЛЬКО вызовом file_get_contents, и ТОЛЬКО в одном файле. После чего, увидев код этого файла я пребывал в состоянии неудержимого фейспалма в течении всего оставшегося вечера.

Код был такой:

if (file_exists(DOC_ROOT."/catalog/big/" . $item['articul'] . ".jpg")) {
        $item['img_big'] = "{$item['articul']}.jpg";
      }
      elseif ($image = file_get_contents("http://{$src_host}/media/catalog/640x480/{$item['articul']}.jpg")) {
        file_put_contents(DOC_ROOT."/catalog/big/" . $item['articul'] . ".jpg", $image);
        $item['img_big'] = "{$item['articul']}.jpg";
      } else {
        $item['img_big'] = "no_photo.jpg";
      }

Код этот запускался не каждый раз, и на разных страницах, потому что на каждой странице сайта был блок со случайными товарами, и с некоторой вероятностью в нём попадались товары, у которых не было картинки в папке /catalog/big/, и которым она была нужна с другого сервера (сразу скажу, это дружественный сайт, откуда в виде CSV импортируются товары, а картинки потом загружаются таким вот извращённым методом)

Специалистам и так понятно, что происходит дальше, но для будущих последователей и пострадавших от file_get_contents объясню:

Современные веб-сервера как правило работают в режиме «несколько запросов в рамках одного соединения», поэтому при ответе на запрос, соединение не разрывают, а ждут нового запроса. При это file_get_contents ждет пока соединение будет разорвано. Соответственно, ждет он пока не случится таймаут, либо пока php-скрипт не будет убит за превышение времени выполнения.

Вот и всё!

Путем замены этой строчки на простую функцию, закачивающую файл через CURL с таймаутом в 1 секунду (хотя даже и он не требуется когда ставишь заголовок Connection: close), и сайт стал просто летать.

Почему были проблемы с require(), session_start() ? Думаю просто из-за той блокировки, которую вызывал скрипт зависая предварительно на file_get_contents. То есть пользователь открывает страницу - она висит. Пользователь её закрывает, и открывает другую, но страница то висеть не перестала, и теперь вместе с ней висит уже вторая, которая просто пыталась инициализировать сессию, или прочитать файл, который уже был открыт и еще не был закрыт.

В подробности этих блокировок я вдаваться не стал, просто заменил file_get_contents на curl_load_file и сайт на исходном селектеловском сервере стал просто летать :)

Еще раз всем спасибо, за то что участвовали в исследовании, официально признаю себя тормозом года, пошел убивать себя об стену :D

MihanEntalpo
() автор топика
Ответ на: комментарий от blackst0ne

В том и дело, что при возникновении это случалось в первую очередь на require'ах и include'ах, и только иногда на file_get_contents, да и тот - в разных местах (например Smarty его использует), так что закономерности на первый взгляд не было вообще никакой

Правда, первое что я сделал - это загуглил «file_get_contents works really slow» и конечно же нашел около 10 вопросов, связанных с file_get_contents("http://....."), надо было еще тогда насторожиться...

MihanEntalpo
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.