Website development
19 June 2011

Использование Amazon Web Services на примере wikipaintings.org

Думаю, уважаемому сообществу будет интересно узнать о моем опыте разработки интернет проекта с использованием amazon web services.

Я не берусь утверждать, что весь проект идеален, однако я постараюсь описать основные решения, которые помогли сделать этот проект. Wikipedia.org, которая нас вдохновляла на работу, отдает 12 миллиардов страниц в месяц и поэтому мы старались с самого начала готовить код к росту популярности.

Что такое wikipaintings.org — www.wikipaintings.org/ru/About
Сейчас заканчивается первый этап разработки и основные задачи следующего этапа — привлечение волонтеров к наполнению сайта.

Если кому-то кажется, что картины – это скучно, оцените творчество Archimboldo — www.wikipaintings.org/ru/giuseppe-arcimboldo/spring-1573#supersized-artistPaintings-184903

Если же вам интересно наконец-то понять классификацию стилей живописи, добро пожаловать на www.wikipaintings.org/ru/paintings-by-style

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

Технологическая платформа

Данный проект выполнен с использованием ASP.NET MVC, LINQ2SQL, MS SQL Server 2008 R2 и JQuery. Не секрет, что AWS больше ориентирован на JAVA/PHP разработчиков, но я не чувствовал себя особо обделенным.
Предлагаю к рассмотрению мои советы разработчикам в порядке традиционных слоев веб приложений.

База данных

Мы начали с централизованной базы данных бизнес объектов – по нашим оценкам, достаточно мощный инстанс способен обработать нужное количество типовых запросов клиентов в ближайшем будущем (на данный момент это High Cpu Medium Instance). Для самых популярных HTTP запросов мы убедились, что выполняется ровно 1 SQL запрос, который возвращает все необходимые данные – именно базу данных сложнее всего отмасштабировать в данном сценарии.
Важно сказать решительное «нет» хранению файлов в базе данных. Несмотря на продвинутые возможности SQL Server 2008 это ложный путь. Не случайно EC2 расшифровывается как Elastic Computing Cloud – ваш сервер в первую очередь это просто процессор и его основное предназначение считать, а не хранить.
Файлов пользователей изначально будет слишком много, чтобы поместиться на любой диск – так что место им в S3 – бесконечном и очень надежном хранилище файлов. Возможно, оно в начале покажется медленным, но его скорость не меняется будет с ним работать 1 ваш процесс или 1000.
Кэшируйте результаты выполнения сложных запросов на уровне базы данных. У нас всевозможный поиск картин и художников инкапуслирован в одну хранимую процедуру. В качествуе ключа кэширования мы записываем все ее параметры и когда другой пользователь кликнет по тому же тэгу либо стилю у нас уже есть подготовленный список ID подходящих объектов в базе данных. Также это позволяет мгновенно отдать результат в случае использования пейджинга.
Упаковывайте подчиненные объекты вместе с основным. К примеру, изображение хранится как сериализованный XML в строке Картины и такой же XML в строке художника. Да, это не очень красиво, но каждый подзапрос это минимум 0.0005 секунды, и когда вы рисуете все painting на странице то это время нужно умножать на 200.
Также очень полезна статья «Открытие скрытых данных для оптимизации производительности приложений» http://msdn.microsoft.com/ru-ru/magazine/cc135978.aspx — я еженедельно проверяю, корректны ли индексы (уверен, что у многих она уже в закладках)

C# / Middle tier

Ведите учет времени рендеринга страницы и каждой серьезной операции. Недавно засветился профайлер от stackoverflow, но в целом можно быстро сделать что-то попроще самостоятельно. В BeginRequest – инициализация, EndRequest – проверяем, не рендерилась страница больше N секунд. Если рендерилась больше – пишем запись в лог ошибок. Расставляйте код, логирующий прошедшее время по приложению, когда нужно понять, сколько какой шаг занимает. Не помешает вывести такой лог в конце masterpage как html комментарий, чтобы наглядно видеть эффект от тюнинга производительности.
Сделайте методы контроллера «случайное действие» — это намного проще чем изучать selenium или что-то подобное, и позволит тестировать скорость работы разных фукнций приложения простыми тестами, такими как loadimpact.
Осторожно относитесь к вложенным циклам. Безобидный на вид цикл отрисовки, который в каждом шаге проходит по массиву с использованием Where(), выполняется десятую долю секунды. Такой же цикл, перед которым мы конвертируем массив в Dictionary – выполняется уже за сотую долю секунды.
Возвращайте несколько таблиц из одной SP даже если вы используете Linq2sql – это возможно и это неплохо работает. Я оцениваю накладные расходы по элементарной выборке объекта по PK в 0.005 секунды; если же такая операция идет циклом экономия растет многократно. К примеру, хранимая процедура возвращает не только картину, но и художника, который ее написал.
Вычитывайте данные пачками а не по одной записи. Так работает показ результатов поиска картин. После получения списка картин в C# строим список id художников и считываю их одним подзапросом в словарь, а потом назначаю считанные объекты соответствующим картинам.
Не используйте компонентный подход в его естественном виде, привитом ASP.NET, к компоновке страницы. Это предполагает минимум параметров у каждого компонента, что на практике ведет к тому, что каждый компонент вычитывает юзера, проверяет его права, читает свои частные настройки. Заведите контекст системы, действительный ровно на время запроса, куда по мере необходимости собирается вся необходимая информация и читается она ровно 1 раз.
Не полагайтесь на outputcache. Это круто, быстро, и это нужно использовать – он позволит снизить нагрузку на сервер, но это не панацея. К примеру, 18 мая 2010 было 19K page views, из них – 8500 РАЗНЫХ (по данным гугл аналитика). К тому же, outputcache собственный у каждого процесса – значит, затраты на память будут пропорционально количеству объектов в системе x количество серверов. Для главных страниц, у которых нет параметров, продолжительность кэширования может быть достаточно большой (15 минут), а для редких страниц мы ставим 30 секунд на случай публикации этой ссылке на хаббре. С другой стороны хорошо кэшируются ajax запросы – у них не так много параметров, они часто повторяются, и их результат, как правило, меньше чем у HTML страницы.
Выносите сложные части в отдельные проекты. Типичный сервер облака довольно слаб, но их может быть несколько. К примеру, мы вынесли генерацию thumbnails в отдельный веб сервис, что дало нам возможность разместить его на другом сервере и улучить скорость загруки картин при импорте.
Откладывайте то, что можно сделать не сейчас – Амазон предоставляет отличный, быстрый бесконечный и дешевый (наши затраты на этот сервис меньше 10 центов в месяц) инструмент очередь (см. Исходный код №2).
Используйте общую память. К примеру, на странице картины (http://www.wikipaintings.org/ru/giuseppe-arcimboldo/portrait-of-eve-1578) мы показываем другие картины этого художника, которые одинаковы для разных страниц. Сохранив минимально необходимую краткую информацию о картинах по каждому художнику в памяти мы уменьшили время генерации этой страницы в 2,5 раза. Можно, конечно, использовать Velocity но для нас стало проблемой отсутствие поддержки Windows 7, на которой мы разрабатываем. Мы написали собственный windows service с которым веб приложения общаются по remoting. По сути, это примерное повторение System.Web.Caching.Cache но оно поддерживает набор данных одинаковый для всех инстансов.

Клиентская часть

Проверяйте ваш вывод разными инструментами – такие утилиты как YSlow, Pagespeed, Firebug – ваши лучшие друзья.
Все что можно мы перекладываем на cloudfront. Очевидно — картины, thumbnails, но также мы записываем туда собранный в 2 файла CSS — один обычный, 1 – gzip. Важно именно расширение .gzip, так как .gz Safari не понимает. Данный прием продемонстрирован в примере кода №1.
Назначайте Expiration – по умолчанию S3 этого не делает (см. пример кода №1). Вы сэкономите время пользователя и деньги на трафике. Для этой операции и управления статическими файлами на s3 вообще рекомендую cloudberry.
Важным нюансом для повышения скорости загрузки страниц стало разнесение запросов по разным поддоменам. Протокол http не разрешает более 2х запросов к одному серверу одновременно; хоть браузеры и делают до 5ти запросов, загрузка страницы с 100+ картинками занимала больше 10 секунд. Мы разнесли картинки по поддоменам и теперь, при условии наличия хорошего канала, такая же страница загружается до 8 раз быстрее. Для этого создайте в cloudfront набор доменов (у нас – uploads0, uploads1, … uploads8) и выбирайте нужный псевдослучайным образом. Еще лучше зарегистрировать для этого отдельный домен чтобы при каждом запросе не передавались куки но мы сочли это паранойей.
Не загружайте больше информации, чем нужно чтобы показать страницу – все остальное можно загрузить потом по ajax. Чем быстрее грузится страница — тем больше лояльность пользователя. Главное, сделать симпатичную и адекватную анимацию при подгрузке данных.

Рекомендации по развертыванию

Мы используем 1 инстанс централизовано для базы данных, и набор micro инстансов которые занимаются рендерингом страниц под управлением load balancing (См. статью о настроке Windows Server 2008 R2 Core.
Сам Load balancing очень удобен в использовании — для работы с ним есть GUI. Желательно предусмотреть страницу состояния системы, которую амазон будет проверять с интервалом от 6 до 60 секунд (задается). В случае двух(можно больше) отрицательных результатов (таймаут либо код ошибки) инстанс объявляется «нездоровым» и запросы идут на другие сервера. После того как работоспособность восстанавливается он опять включается в работу.
1 load balancing в течении первого года предоставляется бесплатно.
Бакап базы делается ежедневно на отдельный EBS диск. Чтобы ни случилось с хост системой, либо самой windows, средставами панели управления можно будет отключить этот диск от одного виртуального сервера, подключить к новому, поднять бакап и продолжить работу. В качестве дальнейших шагов по повышению устойчивости системы к чрезвычайным происшествиям можно создавать snapshot этого диска автоматически.

Процесс разработки

Не последнюю роль сыграл и процесс разработки. Мы не опирались на фиксированную стоимость этапа проекта, просто каждую неделю выдавали новую стабильно работающую версию (хотя задержки, надо признать, были).
Во время итерации вся команда была сконцентрирована вокруг 3-4 основных задач, над которыми мы продолжали работать до тех пор, пока не были удовлетворены.
После запуска очередной версии реальная жизнь и реальные пользователи часто вносили коррективы, и 1-2 функции шли на очередную переделку.
Данный процесс ужасен, с точки зрения планирования? однако, он работает намного лучше стандартного аутсорсинга, где под каждую задачу заранее выделено количество времени, с точки зрения результата. Также он отлично влияет на мотивацию как с точки зрения того, что нет второсортных задач, так и поддерживает постоянный рефакторинг с тем, чтобы было комфортно вносить изменения в будущем.
Дайте клиенту признаться самому себе что важнее не сроки и бюджеты постройки неизвестно чего, а продукт который не пойдет в мусорную корзину и тогда, возможно, и вам удастся поработать с agile подходом.

Выводы


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

Полезные ссылки
1. Работа с S3
2. Работа с SQS
3. Настройка Windows Server 2008 R2 Core для Micro Intsance
4. SQL Server — Открытие скрытых данных для оптимизации производительности приложений
5. Мини профайлер для веб приложений от stackoverflow

+29
4.6k 48
Comments 38