Пример использования guppy/heapy

Posted by Андрей on Август 24, 2011

Guppy — классный профилировщик памяти для Python. К сожалению, им довольно сложно пользоваться, а документация оставляет желать лучшего. Один из разработчиков pkgcore написал отличную статью об использовании Guppy, которая располагалась по адресу: http://www.pkgcore.org/trac/pkgcore/doc/dev-notes/heapy.rst. Статья больше недоступна, я нашел исходник на bitbucket и превратил в PDF/HTML для простоты использования:

HighLoad-2010: Приемы разработки высоконагруженных приложений на Twisted/Python 2

Posted by Андрей on Октябрь 28, 2010

25-26 октября состоялся HighLoad-2010, конференция получилось хорошей хотя бы потому, что было мало докладов ни о чем. Неплохой уровень, особенно было приятно увидеть «профессоров» PostgreSQL.

Я выступал с докладом «Приемы разработки высоконагруженных приложений на Twisted/Python». В докладе получилась (вполне сознательно) сборная солянка из советов и приемов о том, как писать приложения на Twisted (и похожих frameworkах). Из-за большого количества разных тем не получилось углубиться ни в одну, каюсь…

Тезисы:

  1. Запуск и шедулинг многих однопоточных процессов на одном сервере.
  2. Key-value storage и приемы работы с ним.
  3. Обслуживание сотен тысяч соединений на одном сервере.
  4. HTTP-сервисы и балансировка нагрузки, локализация нагрузки.
  5. Сбор статистики, интеграция с системой мониторинга.
  6. Шина обмена сообщениями на примере AMQP.
  7. Поиск и устранение memory leak.
  8. Оптимизация по времени отклика и пропускной способности.
  9. Мифы и правда о Python как языке разработки нагруженных приложений.

Презентация:



Утащить:

  • В формате PDF
  • Модуль py-numа, который упоминался в докладе

Профайлинг Twisted-приложений 2

Posted by Андрей on Февраль 15, 2010

Часто сам забываю, как профилировать легко и быстро Twisted-приложения (с некоторым изменениями подойдет для любых Python-приложений). Кроме Twisted нам понадобится еще KCachegrind

Запускаем наше приложение с включенным профайлингом:

twistd -n --savestats --profile=myprog.hotshot myprog

Подаем нагрузку, профайл собирается. Теперь с помощью утилиты hotshot2cg из поставки KCachegrind превращаем hotshot-профайл в calltree-профайл, который уже умеет KCachegrind «кушать».

hotshot2cg myprog.hotshot > myprog.calltree

Запускаем KCachegrind, открываем в нем полученный профайл:

kcachegrind myprog.calltree

HL++ (2009): Twisted Framework 17

Posted by Андрей on Октябрь 13, 2009

Сегодня выступал на HighLoad++ с докладом Twisted Framework — фреймворк для написания сетевых приложений в Python.

Введение

Последнее время в области web происходит смещение внимания с тяжелых application-серверов, которые тратят на обработку запроса сотни миллисекунд, а то и секунды, к более легковесным сервисам, передающим меньшие объемы данных с минимальной задержкой. Переход от генерации десятков и сотен килобайт HTML-кода в ответ на запрос к передаче изменений в данных, запакованных в JSON и измеряемых сотнями байт. В качестве примеров таких сервисов можно привести Gmail, FriendFeed, Twitter Live Search и т.п.

Для обеспечения минимальной задержки для пользователя необходимо либо поддерживать постоянное соединение (например, Adobe Flash, RTMP) или использовать технику HTTP long polling в сочетании с keep alive. Так или иначе на стороне сервера это приводит к появлению большого количества одновременных соединений (тысячи, десятки тысяч), по каждому из которых передается не такой большой объем данных. Эту ситуацию называют обычно проблемой C10k.

Continue reading…

СпамоБорец: релиз 0.2.0

Posted by Андрей on Февраль 27, 2009

СпамоБорец — веб-сервис, предоставляющий функции по классификации произвольных текстовых сообщений, и, в частности, выделения спама из общего потока сообщений.

СпамоБорец

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

  • личные сообщения;
  • чаты;
  • комментарии к произвольным объектам;
  • девизы, сообщения «о себе» и т.п. на страницах профиля пользователя;
  • письма в службу поддержки.

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

Continue reading…

Deferred: все подробности 2

Posted by Андрей on Февраль 24, 2009

В предыдущей статье были описаны основные принципы работы Deferred и его применение в асинхронном программировании. Сегодня мы постараемся рассмотреть в деталях функционирование Deferred и примеры его использования.

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

Рассмотрим поподробнее цепочку обработчиков:

Deferred

Continue reading…

Асинхронное программирование: концепция Deferred 8

Posted by Андрей on Февраль 10, 2009

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

Первый пример — сетевой сервер, веб-приложение. Чаще всего как таковых вычислений на процессоре такие приложения не выполняют. Большая часть времени (реального, не процессорного) тратится на ввод-вывод: чтение запроса от клиента, обращение к диску за данными, сетевые обращение к другим подсистемам (БД, кэширующие сервера, RPC и т.п.), запись ответа клиенту. Во время этих операций ввода-вывода процессор простаивает, его можно загрузить обработкой запросов других клиентов. Возможны различные способы решить эту задачу: отдельный процесс на каждое соединение (Apache mpm_prefork, PostgreSQL, PHP FastCGI), отдельный поток (нить) на каждое соединение или комбинированный вариант процесс/нить (Apache mpm_worker, MySQL). Подход с использованием процессов или нитей перекладывает мультиплексирование процессора между обрабатываемыми соединениями на ОС, при этом расходуется относительно много ресурсов (память, переключения контекста и т.п.), такой вариант не подходит для обработки большого количества одновременных соединений, но идеален для ситуации, когда объем вычислений достаточно высок (например, в СУБД). К плюсам модели нитей и процессов можно добавить потенциальное использование всех доступных процессоров в многопроцессорной архитектуре.

Альтернативой является использование однопоточной модели с использованием примитивов асинхронного ввода-вывода, предоставляемых ОС (select, poll, и т.п.). При этом объем ресурсов на каждое новое обслуживаемое соединение не такой большой (новый сокет, какие-то структуры в памяти приложения). Однако программирование существенно усложняется, т.к. данные из сетевых сокетов поступают некоторыми «отрывками», причем за один цикл обработки данные поступают от разных соединений, находящихся в разных состояниях, часть соединений могут быть входящими от клиентов, часть — исходящими к внешним ресурсам (БД, другой сервер и т.п.). Для упрощения разработки используются различные концепции: callback, конечные автоматы и другие. Примеры сетевых серверов, использующих асинхронный ввод-вывод: nginx, lighttpd, HAProxy, pgBouncer, и т.д. Именно при такой однопоточной модели возникает необходимость в асинхронном программировании. Например, мы хотим выполнить запрос в БД. С точки зрения программы выполнение запроса — это сетевой ввод-вывод: соединение с сервером, отправка запроса, ожидание ответа, чтение ответа сервера БД. Поэтому если мы вызываем функцию «выполнить запрос БД», то она сразу вернуть результат не сможет (иначе она должна была бы заблокироваться), а вернет лишь нечто, что позволит впоследствие получить результат запроса или, возможно, ошибку (нет соединения с сервером, некорректный запрос и т.п.) Этим возвращаемым значением удобно сделать именно Deferred.

Второй пример связан с разработкой обычных десктопных приложений. Предположим, мы решили сделать аналог Miranda (QIP, MDC, …), то есть свой мессенджер. В интерфейсе программы есть контакт-лист, где можно удалить контакт. Когда пользователь выбирает это действие, он ожидает что контакт исчезнет на экране и что он действительно удалится из контакт-листа. На самом деле операция удаления из серверного контакт-листа опирается на сетевое взаимодействие с сервером, при этом пользовательский интерфейс не должен быть заблокирован на время выполнения этой операции, поэтому в любом случае после выполнения операции потребуется некоторое асинхронное взаимодействие с результатом операции. Можно использовать механизм сигналов-слотов, callback’ов или что-то еще, но лучше всего подойдет Deferred: операция удаления из контакт-листа возвращает Deferred, в котором обратно придет либо положительный результат (всё хорошо), либо исключение (точная ошибка, которую надо сообщить пользователю): в случае ошибки контакт надо восстановить контакт в контакт-листе.

Примеры можно приводить долго и много, теперь о том, что же такое Deferred. Deferred — это сердце framework’а асинхронного сетевого программирования Twisted в Python. Это простая и стройная концепция, которая позволяет перевести синхронное программирование в асинхронный код, не изобретая велосипед для каждой ситуации и обеспечивая высокое качества кода. Deferred — это просто возвращаемый результат функции, когда этот результат неизвестен (не был получен, будет получен в другой нити и т.п.) Что мы можем сделать с Deferred? Мы можем «подвеситься» в цепочку обработчиков, которые будут вызваны, когда результат будет получен. При этом Deferred может нести не только положительный результат выполнения, но и исключения, сгенерированные функцией или обработчиками, есть возможность исключения обработать, перевыкинуть и т.д. Фактически, для синхронного кода есть более-менее однозначная параллель в терминах Deferred. Для эффективной разработки с Deferred оказываются полезными такие возможности языка программирования, как замыкания, лямбда-функци.

Continue reading…

twisted.python.log и trial — веселимся вместе

Posted by Андрей on Октябрь 24, 2008

Следующий забавный случай взаимодействия частей Twisted Framework заставил меня потерять час времени, спешу рассказать, в надежде, что это спасет чьё-то время. Итак, в Twisted есть модуль логгинга, twisted.python.log, в котором есть удобный метод log.err(), который позволяет записать в лог информацию о текущем исключении. А trial — это framework для написания юнит-тестов из того же Twisted. Их сочетание иногда приводит к проблеме :)

Continue reading…

Зачем нам нужны высокоуровневые языки прогаммирования? 3

Posted by Андрей on Апрель 25, 2008

Наткнулся на чудесный пост от Moshe Zadka. Почему мы пишем программы сегодня на Python, Ruby, PHP, а не на старом добром Си? Почему используем заведомо менее эффективные (как по памяти, так и по скорости языки программирования).

Если кратко, Moshe приводит следующие доводы:

  • не все могут писать на Си, многим это просто недоступно, но это даже хорошо, если становится больше программистов — больше программ, больше удововлетворения для общества;
  • на языке высокого уровня писать быстрее — быстрее выпустим продукт на рынок, следовательно опередим конкурентов;
  • слишком много ошибок в программах на Си, которые можно избежать в языках высокого уровня;
  • люди готовы поменять деньги, вложенные в более мощное железо, на более «умные» программы.

Мне кажется, что вообще вопрос разработки на хороших языках высокого уровня не только быстрее, но и качественнее. Я думаю, что отсутствие в Си и Си++ прямо в самом языке так необходимых типов данных (хеши, списки и т.п.) приводит к неправильному мышлению начинающих программистов, они неправильно кодируют алгоритмы, получаются всё равно менее эффективные программы. А хороший язык высокого уровня уже в tutorial приучает читающего к правильному использованию структур данных, даёт сразу много готовых алгоритмов. Конечно, STL в Си++ решает несколько эту проблему, но даже Страуструп рассказывает о ней в отдельной главе, а часть книги предлагает заново написать тип строк и т.п.

Поиск несуществующего memory leak

Posted by Андрей on Февраль 09, 2008

Очень забавная история. Был работающий и написанный сервер вещаний (a-la Red5) на Python. В связи с развитием основной класс — класс протокола — был разрезан на несколько более мелких классов, которые собирались вместе множественным наследованием в единый монолит, который должен был по функциональности повторять исходный класс (до рефакторинга). Как показало вскрытие, старая версия стабильно работает неделю за неделей, а новая версия через неопределенный, но достаточно короткий период времени (несколько часов) откушивает всю доступную память и погибает.

Типичная ситуация для memory leak, причём опытным путём стало понятно, что эта утечка как-то коррелирует с нагрузкой на сервер (что логично), но зависимость неоднозначная. Момент появления утечки был сужен до одного коммита — казалось бы дело за малым. Анализ исходников в поисках того момента, когда была внесена утечка ничего дает — исходники испаханы вдоль и поперек. Ничего.

Анализ во время выполнения с помощью с помощью довольно замысловатых утилит из этого поста ни к чему не привёл — нет ничего. Я пробовал считать количество объектов, которые видит модуль gc в питоне (то есть тех, кто поддерживает garbage collection), но количество не растет существенно и пропорционально нагрузке на сервер. Однако память процесс кушает и довольно быстро.

В проекте было расширение, написанное на Си, — его анализ, исправление мелких неточностей результата не дает.

Остается неутешительный вывод — в силу того, что число объектов в программе не растет, то значит либо растет размер каких-то объектов (например, строк путем конкатенации), либо это какая-то бага в Python, ОС или где-то еще.

Последнее выглядит романтично и привлекательно, но на самом деле ничего не дает — такую ошибку еще тяжелее найти и поправить, однако багтрекеры FreeBSD, Python и Twisted молчат по поводу таких ошибок.

Двигаемся дальше — создается тестовый клиент вещаний, который имитирует нагрузку на сервер: разное количество вещаний, авторы, подписчики вещания, различные ошибочные ситуации, чат. Тест, запущенный локально с локальным сервером (а нелокально тестовое окружение собрать тяжело — поток порядка 50-100 Мбит/с) не дает результатов — никаких следов утечки памяти ни за час, ни за два.

Отчаяние, практически неделя потерянного впустую времени, тестирование различных вариантов… Что еще? Есть еще Twisted-Conch+Twisted-Manhole — живая консоль с интерпретатором питона в работающем сервере. Но и она не дает никаких результатов — garbage collection работает, никаких разумных объектов, которые могли бы дать утечку памяти нет.

Озарение как всегда приходит в тот момент, когда его совсем не ждешь: ведь сервер вещает, то есть получает на вход поток в 0,5 Мбит/с, раздает его 200 клиентам и делает на выходе 100 Мбит/с, если у кого-то из этих 200 клиентов канал меньше по ширине 0,5 Мбит/с, то данные в буферах процессах начнут скапливаться. Для этого есть решение — дроп пакетов, он был реализован и работал…

Эврика! После разделения большого класса на кучу мелких фрагментов некоторые методы «разрезались» на части. И в них надо поставить вызов метода предка, чтобы все «куски» метода отработали, как и раньше. Но ведь в питоне есть еще MRO (Method Resolution Order), и надо отвыкнуть от того, что базовый класс в моём ощущении является последним в цепочке, то есть у него нет super(Klass, self) — он есть! Просто метод, который включал анализ буфера записи, который в свою очередь вовремя включал механизм пропуска пакетов отключился. И хотя всё работало, но пропуск пакетов не включался и буферы записи росли неограниченно. Исправление в одной строке — вызова метода предка решило проблему. Обидно :)