Последние недели выдалась весьма напряженными. Как водится, в самый неподходящий момент появился баг, проживший незаметным полтора года в активно использующемся приложении. Точнее говоря, это был не баг, а своеобразная реализации выборки из БД новых данных для обработки. Построенная по принципу работы маляра Шлемиля – чем больше данных накапливается данных, тем больше времени занимает поиск необработанных частей. Самое удивительное в этом то, что сия неэффективность смогла прожить незамеченной столь долгое время. Ведь приложение неоднократно подвергалось специальному нагрузочному тестированию, а так же выдерживало нагрузку в реальной эксплуатации. Но только недавно звезды расположились так, что новые данные в БД были перемешаны со старыми именно таким образом, который и привел к резкому росту времени на их поиск.
Найти проблему помогли, в основном, два фактора:
- во-первых, это своевременное привлечение разработчиков к выяснению причин странной работы ПО. У нас работает комплекс из программных компонентов, который в совокупности стал показывать меньшую, чем обычно, производительность. А причин этого видно не было. Коллеги из техподдержки попросили меня посмотреть на происходящее. Я же, зная принципы работы частей комплекса увидел, что операция, которая должна отрабатывать за доли секунды, почему-то занимает десятки секунд. Если бы не это, то проблема могла бы остаться незамеченной и дальше;
- во-вторых, везение. Медленное выполнение одной операции на боевой БД – это хорошая диагностика, но явно недостаточная для выяснения причин. Нужно было как-то повторить такую ситуацию на тестовых стендах. И тут невероятное везение. Первый же запуск приложения на старом тестовом стенде выявил ту же самую проблему. Оказалось, что стенд использовался для проверки разных компонентов и нагрузку на него давали самую разнородную. В результате чего в тестовой БД так же образовалась нужная смесь из старых и новых данных, которая и приводила к увеличению времени на их обработку. Ну а дальше оставалось только спокойно разобраться в ситуации.
Что особенно хотелось бы подчеркнуть говоря об этой проблеме – самые серьезные ошибки в приложениях вызваны вовсе не особенностями языков программирования (чем Java/C#-программисты так любят пугать C++ников, а Haskell-исты вообще гнобят всех нефункциональщиков). А, например, просчетом разработчика при выборе алгоритма. В результате чего есть правильно написанный код, без багов, который делает все, что хотел программист. Только вот не то, что следовало бы делать.
Вторая ошибка вообще вспоминается как страшный сон. Пытаясь ускорить обработку данных в одном из приложений я вместо while(!stream.eof()) написал if(!stream.eof()). Т.е. вместо цикла по содержимому потока сообщений выполнялась обработка только первого сообщения в потоке. Приложение действительно ускорилось – ведь оно стало выполнять в сотни раз меньше действий.
Хохма в том, что модификации касались только обработки двух потоков с ответами от удаленной системы. Т.е. приложение продолжало нормально создавать нагрузку на удаленную систему, но обрабатывало только по одному ответу-подтверждению из сотни.
Причина сей ошибки банальна – модификация делалась в спешке и, поэтому, не была должным образом протестирована. Я прогнал только одиночные тесты. Затем выполнил тесты для пачек сообщений большего размера, но анализ результатов толком не провел. Тем не менее, излишняя самоуверенность и желание быстрее все закончить привели к тому, что обновленная версия ПО с ошибкой была поставлена в бой (хорошо хоть хватило здравого смысла установить ее лишь на одном из многих каналов обработки сообщений).
Обнаружить проблему удалось на следующий день. Благодаря хорошему стечению обстоятельств:
- во-первых, я таки не поленился создать для новой версии отдельное тестовое окружение чтобы продолжить начатые накануне оптимизации. И именно на новом тестовом стенде стали проявляться странности, природу которых я сразу не понял;
- во-вторых, приблизительно в это же время стали приходить жалобы из техподдержки о том, что в одном из приложений растут очереди не доставленных ответов. Что было странно, поскольку по логам было видно, что ответы до нужных получателей доходят.
Сложив всю симптоматику я понял, что дело в тех самых внесенных накануне изменениях. Версию, понятное дело, откатили, очереди стали уменьшаться. А я, как идиот, по 20 раз пересматривал куски кода с if-ами вместо while-ов и не мог понять, почему же программа работает не правильно.
Уроки, вынесенные из этого бага до неприличия банальные, но крайне актуальные, даже не смотря на весь мой опыт:
- тестирование, тестирование и еще раз тестирование. Даже если ты уверен, что изменения незначительные и все работает – проверь еще раз. Особенно, если ты уверен;
- писать нужно просто. Очень просто. Чуть ли не до дебилизма просто. Поскольку в условиях сильного напряжения, внешнего давления и жестокого цейтнота разобраться в простом коде (пусть даже объемном) еще можно. А вот в сложном, в котором каждая строчка содержит по несколько нетривиальных действий – уже навряд ли.
Ну и в завершение, чтобы не забыть, еще пару тезисов:
- что-то я стал сомневаться, что посредством каких-то абстрактных слоев (вроде ORM-библиотек) можно создавать нагруженные приложения, способные работать с СУБД от разных производителей. Скажем, которые могут работать и с MS SQL, Oracle и PostgreSQL. Под конкретную СУБД и даже под конкретную инсталляцию БД нужно делать какую-то специфическую заточку напильником. Например, по результатам трассировки активности MS SQL пришлось добавить в один из SQL-запросов опцию MAXDOP. Можно ли делать такие низкоуровневые оптимизации текстов запросов в случае ORM – я не знаю;
- online-мониторинг происходящего в приложении – это must have. Хорошо, что на заре своей карьеры мне довелось принять участие в разработке SCADA-пакета, где мне и была привита привычка к online-мониторингу. Вообще, чем больше работаю, тем более занимает меня идея о том, что для контроля над приложениями (режима 24x7 в частности) нужно применять те же самые подходы, которые уже давно используются в АСУТП. В частности, приспосабливать системы SCADA для мониторинга внутренностей приложения. Впрочем, это уже тема отдельного разговора.