Начну с небольшой предыстории. Часто достаются проекты, которые сменили много разработчиков, не всегда знающих что они делали или не задумывающихся о том что делали до них. Не всегда возможно или тем более оправданно причесывать это все прямо сейчас, и приходится быстро исправлять проблемы, не взирая на общее желание выкинуть все это в трубу.
На одном таком проекте произошло странное. Приблизительно в тоже время, как были внесены небольшие исправлений по функционалу, неизвестно откуда стали возникать странные подтормаживания на генерацию страницы, чуть ли не по минуте. (виноват не тот кто все испортил, а тот кто последним трогал :) )
Расследование началось
Понимая что эти исправления не могут внести такого, начали копать. Первая мысль «это какой то странный timeout, наверное куда то удаленно стучимся». Надо было цепляться за эту идею и не отпускать её, но об этом чуть позже. Сайт был на битриксе, достаточно сложный и в нем было много разных агентов. Почти все их них отрабатывали по крону. Но мало ли что. Уж больно плавающими были подтормаживания, у разных пользователей и раз в несколько последовательных посещений (еще один тревожный звоночек, на который никто не обратил внимания).
Профилировщик битрикса показывал что дело в PHP коде ядра пролога. (как раз там иногда могут запускаться агенты). Иногда правда показывалось что проблема перемещается в код шаблона. Но так как в большинстве случаев был пролог решили начать с него. (очередная ошибка, которая могла сэкономить немного бесценного времени)
Начали копать последовательно.
- Шаг первый. Сначала откатили изменения внесенные, проблема не пропала. Вздохнули спокойно, все же не мы.
- Шаг второй. Добавили в механизм вызова агентов логирование времени работы. Никакой корреляции с подтормаживаниями, все цифры на уровне миллисекунд.
Надо бы значит замерить различные части ядра. (Вот тут бы вцепиться за дельную мысль, что зачем эти ручные замеры кода, надо бы поставить на боевой сервер XHProf и посмотреть, но было решено что и так найдется). Кусок ядра обфусцирован. Немного ловкости рук, и основные места становятся понятными, код разбит на куски, и можно проводить замеры отдельных его частей.
Область поиска сужается и вот мы находим нашего подозреваемого. Как так session_start() ???
Тучи сгущаются
Смотрим, сессии в файлах, файлы на SSD, сессий кот наплакал. Должно отрабатывать мгновенно. И вот здесь мы свернули с верного пути. Отправившись окольными путями. Мысли то логичные. Если сессия долго стартует, значит возможно проблемы с IO, на эту же идею работали задержки в шаблонах. Долго подключались файлы. htop/iotop и прочая братия показывает что проблем с вводом выводом нет. Папки с файлами летят туда сюда без каких то задержек. В логах рейда ошибок нет, прошивки у ssd последние. SSD производства Intel'а. То есть случай с Samsung упоминаемый на хабре тут никаким боком.
Пробуем перенести сессии на ram disk, безрезультатно. Проблема повторяется все чаще. Перезапуск процессов не помогает. Планируем на ночь перезагрузку физического сервера, чтобы исключить возможные проблемы, которые могли накопиться за год uptime'а, заодно встанут все обновления ядра.
Перезапуск не помог. В логах nginx встречаются странные надписи вида:
*38600646 an upstream response is buffered to a temporary file /var/cache/nginx/proxy_temp/7/29/0000006297 while reading upstream
Они совпадают по времени с известными подтормаживаниями. Похоже, проблема гораздо более распространенна, чем казалось. Подумав, приходим к мысли, что все логично, если апач по какой-то причине ждет ожидания сессии и не виснет при этом, то он должен как-то говорить nginx'у, что все нормально, и надо подождать, значит вероятно шлет пустые пакеты, которые nginx кладет в буфер. Отставляем это в сторону и идем дальше.
Обновляем сервисы. apache/php/nginx обновлены. Проблема все еще есть. Свободной памяти на сервере под 25%, крутятся настройки swap'а чтобы избежать каких-нибудь гипотетических проблем с перекидыванием процессов апач на диск. Вполне ожидаемо никаких результатов.
Переносим на memcached хранение сессий. Пробуем найти решение проблемы на stackoverflow. (Уходим все дальше от корня проблемы, параллельно подкручивая настройки софта и системы). Переносим кэш частично в memcache частично на ram диск.
Как можно догадаться толку никакого.
Я должен погрузиться глубже
Достаем тяжелую артиллерию. В очередной раз пытаемся поймать процесс с помощью команды — ps -e -o pid,comm,wchan=WIDE-WCHAN-COLUMN | grep httpd. Похоже это он — poll_schedule_timeout. Цепляемся с помощью strace.
Долгое ожидание —
restart_syscall(<... resuming interrupted call ...>)
После чего процесс оживает. Как и страница в браузере.
Видим работу с сокетами.
getsockopt(32, SOL_SOCKET, SO_ERROR, [110], [4]) = 0
fcntl(32, F_SETFL, O_RDWR) = 0
close(32) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 32
fcntl(32, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(32, F_SETFL, O_RDWR|O_NONBLOCK) = 0
connect(32, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("87.250.250.85")}, 16) = -1 EINPROGRESS (Operation
now in progress)
Вспоминаются изначальные мысли о timeout'ах.
Похоже нашли виновника. Находим по strace куда мы стучались
sendto(32, "\0\264\1\0\0\1\0\0\0\0\0\0\5vesna\6yandex\2ru\0\0\1\0"..., 33, MSG_NOSIGNAL, NULL, 0) = 33
И закрываем vesna.yandex.ru. (Какая такая vesna.yandex.ru откуда она взялась то? Я понимаю какой нибудь doorway или что то подобное, но это? )
Проблемы вдруг пропадают. Отключаем memcache и ram диск, все нормально. Значит дело именно в этом.
Возвращаемся в логи strace и ищем там причину
lstat("/home/bitrix/ext_www/XXX/bitrix/modules/defa.tools/include.php", {st_mode=S_IFREG|0775, st_size=11
43, ...}) = 0
lstat("/home/bitrix/ext_www/XXX/bitrix/modules/defa.tools", {st_mode=S_IFDIR|0775, st_size=4096, ...}) =
0
lstat("/home/bitrix/ext_www/XXX/bitrix/modules/defa.tools/classes/general/ibprop_elemcompleter.php", {st_
mode=S_IFREG|0775, st_size=15267, ...}) = 0
Defa.Tools? А при чем тут defa.tools. профилирование же явно показывало на session_start().
Свет во тьме
Лезем в код. Видим получение архива с vesna.yandex.ru для автогенерирование каких то текстов. (функционал, который никто не использовал, да собственно как и сам модуль. его поставил кто-то из предыдущих разработчиков).
Начинает вырисовываться понимание того что вероятно из-за частого обращения либо еще по каким причинам, yandex время от времени отдает этот файл с задержкой либо вообще не отдает и поэтому мы стали натыкаться на эту проблему. (почему нельзя класть получаемый с яндекса файл в кэш на сервере, и время от времени обновлять, а не хранить в экземпляре класса и постоянно его запрашивать — это вопрос к разработчикам defa.tools).
Но почему он у нам вызывается, а главный вопрос причем тут session_start(). Продолжаем копать. И находим навешанный обработчик (напоминаем, достаточно сложный проект, который писала куча народу, переходящий от разработчика к разработчику, множество обработчиков в разных файлов иногда без какой то системы) на сохранение свойства.
Находим не правильно прописанный фильтр. Проблема тормозов наконец то найдена.
Разгадка
Теперь же вернемся к главной мистике, причем тут session_start. На всех страницах для авторизованных пользователей (в виду тематики и идеи сайта большинство пользователей авторизовано) присутствует вызов скрипта проверки обновлений ajax'ом. (websocket'ы? нет не слышал). Браузер отделяет зерна от плевел, поэтому позволяет открывать новую вкладку в момент выполнения ajax-запроса. Так как сессия там начата, но еще не законченна, session_start послушно ждет когда же ему дадут начать. И это никак не связано с io и прочим шаманством.
Какие же можно сделать выводы в итоге:
- Наследие старого кода всегда подкрадывается незаметно, и больно бъет по голове.
- Доверяйте своей интуиции. (Мысль об обращение к внешнему ресурсу оказалась верной. А шаманства с установкой и настрйкой XHProf позволили бы заметить что есть другие длительные задержки. И соответственно быстрее понять суть проблемы.)
- Выкатывайте тяжелую артиллерию пораньше.
- Вероятней всего проблема гораздо проще чем кажется.
Комментарии (19)
bat
01.10.2015 19:25-2после обнаружения долгого session_start при хранении сессий в файлах, надо искать долгоиграющий параллельный запрос от того же пользователя, очевидно же
tkf
01.10.2015 19:32+1что интересно, хранение сессии в memcached проблему не решало. К тому же сыграло роль то что похожая проблема была относительно недавно на другом сервере, и там в итоге виноваты оказались винты, которые пришлось заменять. Вот мысль и ушла не в том направление :(
inkvizitor68sl
01.10.2015 19:31+1> Цепляемся с помощью strace.
Всегда делаю это сразу после того, как посмотрю в логи. Если нагрузка большая, то сначала смотрю в tcpdump на исходящие коннекты к 80/443, потом уже иду к владельцу проекта с просьбой положить всё это дело минут на 10, чтобы снять strace с тормозящих запросов.
Ни разу не подводило, хотя бы косвенно, но проблема всегда находилась сразу после прочтения одного трейса тормозящего запроса.
Чего и вам советую )tkf
01.10.2015 19:34+1тут немного мешало то что было сложно поймать какой именно процесс обрабатывает тормозящий запрос. Потому что тормоза наблюдались в одном процессе. И strace тоже бы привел к ожиданию сессии, хотя другой процесс в этот момент и подвисал :(
Но да, strace надо было использовать раньше. Сэкономило бы время. Как я и написал в итоге.inkvizitor68sl
01.10.2015 19:38+2Можно на пару минут запустить strace на все процессы апача, записать всё в один лог, потом по логу nginx найти тех, кто ушел с 499 (или 502 с 60 секунд в вашем случае, скорее всего, или 200-кой с адским временем ответа), потом грепом найти нужный трейс.
> тоже бы привел к ожиданию сессии
Ну там в 3-4 раза процесс медленнее начинает работать, до 60 секунд не дошло бы.
А ещё можно сразу писать с -r и искать те сисколлы, которые выполняются несколько секунд (в вашем случаем можно было бы сразу 50+ секунд искать), так даже быстрее (но это работает только в случаях, когда нужно найти сильные тормоза, на тормозах меньше секунды так сложнее искать).
Nastradamus
02.10.2015 01:35У меня был такой же сложный сайт на битриксе. Подобные проблемы, действительно, быстро решались с помощью XHProf (XHGui лучше сразу ставить).
Иначе, можно искать тормоза месяцами.
TimsTims
02.10.2015 12:59Спасибо за интересную статью!
Раскрылись глаза на такую казалось мелочь — session_start() и блокировки файлов сессий!
А я всё понять не мог, почему на нашем небольшом бэкэнде невозможно открытие на загрузку одновременно нескольких страниц, а выдавал только по одной… думал ограничения в браузере какое-то, или веб-сервер на одного клиента не дает больше процессов выполнять… а оказалось это просто блокировки файлов от session_start!jrip
02.10.2015 14:23Тут стоить заметить, что это совсем не мелочь и, что в официальной доке по start_session про блокировки сказано, а вот в разных «учебниках» обычно нет.
TimsTims
02.10.2015 17:29Да что-то совсем про это ничего не сказанно: http://php.net/session_start
jrip
02.10.2015 18:11Грусть, однако, в переводе нет, а вам похоже по умолчанию как раз перевод отдается.
Я то специально вот сюда смотрел, тут есть, в блоке options:
php.net/en/session_start
jrip
02.10.2015 18:22Собственно, информация там о блокировка есть в базовом описании работы с сессиями, например вот тут даже в переводе:
php.net/manual/ru/session.examples.basic.php
Процитирую оттуда:
Замечание:
Сессии, использующие файлы (по умолчанию в PHP), блокируют файл сессии сразу при открытии сессии функцией session_start() или косвенно при указании session.auto_start. После блокировки, ни один другой скрипт не может получить доступ к этому же файлу сессии, пока он не будет закрыт или при завершении скрипта или при вызове функции session_write_close().
Скорее всего это станет проблемой для сайтов, которые активно используют AJAX и делают несколько одновременных запросов. Простейшим путем решить эту проблему будет вызов функции session_write_close() сразу же как только все требуемые изменения в сессии будут сделаны, предпочтительно ближе к началу работы скрипта. Также можно использовать другой механизм сессии, который поддерживает конкурентный доступ.
jrip
Так и в чем мораль? не юзать bitrix?
tkf
а почему не юзать? в нем конечно полно своих проблем. Но это как и в любой другой CMS.
суть в том что проблемы могут подкрасться откуда не ждали, и иногда стоит довериться первым мыслям чем устраивать долгий и непонятный квест :)
Хотя в результате приходим к тому же. время только тратится.
jrip
Ну тогда странная мораль как бы.
Т.е. у нас есть мысли, мы им не доверям, потому что они первые, но иногда им нужно довериться.