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

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

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

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

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

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

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

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

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

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

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

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

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

Есть три основных типа кеширования по механике работы:

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

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

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

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

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

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

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

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

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

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

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

Ленивый кеш

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Заключение

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



Как создавать надежные приложения в серверном коде для SharePoint

Несколько советов о том как сделать код более надежным и устойчивым:

  1. Получайте списки только с помощью такой конструкции 
    web.GetList(SPUrlUtility.CombineUrl(web.Url, listUrl)

    Получение по Title списка ненадежно, так как Title может быть изменен пользователем без вашего ведома.
  2. При получении значений полей элементов списка используйте Id поля или его StaticName.
    listItem[FieldId] //Если вы деплоите это поле или это встроенное поле
    listItem[FiledStaticName] //Если поле нестандартное и не вы его деплоите

    Обращение по Title поля ненадежно, так как Title может меняться.
  3. Для стандартных полей используйте классы SPBuiltInFieldId и Microsoft.SharePoint.Publishing.FieldId
  4. Для обращения к стандартным типам контента используйте SPBuiltInContentTypeId и Microsoft.SharePoint.Publishing.ContentTypeId
  5. Используйте класс Convert для получения типизированного значения поля
    Convert.ToDateTime(listItem["Created"])
    Для разных типов полей SharePoint может возвращать разные типы данных. Например числа могут возвращаться как строки. Кроме того Convert корректно обрабатывает null. Даже если у вас Required поле, то все равно может вернуться null по многим причинам.
  6. Добавление элементов в список
    list.AddItem() //только так
    //list.Items.Add() - ТАК ДЕЛАТЬ НЕЛЬЗЯ
  7. Количество элементов в списке
    list.ItemCount //Только так
    //list.Items.Count – ТАК ДЕЛАТЬ НЕЛЬЗЯ
  8. AllowDeletion

    Используйте AllowDeletion=”false” для списков и полей, которые вы деплоите в своем решении
  9. Sealed

    Используйте Sealed=”true” для полей и типов контента, которые вы деплоите в своем решении
  10. Количество элементов в результатах

    Обязательно указывайте RowLimit свойство при получении элементов с помощью SPQuery или KeywordQuery
  11. Проверка активации фич

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

    Если вы создаете веб-часть, которая обращается к списку, в том же решении, то обязательно поместите в одну фичу уровня Site. Внутри веб-части получайте список с RootWeb (см пункт 1).
  13. Не использовать RunWithElevatedPriveleges

    Используйте конструктор SPSite с SPUserToken. Передавайте SPUserToken.SystemAccount.

    Использование RunWithElevatedPriveleges оправдано только когда вы хотите обратиться к веб-сервису\БД от имени учетной записи пула приложений.

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

  14. Не модифицировать SPPersistedObject в контексте веб-приложений

    Это просто не работает. Можно обойти, но не стоит.
    Все объекты, унаследованные от SPPersistedObject, должны создаваться\изменяться в фичах уровня Farm и WebApplication или в задачах таймера.

  15. Не обращаться к ресурсам компьютера

    Их просто может не быть. Даже если вы при активации фичи создаете все что необходимо, то новый сервер может быть введен в ферму без переактивации фич.

    Это касается фалов, вне тех что деплоятся в решении, ключей реестра, event source  в windows event log и другого.
  16. Не использовать параметры в web.config

    Код SharePoint может быть запущен не только в веб-приложении, но и в timerjob, powershell, процессе-домене службы поиска или в любом коде на сервере.

    Естественно не везде будет работать стандартный класс ConfigurationManager(WebConfigurationManager).

Если есть еще советы – пишите, обязательно дополним список.



Будь мужиком, пиши правильный код, б***ть

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

1. Читай книги, б***ть

Сколько книг по программированию вы прочитали за последний год? Одну? Три? Пять? Это слишком мало. Чтобы стать хорошим программистом надо много читать профессиональной литературы. По книге в месяц будет нормально, нельзя останавливаться, нельзя говорить себе “я уже все знаю, хватить читать”.

Если кончились книги – читайте статьи, кончатся статьи – смотрите видеокурсы. Никогда не останавливайте свое обучение.

При этом нельзя концентрироваться на одном языке или технологии, читайте про javascript, SQL, Web, UX, .NET, Java, функциональное программирование, архитектуру ПО, тестирование, алгоритмы, управление проектами, прикладную платформу, noSQL, снова про алгоритмы и архитектуру.

2. Читай код, б***ть

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

Сейчас очень много кода есть в сети. Есть open-source проекты, есть репозитарии вроде github или codeplex, есть инструменты для реверсинга модулей. Уделяйте несколько часов в неделю чтению кода.

Читайте код платформы, с который работаете. Так вы лучше начнете понимать как (или чем) думали авторы и сможете обходить грабли.

3. Думай, б***ть, своей головой

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

Даже если вы находите готовое решение и паттерн – думайте подойдет ли оно для вашей ситуации. Оцените преимущества и недостатки, попробуйте найти подводные камни. Подумайте как бы вы сами написали этот код.

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

4. Пиши код, б***ть

Ну и самое важное в умении писать хороший код – непосредственно умение писать код. Да, его тоже надо тренировать.  Даже если должность ваша уже не предполагает написание кода – заведите себе pet project и пишите там.

Будьте готовы в любой момент писать код. Вот прямо сейчас сесть и написать решение какой-нибудь задачи. Радуйтесь если на собеседовании вас попросят написать код.

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

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

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

Заключение

Читай книги, читай код, думай головой, пиши код и будь настоящим мужиком б***ть!



Javascript-enabled SharePoint WCF services

К сожалению очень мало толковой информации о том, как создать WCF веб-сервис для SharePoint, который будет доступен как для WS-* клиентов, так и для Javascript.

Да-да, я знаю что уже вышел SharePoint 2013 и .NET 4.5, то что WCF уже не модно, и надо юзать WebAPI и REST. НО не факт что можно будет расширять API для SharePoint 2013, а WebAPI в SharePoint 2013 не работает (или по крайней мере это еще никто не сделал).

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

Для начала надо поставить расширение студии CKS Dev (правильно говорить “секасдев”, @amarkeev гарантирует это), в нем есть шаблон для WCF сервиса

image

Этот пункт создает веб-сервис с BasicHttpBinding.

В SharePoint не обязательно параметры указывать в web.config, достаточно указать Factory в .svc файле, что и делает данный шаблон.

Какие есть Factory и зачем они нужны можно узнать по ссылке.

В принципе можно и этот сервис использовать в javascript коде, но придется долго и мучительно генерировать и парсить XML для SOAP. Чего делать крайне не хочется.

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

  1. Скопировать .svc файл и поставить ему те же параметры развертывания, что у исходного сервиса. Я даю сервисам имя <servicename>.json.svc
  2. В новом файле .svc зменить MultipleBaseAddressBasicHttpBindingServiceHostFactory на MultipleBaseAddressWebServiceHostFactory
  3. В интерфейсе сервиса добавить атрибуты методам, например
    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        [WebGet(BodyStyle = WebMessageBodyStyle.Bare, 
                RequestFormat = WebMessageFormat.Json, 
                ResponseFormat = WebMessageFormat.Json)]
        //[WebInvoke(Method="POST",
        //        BodyStyle = WebMessageBodyStyle.Bare,
        //        RequestFormat = WebMessageFormat.Json,
        //        ResponseFormat = WebMessageFormat.Json)]
        string HelloWorld();
    }

И все, код сервиса править не надо. Атрибуты WebGet и WebInvoke можно найти в сборке System.SeviceModel.Web.

Код для вызова сервиса (javascript):

var webServerRelativeUrl = _spPageContextInfo.webServerRelativeUrl != "/" 
                           ? _spPageContextInfo.webServerRelativeUrl 
                           : "";

$.ajax({
    type:"GET",
    url: webServerRelativeUrl + "/_vti_bin/testservice.json.svc/HelloWorld",
    dataType: 'json',
    success: function (data) {
        //success
    },
    error: function (error) {
        //error
    }
});

Если вам понадобится изменить параметры WCF сервиса, такие как максимальный объем сообщения, то это можно сделать с помощью класса SPWcfServiceSettings

public override void FeatureInstalled(SPFeatureReceiverProperties properties)
 {
     SPWebService contentService = SPWebService.ContentService;
     SPWcfServiceSettings wcfServiceSettings = new SPWcfServiceSettings();
     wcfServiceSettings.ReaderQuotasMaxStringContentLength = Int32.MaxValue;
     wcfServiceSettings.ReaderQuotasMaxArrayLength = Int32.MaxValue;
     wcfServiceSettings.ReaderQuotasMaxBytesPerRead = Int32.MaxValue;
     wcfServiceSettings.MaxReceivedMessageSize = Int32.MaxValue;            
     
     contentService.WcfServiceSettings["servicename.svc"] = wcfServiceSettings;

     contentService.Update(true);
 }

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



Поиск по исходным кодам

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

К сожалению Team Foundation Server не имеет встроенного полнотекстового поиска по исходникам. Найти нужный класс можно только открыв нужный solution, который еще нужно найти в репозитории.

Я сам регулярно сталкиваюсь с такой проблемой. С выходом SharePoint 2013 можно её решить.
Небольшое видео решения:

На видео SharePoint 2013, установленный локально на VM кравлит репозиторий на Codeplex и показывает иходники в результатах поиска.

Если есть замечания\пожелания, то пиши в комментах.