DevConf 2011. Фотоотчёт и немного видео.

// Июнь 6th, 2011 // IT конференции

Рубрика «IT-конференции» в моём блоге начинает медленно но верно наполняться. Вчера побывал на DevConf, и как и в прошлом году мне оочень  понравилось. В этой заметке будет много текста, моих мыслей (всё сплошное ИМХО и личные впечатления) немного фоток и видео.Сразу хочу сказать, что фотик у меня сел, и некоторые фотки получились размытые, но думаю организаторы скоро выложат свои.

DevConf — что это?

Во время вступительного слова Александр (организатор) сказал по-моему очень правильную вещь. DevConf — это конференция сообществ. Не корпораций, не чиновников, а сообществ разработчиков. А раз так, то в программе будет то, что интересно людям (не даром было объявлено голосование). Даже логотип конференции как бы намекает :-)

Программа была очень обширная. Больше всего интересовал доклад про HipHop, который должен был быть первым, но Скотт МакВикар (автор доклада) к сожалению не смог приехать. В результате доклад был заменён на доклад «Архитектура Badoo» от Алексея Рыбака. Конференция открылась фразой Алексея «не каждый Скотт долетит из Калифорнии в Москву».

Архитектура Badoo // Алексей Рыбак, Badoo

Первый слайд традиционно показывал, что Badoo — это highload. Характеристики:

  • 120 M пользователей
  • > 2K серверов
  • 20 K rps (requests per seconds) на сайт (badoo.com)
  • 30 K rps на десктопное приложение
  • 10 K rps на мобильное приложение

В качестве движка хранения планируется использовать XtraDB, активно используют HandlerSocket. В ообщем тренд на лицо: No NoSQL, use MySQL+HS.

Badoo — большая компания, и у них есть две ветви власти: техническая (где принимаются решение как делать), и бизнес (где принимаются решения что делать). Т.к. система гетерогенная, многопоточная и изменяющаяся, то разработчики делают большой упор на измерения системы, особенно на realtime-измерения. Любые ресурсоемкие задачи, результат которых не попадает в HTTP-ответ выносятся в очередь на специальные Job-сервера. Про тип очереди подробно не распространились, но я рекомендую вам Starling.

Наименование серверов в Badoo

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

Распределение нагрузки в Badoo

Для распределения нагрузки используется алгоритм WRR (weight round-robin), где вес сервера сообразен его мощности. Используется GeoIP и статическая привязка клиента к «более близкому бекэнду». Веб-страницы (по задачам) делятся на два типа: CPU bound (нагружающие процессор) и IO bound (нагружающие дисковую и сетевую подсистему). Идеальным подходом конечно будет переход на асинхронную работу, но он не всегда подходит (большие расходы на переключение контекста и т.д.). В Badoo просто апгрейдят сервера. Кстати, время сборки страницы не превышает 0,1 сек.

Архитектура

Сервера приложений = nginx + php_fpm + eaccelerator/apc. У eAccelerator лучше поддержка, но APC вроде бы скоро войдёт в стандартную поставку PHP.

Сервера баз данных основаны на MySQL + HandlerSocket. Сознательно используются не все возможности MySQL. Базы шардятся, шард = {сервер, схема, таблица} Для выделения уникальных ключей используется выделенный сервер, он же сервер координации. Миграции накатываются параллельно, перестроение схем для 100 машин занимает около 15 минут. Используются транзакционные очереди MySQL, как альтернатива двухфазному коммиту.

При кэшировании используется prolongate — пролонгация элементов кэша, чтобы не писать одно и то же. Есть несколько  типов очередей сообщений: BI (Bisness intelligenxe), Q (обычная очередь), CPQ (Cross-Platform Queue) — для кросс-платформенных сообщений, CSQ (Cross Server Queue) — кросс-серверные сообщения.

Кэширование

Примечательно, что 1/3 всей инфраструктуры серверов — это фото и видео сервера. Используется двухуровневая система кэширования, которая работает следующим образом: все промахи кэша логгируются в nginx. Получается разделение кэша на горячий (много попаданий) и холодный (много промахов). Потом остывший кэш очищается, а все горячие данные переносятся (с помощью перезаписи симлинков) в новый кэш.

Вопросы и ответы

— Почему вы используете storage engine XtraDB а не Maria?
— Maria ещё сырая. Кстати, тесты с DUMP подтверждают это.

— Почему вы используете JOIN?. Надо правильно готовить MySQL. У нас например 570 K rps на HandlerSocket.
— Всё, что у нас тормозит, мы переписываем на C. Это Unix-Way, делаем маленькую программу, которая хорошо делает свою работу (быстро и надежно). Мы денормализуем код, а не данные.

-Вы используете шардинг?
— Да, разные range на разные дата-центры. id — уникальный по всей системе, но также есть и внутренний id объекта пользователя.

— NoSQL?
— Желание есть, но NoSQL — это не панацея.

— Вы говорили, что сбрасываете весь memcache разом? Не тормозит потом?
— Во-первых мы сбрасываем его не по всему кластеру, а в рамках одного сервера. И пока сбрасывается следующий, этот успевает прогреться. И так по очереди.

Ускорение веб-приложений на PHP (Profiling PHP Applications) // Дерик Ретранс, PHP core QA

Этот доклад был в форме видеомоста. Затрагивались вопросы профилировки PHP приложений с помощью Xdebug. Узнал, что есть такая штука в PECL репозитарии, как «inclued» (именно так, опечатки нет) и XHGui. Inclued позволяет профилировать и дампить через иерархию классов и инклюдов в реальном времени. Картинки и графики можно посмотреть в статье «Sorting out your PHP includes using inclued«. Вторая утилитка XHGui позволяет строить очень занятные графики производительности (точнее профилировки) и хода выполнения PHP приложения. Рекомендую вам прочитать статью «Profiling PHP applications«. Вот несколько скриншотов для затравки:

 

Про Xdebug писать не буду, т.к. вам о нём скорее всего ужевсё известно. Да и сам я слушать не стал, а пошёл к рубистам в зал «Поленов». Кстати в конференц центре «Измайлово» залы названы по именам художников.

Построение событийно-управляемого веб-сервиса на Ruby // Иван Касатенко, техлид, UNIQ Systems.

В зале рубистов был поднят серьезный (практически философский) вопрос. Что лучше — синхронный или асинхронный код? У синхронного кода есть проблемы в том, что он выполняется последовательно (ваш К.О.), а проблема асинхронного кода в большом количестве callback’ов, навешанных друг на друга.

Они наслаиваются и в результате получается спагетти-код. Решение — представление системы как среды событий, в которой есть генераторы событий (publishers) и потребители событий (subscribers). Идеальный компонент в общам случае является и генератором и слушателем.

Есть два вида нагрузки: CPU intensive (нагрузка на процессор) и IO intensive (нагрузка на диск и сеть). Очевидно, что делать асинхронными желательно делать именно функции с нагрузками второго типа. Для реализации подобных задач есть несколько решений: NodeJS (javascript), Erlang, phpDaemon (php), tornado (python), eventmachine (ruby). Т.к. мы были в руби зале, то ближе к делу оказалась eventmachine.

Eventmachine предлагает реализацию пула тредов (ThreadPool), и осуществляет мультиплексирование. В качестве веб-сервера использовался Thin, для транспорта — XMLRPC::BaseServer и собственноручно разработанные синхронные и асинхронные обработчики. В рузультате это дало огромный прирост производительности, с 37 rps производительность возрасла на 500 rps.

Вопросы и ответы

— Почему не подошёл Erlang?
— Ruby ближе.

Интеграция frontend-фреймворков с Ruby on Rails // Николай Рекубратский, компания Undev

Сейчас активно идёт разработка RIA приложений, в т.ч. и с помощью RoR платформы. В связи с этим встает вопрос о получении данных с сервера. Как получать данные?

  • JSON
  • ActiveResource
  • Массовые операции, как?

Для этого можно использовать несколько фреймворков.

CoffeeScript

CoffeeScript – это минималистичный язык, который компилируется в JavaScript. Он нацелен на представление хороших частей JavaScript в наиболее простой способ. Прежде чем мы рассмотрим подробно CoffeeScript я хочу рассказать вам мою историю о том, как начал работать с CoffeeScript.

Одно из его достоинств в том, что он позволяет использовать переменные Ruby в ваших JS скриптах. Разработчики создали свою структуру папок (sprockets) в /app/javascripts

Backbone.js

Backbone.js это каркас для создания RIA JavaScript приложений, его автором является Jeremy Ashkenas, создатель CoffeeScript, Backbone является частью компании Document Cloud ей же «принадлежит» Undescrore.js. Backbone — очень легкая библиотека, помогающая вам создавать интерфейсы. Она может работать с любыми библиотеками, к которым вы привыкли.

Backbone это набор классов, размером менее 4Кб, которые формируют структуру вашего кода и помогают создавать качественные MVC веб-приложения.

Backbone формирует структуру тяжелых JavaScript приложений, внесением моделей с key-value подобным хранилищем и своими событиями, коллекций с богатыми API, видов (ориг. views) с декларативной обработкой событий и соединяет все это в одно приложение, поддерживающее RESTful JSON интерфейс.

Backbone не может работать без Underscore.js. Для поддержки REST API и работы с DOM элементами в Backbone.View настоятельно рекомендуется подключить json2.js и jQuery-подобную библиотеку: jQuery или Zepto

Cappucino

Фреймворк Cappuccino – уникальная технология, позволяющая создавать веб-приложения десктопного качества. Он абстрагирует DOM и вместо него предоставляет Cocoa-подобный API. Вместо того, чтобы возиться с CSS-версткой и кроссбраузерными проблемами, вы используете интерфейсы, специально созданные для разработки приложений, а не статических страниц, интерфейсы, взятые с платформ Mac OS X и iOS.

Главная особенность Cappuccino – это созданный специально для него язык Objective-J. Он является такой же надстройкой над JavaScript, как Objective-C – над C. Для меня, как и для многих, вначале было совершенно неочевидно, зачем делать объектно-ориентированную надстройку над языком, который сам по себе является объектно-ориентированным. Однако в этом есть большой смысл.

Во-первых, благодаря Objective-J Cappuccino полностью повторяет Cocoa API. Это не фантазия на тему и даже не творческая переработка, это именно повторение, вплоть до сигнатур функций. За счет этого программисты, работавшие на Mac OS X и iOS могут безболезненно перейти на веб-разработку. Удобство всех API проверено долговременными использованием на десктопе, начиная с ОС NeXTSTEP. И наконец, при использовании фреймворка можно успешно пользоваться документацией Cocoa, которая значительно превосходит по качеству и проработанности Doxygen документацию Cappuccino.

Во-вторых, отсутствие гибкости иногда является плюсом. Большинство программистов посчитает, что я сейчас высказал большую ересь, поэтому сразу начну оправдываться. Дизайнеры знают, что введение сеток и других искусственных ограничений может улучшить дизайн. Objective-J интерфейсы Cappuccino на первый взгляд кажутся неповоротливыми и допотопными по сравнению с тем, что можно было бы сделать на JavaScript. Вместо парадигмы Target-Action зачастую можно было бы использовать замыкания, вместо явных геттеров и сеттеров – __defineGetter__ и __defineSetter__, вместо квадратных скобок – привычные точки, в конце концов. И самое ужасное: Objective-J методы не являются объектами первого класса! Вообщем cappucino будет интересен тем, кто кодит на Маке, и уже кодил под iOS.

Sprutcore

SproutCore — open-source JavaScript фреймворк для создания веб-приложений с расширенными возможностями пользовательского интерфейса, предоставляющими пользователю опыт сравнимый с десктопными приложениями. Для создания приложения используется язык JavaScript. SproutCore был основой для приложения Mailroom. SproutCore также использовался разработчиками компании Apple для создания iWork.com — онлайн расширения для ПО iWork, и при создании интернет-сервиса MobileMe.

Над этим фреймворком сейчас вовсю работает Иегуда Катц (да-да, тот самый, который приезжал к нам на прошлом DevConf).

Вообще я заметил такую тенденцию: интерпретируемые языки (php, javascript) стремятся к компилируемым с помощью таких утилит, как eAccelerator, Backbone.js.

Оптимизация LAMP-приложения на примере OpenX: разгоняемся до 1000 запросов в секунду // Александр Чистяков

Затем я заглянул в зал «Фантазия», где вовсю шел доклад про оптимизацию LAMP приложений. Рассматривались различные решения, сравнивались движки хранения MySQL Также было рассказано про развертывание на базе Pupet. Puppet — это инструмент, который позволяет автоматизировать настройку и управление большим парком машин. Используя Puppet вы сможете централизованно управлять конфигурациями одной, десятков, сотен и тысяч машин. Хотя по сравнению с TeamCity никаких преимуществ (ну кроме ruby vs javaa) я у него не увидел.

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

Как потерять друзей и заставить всех тебя ненавидеть // Андрей Аксенов, Sphinx

Этот доклад был ну очень экспрессивный. Хотя если вдуматься, то всё по делу. Вот кусочек видео оттуда. (Осторожно видео снято издалека и на видеорегистратор :-) )

 

Rails 3.1 // Тимофей Цветков из EvilMartians и Григорий (GreenMouse)

Этот доклад нам представлял Григорий, который ведёт RubyNoName подкаст и один из разработчиков Groupon.com Тимофей Цветков. Они рассказывали о новых возможностях Rails 3.1

HTTP-Стриминг

Это нововведение позволит начать браузеру скачивать таблицы стилей и javascript’ы ещё до того, как браузер закончит формировать ответ. Результат — заметное увеличение скорости отдачи страниц. Это всего лишь опция, которая требует поддержки от web-сервера, но популярное сочетание nginx+unicorn уже готово это обеспечивать. На RailsCasts есть видео об этом, также можно посмотреть документацию здесь.

Основной принцип действия заключается в том, что закешированный заголовок (всё до </head> к примеру) отдаётся сразу после запроса, браузер начинает скачивать указанные в нём скрипты и css, пока сервер делает запросы к БД и т.п.

Однако есть ряд ограничений. Http-Streaming работает только с веб-сервером unicorn, и только с ruby 1.9.2 (из-за fiber).

Asset Pipeline

Ключевой фичей в 3.1 станет asset pipeline под управлением Sprockets 2.0. Оно позволяет намного органичнее организовывать CSS и JavaScript, в том числе в плагинах и движках (Engines). По умолчанию asset pipelines идёт с SCSS для таблиц стилей и CoffeeScript для JavaScript. Большое количество документации на подходе.
Asset Pipeline — это общее обозначение новой концепции работы с клиентской частью приложения. Общий смысл его в том, что теперь как генерируются контроллеры, виды и модели для ваших скаффолдов, также будут создаваться и JS и CSS. Это позволяет достичь более удобной архитектуры и разгрести десятки десятки файлов из public/javascript по модулям. Рельсы сами будут заботиться о сборке всех ваших клиентских файлов в один и кешировании.

jQuery теперь по дефолту

jQuery сделали дефолтным JavaScript-фреймворком, который поставляется с Rails, но откатиться на Prototype остаётся всё так же легко. Они устанавливаются с помощью Bundler’а в гемах jquery-rails и prototype-rails. Просто выберите нужный в Gemfile и оно Просто Заработает.

Картинка с девушкой видимо была для привлечения внимание. И надо сказать, это удалось :-)

Другие изменения в Ruby on Rails 3.1

  • Accessable по ролям. Пример: attr_accessible :title, :id, :as => :admin
  • RJS выделен в отдельный гем.
  • Появилась инструкция force_ssl для заворачивания всего сайта в SSL
  • Теперь можно выключить authentification_token для форм, которые передаются на сторонние сервисы. Например для отправки форм на S3 из Rails.

Вообще доклад повторял во-многом содержание подкаста Григория, так что ничего особо нового не было. Но в любом случае, было приятно окунуться в Ruby среду :-)

Создание веб-кластерных систем на базе PHP+MySQL // Рыжиков Сергей, директор Битрикс

Откровенно говоря, идя в зал «Фантазия» на презентацию от Битрикса я особо ничего не ждал. Возможно сказывается негативный опыт предыдущих докладов. Но в это раз был действительно качественный доклад.

 

Какие же технологии используют в Битриксе? В принципе тот же MySQL + php_fpm. Тут для меня были важны два вопроса. Как реплицируются данные пользователей (например, загружаемые артинки), и как реплицируется БД.

Оказывается для репликации файлов не используют всякие штуки, вроде DRDB, GFS, NFS и SMB, а юзают такую вещь как csync2. Она позволяет поддерживать набор файлов на нескольких машинах в синхронизированном состоянии. Позвоялет задавать действия при обновлении определенных файлов (например, перезапустить программу при обновлении файла конфигурации). Важной особенностью csync2 является низкое потребление процессора и памяти, в результате чего её можно запускать довольно часто. А лучше всего определять данные по частоте, и редко-обновляемые данные синхронизировать тоже редко.

Сергей рекомендует как можно чаще проводить учения, выключать ноды кластера и смотреть на результат. Только тогда вы увидите реальную картинку. Уставивайте учения!

NoSQL — новое слово в мире хранилищ данных // Котусев Святослав Юрьевич.

Доклад был обзорный, так что чего-то нового от него ожидать было глупо. Вот кстати, чем мне не понравился этот DevConf — так это обилием обзорных докладов. В третий раз слушать про NoSQL БД, равно как и про форки MySQL было не интересно. Хотя для тех, кто пропустил эти тренды — будет в самый раз.

Итак решения кластеризации СУБД бывают двух типов: централизованные (master-slave) и децентрализованные (peer-2-peer). В соответствии с этим были представлены следующие СУБД: MongoDB, Redis, Amazon Dynamo, Project Voldemort, CouchDB, Keyspace, BigTable, Hbase, HyperTable.

Badoo Desktop: приложение на миллион юзеров онлайн // Аверин Сергей

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

  • Для связи приложения с сервером используется Google Protocol Buffers.
  • Отсутствуют сложные обработки ошибок.
  • Не нужна 100% синхронность. Пользователь может подождать 5 минут при получении уведомления.
  • На большинство команд не требуется ответа.
  • Desktop программа отстает от сайта (синхронизируется с запаздыванием).
  • Сервер для десктом-клиентов должен поддерживать огромное число соединений.
  • Идёт большой поток команд.
  • Обработка одной команды занимает мало времени.
  • Время ответа не так критично, как для веб страницы.
  • Ошибка на серверной стороне сильно не расстраивает клиента.

Для этого надо провести ряд оптимизаций. Есть несколько важных моментов:

  • Top и профилирование малорезультативны
  • Лучший результат даёт изменение логики программы.
  • Необходимо использовать Real-Time профилирование. Для этого они изобрели PINBA.

Pinba — это демон для сбора статистики о выполнении PHP-скриптов. Статистика есть двух видов – общая (скрипт, время, rusage, объем вывода и др.) и данные по таймерам. Таймеры тут один из ключевых моментов, поэтому они достойны отдельного упоминания.
В то же время, это не standalone-демон, данные собираются отдельным тредом в MySQL. При этом функционал MySQL используется для доступа к данным (которые видны пользователю как обычные таблицы, только read only), т.е. для выборок можно использовать обычный SQL.
Статистика не хранится вечно, это просто невозможно, хранится только актуальная статистика (это понятие для всех разное и конечно это настраивается), так что возьмем для примера 1 000 000 последних запросов или 15 минут – т.е. хранятся все запросы за последние 15 минут, но не более миллиона записей. В зависимости от количества таймеров, эти данные могут занимать от 500Mb до нескольких Gb памяти. На диск, конечно, ничего не пишется.
Таймеры нужны для замеров конкретных частей кода. У таймеров есть «таги» для описания и группировки.
Например, есть у вас коннект к базам – оборачиваете его в таймер с тагами «operation»=>»connect», «db»=>$dbase. В результате мы получаем статистику по одному тагу – «сколько раз в секунду у нас выполняется операция connect и сколько времени она занимает» и по двум – «сколько раз в секунду у нас выполняется операция connect к конкретному серверу и сколько она занимает». Тагов и таймеров может быть произвольное количество (но про разум не стоит забывать тоже, всё это доп. нагрузка на сервер).

После профилирования через PINBA выяснилось, что больше всего тормозил CURL (загрузка занимала 4мс), и замена его на fsockopen() дала +25% производительности.

Железно-площадные оптимизации

  • Начали использовать 4 сетевые карты.
  • Минимум конкуренции, отдельная площадка для сервиса.
  • По СУБД. Миграция с MemcachedDB -> Redis -> MySQL + HandlerSocket. Прослеживаем тренд HS :-)
  • Используются жесткие таймауты. Чтобы не висели неактивные клиенты. Напомню, что у них более 600 000 сесссий онлайн.
  • Хранение сессий в Memcached — зло! Согласен на все 100% Автовытеснение и хранение сессий — несовместимы. А ещё MemcachedDB глючит с TCP.
  • Использование pconnect() может помочь сэкономить ресурсов. Производительность увеличивается x10 раз.. Т.к. 30-60% тратится на установку коннекта. Однако сокет не закрывается. Важно не подцепить чужие данные. Все проблемные сокеты надо закрывать.

БД делятся на ориентированые на чтение, и на ориентированные на запись. В приложении операций чтения в 10 раз больше, а в Redis как раз тормозит чтение. Его очень трудно сдампить, при записи на диск он fork()ается и копирует память. А в это время поступают новые данные! Получается Race condition (копирование vs запись). Он жрет кучу CPU. А HandlerSocket не жрёт! :-)

Борьба с положительной обратной связью

  • Жесткие таймауты. 5 секунд процесс будет ждать а потом отваливается. Использовать прогрессивные таймауты для реконнектов. Это необходимо, чтобы не забить все нити php-fpm. А то можно стать самому себе DDOS’ером.
  • Делать реконнект на свой сервер (статическая привязка пользователя к серверу).

Client-Side кэширование и логика

  • Программы отслеживают время обновления статуса.
  • Клиенты должны реже обновлять свой статус при скринсейвере.
  • Надо дружить с медленными соединениями. Оказывается некоторые сидят на GPRS :-) И исходя из скорости коннекта ставить таймауты на коннекты.

Убираем лишнюю нагрузку

  • Кэшируют всё, что можно.
  • Ввели rate-limited обновления данных, время жизни КЭШа должно быть разумным.
  • Если значение не изменилось, то не надо его писать.

Пороговые срабатывания

  • Порог на изменение сетевой среды. Например, для медленных изменений. А раз в 10 минут обновляем всё принудительно.
  • Например для вычисление города делаем порог по расстоянию. Если изменения < 300м, то не обновляем город.

Асинхронность и пост-обработка

  • Ответ на команду нужно выдавать как можно раньше. Остальное делаем через очередь.
  • Все сервисные задачи обрабатываются отдельно. Привет JobServer!
  • Синхронизация запуска сервисных задач. Например если в очереди несколько запросов к MySQL — выполняем их пачкой! Определяем временное окно, в течении которого должна быть выполнена операция в очереди.

Заключение

  • Мониторинг и профилирование продакшена. Можно сколько угодно гонять тестовую машину, но это всё равно будут тесты, оторванные от реальности.
  • Если вы выпилили все медленные места в php коде, то вы сделали 1/7 всей работы.
  • Меняем логику работы — улучшаем в 10 раз.
  • Предусмотрите все проблемы заранее. Ну или постарайтесь предусмотреть как можно больше.

И кстати, оказывается есть некая функция в php-fpm, которая отправляет ответ, закрывает сокет, а потом что-то доделывает. Можно в принципе использовать как альтернативу очередям.

We build wheel while existing suck or don’t exists

AfterParty

Afterparty удалось на славу. Пообщались в живую с Степаном (@stfalcon), Кириллом Чебуниным (@iamchebba) Валерием Рабиевским @ftrrtf и отлично провели время. После сбора у гостиницы, отправились в Измайловский парк и через каких-то 40 минут пути уже сидели у костра, обсуждая всякие программерские штуки. Валерий развлекал головоломками (да, это моё больное место), частьт народа играли в iPad, общались, болтали, в ообщем было здорово!

Большое спасибо организаторам и Александру лично за конференцию!

Другие отчёты о DevConf 2011

Share

Спасибо!


Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:


2 Responses to “DevConf 2011. Фотоотчёт и немного видео.”

  1. Спасибо за рассказ!

  2. […] тут замечательный фотоотчет с devconf2011, больше всего порадовало Badoo, два слайда […]

Комментировать