Tuesday, September 29, 2015

Работа с ветками в GIT

Приминимо для написания автоматических тестов по задачам в JIRA
События
Действия
Комментарии
Работа над задачей начата
git checkout master
git pull origin master:master
git checkout -b JiraTiket
 
Работа в процессе
git add .
git commit -am "comment here"
git push
Комитов может быть сколько угодно
Работа над задачей завершена
git checkout JiraTiket
git push -u origin JiraTiket:JiraTiket
Создаем Merge Request в CodeBase и отправляем на ревью
 
Ревью кода завершено
Изменения не нужны:
Выполняем Merge (кнопка Perform Megre)
Необходимо изменить код тестов:
После изменения кода выполняем шаг 2 (Работа в процессе).
Заводить новый Merge Request не нужно, все изменения будут отображаться в предыдущем.
 
Установка тэга
git checkout master
git tag releaseN.N.N
git push origin releaseN.N.N

Friday, September 18, 2015

Кэширование в ASP.NET MVC

Стратегия кеширования в приложении


Когда заходит разговор о кешировании складывается парадоксальная ситуация. С одной стороны все понимают важность и нужность кеширования в архитектуре приложений. С другой стороны мало кто может внятно объяснить что и как надо кешировать. 

Обычно люди сходу начинают предлагать готовые реализации кеша, вроде memcached или HTTP-кеша, но это лишь ответ на вопросгде кешировать.

Кеширование – одна из многих тем, наряду с безопасностью и логированием, о которых знают и говорят все, но мало кто может это сделать правильно. 


Зачем нужен кеш


Кеш приближает данные к месту их использования. В современном мире, состоящим на 98% из интернета, данные обычно лежат очень далеко от пользователя. На всем пути от хранилища к пользователю есть кеши, которые служат только одной цели – чтобы пользователь как можно быстрее получил свои данные.

Если рассмотреть внимательнее, то видно, что драгоценное время тратится на обработку данных в поставщике и передачу данных от поставщика клиенту, время обработки данных на клиенте тут не учитываем.

При наличии высоких нагрузок кеширование просто необходимо. Оно позволяет обслуживать больше клиентов, с теми же ресурсами, потому что поставщики данных больше отдыхают. Но даже при невысоких нагрузках кеширование положительно влияет на отзывчивость приложения.

Кеш нельзя просто включить


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

На заре своей карьеры программиста я один раз просто так включил кеширование, буквально через час пришлось его выключить. Тогда я нарвался на основную проблему при кешировании – устаревание данных. Пользователь после изменения данных не видел результата 15 минут.

Очень важно понимать что и как вы собираетесь кешировать, чтобы не нарушать логику работы приложения. И первый вопрос, на который вам необходимо ответить – насколько устаревшие данные можно отдавать клиенту. Естественно можно сделать свой кеш для каждого клиента, это упростит решение вопроса об актуальности данных, но принесет много других проблем.

Типы кеширования


Есть три основных типа кеширования по механике работы:
  • Lazy cache, он же ленивый кеш, он же тупой кеш – самый простой в реализации тип кеширования, зачастую встроен в фреймворки. Кеш просто сохраняет данные и отдает их пока не устареет.
  • Synchronized cache, синхронизированный кеш – клиент вместе с данными получается метку последнего изменения и может спросить у поставщика не изменились ли данные, чтобы повторно из не запрашивать. Такой тип кеширования позволяет всегда иметь свежие данные, но очень сложен в реализации.
  • Write-through cache, или кеш сквозной записи – любое изменение данных выполняется сразу в хранилище и в кеше. Этот тип кеша может никогда не устаревать, но возникают проблемы с так называемой “когерентностью”.


Наверное можно придумать и другие типы кешей, но я не встречал.

Устаревание и когерентность кеша


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

Если одни и те же данные попадают в разные кеши, то возникает проблема когерентности кеша. Например одни и те же данные используются для формирования разных страниц и кешируются страницы. Страницы сформированные позже будут содержать обновленные данные, а страницы, закешированные раньше, будут содержать устаревшие данные. Таким образом будет нарушена согласованность поведения.

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

Эффективность кеша


Основной параметр, который характеризует систему кеширования – это процент попаданий запросов в кеш. Этот параметр довольно легко измерить, чтобы понять насколько ваша система кеширования эффективна.

Частые сбросы кеша, кеширование редко запрашиваемых данных, недостаточный объем кеша – все это ведет к пустой трате оперативной (обычно) памяти, не повышая эффективность работы.

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

Применение разных типов кеширования


Ленивый кеш

Это самый простой вид кеширования, но его нужно использовать осторожно, так как отдает устаревшие данные. Можно при каждой записи сбрасывать ленивый кеш, чтобы поддерживать актуальность данных, но тогда затраты на реализацию будут сравнимы с более сложными типами кеширования.

Такой тип кеширования можно использовать для данных, которые почти никогда не меняются. Другой вариант использования – делать ленивый кеш с небольшим временем устаревания для стабильной работы при всплесках нагрузки.

Такой тип кеширования позволит быстрее всех дать ответ.

Синхронизированный кеш

Это самый полезный тип кеширования, так как отдает свежие данные и позволяет реализовать многоуровневый кеш. 

Такой тип кеширования встроен в протокол HTTP. Сервер отдает метку изменения, а клиент кеширует у тебя результат и в последующем запросе передает эту метку. Сервер может дать ответ, что состояние не изменилось и можно использовать кешированный на клиенте объект. Сервер в свою очередь, получив метку может переспросить у хранилища были ли изменения или нет. 

Этот тип кеширования не избавляет от накладных расходов на общение между системами. Поэтому часто дополняется другими типами кеширования, чтобы ускорить работу.

Кеш сквозной записи

Если есть система распределенного кеширования (memcached, Windows Sever App Fabric, Azure Cache), то можно использовать кеш сквозной записи. Рукопашная реализация синхронизации кешей между узлами сама по себе отдельный большой проект, потому не стоит заниматься ей в рамках разработки приложения. 

Не стоит пытаться кешировать все в синхронизированном кеше, иначе большая часть кода приложения будет заниматься перестройкой кеша.

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

Что еще нужно учитывать в стратегии кеширования


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

Кешируйте данные как можно позже, непосредственно перед отдачей во внешнюю систему. Кешировать данные, полученные извне, необходимо только в случае проблем с производительностью на этом этапе. Внешние хранилища, такие как СУБД и файловые системы, сами реализуют кеширование, поэтому обычно нет смысла кешировать результаты запросов.

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

Заключение


Надеюсь статья была интересной и полезной для вас. Комментируйте, оценивайте, буду рад любым предложениям.

original: http://habrahabr.ru/post/168725/


Кеширование в ASP.NET MVC


В прошлом посте я рассказывал о различных стратегиях кеширования. Там была голая теория, которая и так всем известна, а кому неизвестна, тому без примеров ничего не понятно. 

В этом посте я хочу показать пример кеширования в приложении ASP.NET MVC и какие архитектурные изменения придется внести, чтобы поддерживать кеширование.



Для примера я взял приложение MVC Music Store, которое используется в разделе обучение на сайте asp.net. Приложение представляет из себя интернет-магазин, с корзиной, каталогом товаров и небольшой админкой.

Исследуем проблему


Сразу создал нагрузочный тест на одну минуту, который открывает главную страницу. Получилось 60 страниц в секунду (все тесты запускал в дебаге). Это очень мало, полез разбираться в чем проблема.

Код контроллера главной страницы:
public ActionResult Index()
{
    // Get most popular albums
    var albums = GetTopSellingAlbums(5);
    return View(albums);
}

private List<Album> GetTopSellingAlbums(int count)
{
    // Group the order details by album and return
    // the albums with the highest count

    return storeDB.Albums
        .OrderByDescending(a => a.OrderDetails.Count())
        .Take(count)
        .ToList();
}


На главной странице выводится агрегирующий запрос, которому придется считать большую часть базы, чтобы вывести результат, при этом изменения на главной происходят нечасто.

При этом в каждой странице выводится персонализированная информация — количество элементов в корзине.
Код _layout.cshtml (Razor):
<div id="header">
    <h1><a href="/">ASP.NET MVC MUSIC STORE</a></h1>
    <ul id="navlist">
        <li class="first"><a href="@Url.Content("~")" id="current">Home</a></li>
        <li><a href="@Url.Content("~/Store/")">Store</a></li>
        <li>@{Html.RenderAction("CartSummary", "ShoppingCart");}</li>
        <li><a href="@Url.Content("~/StoreManager/")">Admin</a></li>
    </ul>        
</div>


Такой «паттерн» часто встречается в веб-приложениях. На главной странице, которая открывается чаще всего, выводится в одном месте статистическая информация, которая требует больших затрат на вычисление и меняется нечасто, а в другом месте — персонализированная информация, которая часто меняется. Из-за этого главная страница работает медленно, и средствами HTTP её кешировать нельзя.

Делаем приложение пригодным для кеширования


Чтобы такой ситуации, как описано выше, не происходило надо разделить запросы и собирать части страницы на клиенте. В ASP.NET MVC это сделать довольно просто.
Код _layout.cshtml (Razor):
<div id="header">
    <h1><a href="/">ASP.NET MVC MUSIC STORE</a></h1>
    <ul id="navlist">
        <li class="first"><a href="@Url.Content("~")" id="current">Home</a></li>
        <li><a href="@Url.Content("~/Store/")">Store</a></li>
        <li><span id="shopping-cart"></span></li>
        <li><a href="@Url.Content("~/StoreManager/")">Admin</a></li>
    </ul>        
</div>

<!-- skipped -->

<script>        
    $('#shopping-cart').load('@Url.Action("CartSummary", "ShoppingCart")');
</script>


В коде контроллера:
//[ChildActionOnly] //Убрал
[HttpGet] //Добавил
public ActionResult CartSummary()
{
    var cart = ShoppingCart.GetCart(this.HttpContext);

    ViewData["CartCount"] = cart.GetCount();
    this.Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache); // Добавил
    return PartialView("CartSummary");
}


Установка режима кеширования NoCache необходима, так как браузеры могут по умолчанию кешировать Ajax запросы.

Само по себе такое преобразование делает приложение только медленнее. По результатам теста — 52 страницы в секунду, с учетом ajax запроса для получения состояния корзины.

Разгоняем приложение


Теперь можно прикрутить lazy кеширование. Саму главную страницу можно кешировать везде и довольно долго (статистика терпит погрешности).
Для этого можно просто навесить атрибут OutputCache на метод контроллера:
[OutputCache(Location=System.Web.UI.OutputCacheLocation.Any, Duration=60)]
public ActionResult Index()
{
    // skipped
}


Чтобы оно успешно работало при сжатии динамического контента необходимо в web.config добавить параметр:
<system.webServer>
  <urlCompression dynamicCompressionBeforeCache="false"/>
</system.webServer>

Это необходимо чтобы сервер не отдавал заголовок Vary:*, который фактически отключает кеширование.

Нагрузочное тестирование показало результат 197 страниц в секунду. Фактически страница home\index всегда отдавалась из кеша пользователя или сервера, то есть настолько быстро, насколько возможно и тест померил быстродействие ajax запроса, получающего количество элементов в корзине.

Чтобы ускорить работу корзины надо сделать немного больше работы. Для начала результат cart.GetCount() можно сохранить в кеше asp.net, и сбрасывать кеш при изменении количества элементов в корзине. Получится в некотором роде write-through кеш.

В MVC Music Store сделать такое кеширование очень просто, как так всего 3 экшена изменяют состояние корзины. Но в сложном случае, скорее всего, потребуется реализации publish\subscribe механизма в приложении, чтобы централизованно управлять сбросом кеша.

Метод получения количества элементов:
[HttpGet]
public ActionResult CartSummary()
{
    var cart = ShoppingCart.GetCart(this.HttpContext);
    var cacheKey = "shooting-cart-" + cart.ShoppingCartId;

    this.HttpContext.Cache[cacheKey] = this.HttpContext.Cache[cacheKey] ?? cart.GetCount();

    ViewData["CartCount"] = this.HttpContext.Cache[cacheKey];

    return PartialView("CartSummary");
}


В методы, изменяющие корзину, надо добавить две строчки:
var cacheKey = "shooting-cart-" + cart.ShoppingCartId;
this.HttpContext.Cache.Remove(cacheKey);


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

Используем HTTP кеширование


Последний аккорд — прикручивание HTTP кеширование к запросу количества элементов в корзине. Для этого нужно:
  1. Отдавать Last-Modified в заголовках ответа
  2. Обрабатывать If-Modified-Since в заголовках запроса (Conditional GET)
  3. Отдавать код 304 если значение не изменилось


Начнем с конца. 
Код ActionResult для ответа Not Modified:
public class NotModifiedResult: ActionResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        var response = context.HttpContext.Response;
        response.StatusCode = 304;
        response.StatusDescription = "Not Modified";
        response.SuppressContent = true;
    }
}


Добавляем обработку Conditional GET и установку Last-Modified:
[HttpGet]
public ActionResult CartSummary()
{
    //Кеширование только на клиенте, обновление при каждом запросе
    this.Response.Cache.SetCacheability(System.Web.HttpCacheability.Private);
    this.Response.Cache.SetMaxAge(TimeSpan.Zero);

    var cart = ShoppingCart.GetCart(this.HttpContext);
    var cacheKey = "shooting-cart-" + cart.ShoppingCartId;
    var cachedPair = (Tuple<DateTime, int>)this.HttpContext.Cache[cacheKey];

    if (cachedPair != null) //Если данные есть в кеше на сервере
    {
        //Устанавливаем Last-Modified
        this.Response.Cache.SetLastModified(cachedPair.Item1);

        var lastModified = DateTime.MinValue;

        //Обрабатываем Conditional Get
        if (DateTime.TryParse(this.Request.Headers["If-Modified-Since"], out lastModified)
                && lastModified >= cachedPair.Item1)
        {
            return new NotModifiedResult();
        }

        ViewData["CartCount"] = cachedPair.Item2;
    }
    else //Если данных нет в кеше на сервере
    {
        //Текущее время, округленное до секунды
        var now = DateTime.Now;
        now = new DateTime(now.Year, now.Month, now.Day,
                            now.Hour, now.Minute, now.Second);

        //Устанавливаем Last-Modified
        this.Response.Cache.SetLastModified(now);

        var count = cart.GetCount();
        this.HttpContext.Cache[cacheKey] = Tuple.Create(now, count);
        ViewData["CartCount"] = count;
    }

    return PartialView("CartSummary");
}


Конечно такой код в production писать нельзя, надо разбить на несколько функций и классов для удобства сопровождения и повторного использования. 

Итоговый результат на минутном забеге — 321 страница в секунду, в 5,3 раза выше, чем в первоначальном варианте. 

Залкючение


В реальном проекте надо с самого начала проектировать веб-приложение с учетом кеширования, особенно HTTP-кеширования. Тогда можно будет выдерживать большие нагрузки на довольно скромном железе.

original: http://habrahabr.ru/post/168869/