Мы привыкли видеть истории проектов с миллионами клиентов и целым парком серверов, но разные интересные и курьезные ситуации могут встречаются и на относительно небольших проектах. Одна из них произошла недавно. Однажды работающий нормально сервер вдруг начал случайным образом тормозить…



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

Расследование началось


Понимая что эти исправления не могут внести такого, начали копать. Первая мысль «это какой то странный 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)


  1. jrip
    01.10.2015 17:33
    +17

    Так и в чем мораль? не юзать bitrix?


    1. tkf
      01.10.2015 17:47

      а почему не юзать? в нем конечно полно своих проблем. Но это как и в любой другой CMS.
      суть в том что проблемы могут подкрасться откуда не ждали, и иногда стоит довериться первым мыслям чем устраивать долгий и непонятный квест :)
      Хотя в результате приходим к тому же. время только тратится.


      1. jrip
        01.10.2015 18:28
        +1

        Ну тогда странная мораль как бы.
        Т.е. у нас есть мысли, мы им не доверям, потому что они первые, но иногда им нужно довериться.


  1. tattoor
    01.10.2015 17:39
    +6

    Это конечно увлекательно так искать проблемы, но если бы у вас стоял newrelic хватило бы одного взгляда.


    1. tkf
      01.10.2015 17:49

      Не знаю что у newrelic'а есть такая удобная интеграция с php. Надо будет попробовать как нибудь посмотреть на этого зверя. Спасибо.


  1. bat
    01.10.2015 19:25
    -2

    после обнаружения долгого session_start при хранении сессий в файлах, надо искать долгоиграющий параллельный запрос от того же пользователя, очевидно же


    1. tkf
      01.10.2015 19:32
      +1

      что интересно, хранение сессии в memcached проблему не решало. К тому же сыграло роль то что похожая проблема была относительно недавно на другом сервере, и там в итоге виноваты оказались винты, которые пришлось заменять. Вот мысль и ушла не в том направление :(


    1. jrip
      01.10.2015 19:42
      +2

      Так не важно же файлы или не файлы в этом случае.
      И вообще по факту session_start тут только усугубляет проблему производительности и ни о чем не говорит.


      1. tkf
        01.10.2015 20:02
        +1

        именно уткнувшись в него, пошли по неверному пути, подменив причину следствием.


  1. inkvizitor68sl
    01.10.2015 19:31
    +1

    > Цепляемся с помощью strace.
    Всегда делаю это сразу после того, как посмотрю в логи. Если нагрузка большая, то сначала смотрю в tcpdump на исходящие коннекты к 80/443, потом уже иду к владельцу проекта с просьбой положить всё это дело минут на 10, чтобы снять strace с тормозящих запросов.
    Ни разу не подводило, хотя бы косвенно, но проблема всегда находилась сразу после прочтения одного трейса тормозящего запроса.

    Чего и вам советую )


    1. tkf
      01.10.2015 19:34
      +1

      тут немного мешало то что было сложно поймать какой именно процесс обрабатывает тормозящий запрос. Потому что тормоза наблюдались в одном процессе. И strace тоже бы привел к ожиданию сессии, хотя другой процесс в этот момент и подвисал :(
      Но да, strace надо было использовать раньше. Сэкономило бы время. Как я и написал в итоге.


      1. inkvizitor68sl
        01.10.2015 19:38
        +2

        Можно на пару минут запустить strace на все процессы апача, записать всё в один лог, потом по логу nginx найти тех, кто ушел с 499 (или 502 с 60 секунд в вашем случае, скорее всего, или 200-кой с адским временем ответа), потом грепом найти нужный трейс.

        > тоже бы привел к ожиданию сессии
        Ну там в 3-4 раза процесс медленнее начинает работать, до 60 секунд не дошло бы.

        А ещё можно сразу писать с -r и искать те сисколлы, которые выполняются несколько секунд (в вашем случаем можно было бы сразу 50+ секунд искать), так даже быстрее (но это работает только в случаях, когда нужно найти сильные тормоза, на тормозах меньше секунды так сложнее искать).


  1. Nastradamus
    02.10.2015 01:35

    У меня был такой же сложный сайт на битриксе. Подобные проблемы, действительно, быстро решались с помощью XHProf (XHGui лучше сразу ставить).
    Иначе, можно искать тормоза месяцами.


  1. TimsTims
    02.10.2015 12:59

    Спасибо за интересную статью!
    Раскрылись глаза на такую казалось мелочь — session_start() и блокировки файлов сессий!

    А я всё понять не мог, почему на нашем небольшом бэкэнде невозможно открытие на загрузку одновременно нескольких страниц, а выдавал только по одной… думал ограничения в браузере какое-то, или веб-сервер на одного клиента не дает больше процессов выполнять… а оказалось это просто блокировки файлов от session_start!


    1. jrip
      02.10.2015 14:23

      Тут стоить заметить, что это совсем не мелочь и, что в официальной доке по start_session про блокировки сказано, а вот в разных «учебниках» обычно нет.


      1. TimsTims
        02.10.2015 17:29

        Да что-то совсем про это ничего не сказанно: http://php.net/session_start


        1. jrip
          02.10.2015 18:11

          Грусть, однако, в переводе нет, а вам похоже по умолчанию как раз перевод отдается.

          Я то специально вот сюда смотрел, тут есть, в блоке options:
          php.net/en/session_start


        1. 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() сразу же как только все требуемые изменения в сессии будут сделаны, предпочтительно ближе к началу работы скрипта. Также можно использовать другой механизм сессии, который поддерживает конкурентный доступ.


  1. nelson
    03.10.2015 16:32

    Блокировка сессии — это перебдение в 99% проектов.