Поделиться через


Антишаблон монолитной сохраняемости

Хранение всех данных приложения в одном хранилище может привести к снижению уровня производительности. Это связано с возможным конфликтом ресурсов или с тем, что это хранилище не подходит некоторым типам данных.

Описание проблемы

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

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

  • Хранение больших объемов несвязанных данных в том же хранилище и их извлечение может вызывать конфликты, что, в свою очередь, приводит к снижению времени отклика и ошибкам подключения.
  • Выбранное хранилище может не подходить всем типам данных, или же оно не оптимизировано для выполняемых приложением операций.

Ниже приведен пример контроллера веб-API ASP.NET, который добавляет новую запись в базу данных, а также записывает результаты в журнал. Этот журнал хранится в одной базе данных с бизнес-данными. Полный пример см. здесь.

public class MonoController : ApiController
{
    private static readonly string ProductionDb = ...;

    public async Task<IHttpActionResult> PostAsync([FromBody]string value)
    {
        await DataAccess.InsertPurchaseOrderHeaderAsync(ProductionDb);
        await DataAccess.LogAsync(ProductionDb, LogTableName);
        return Ok();
    }
}

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

Как устранить проблему

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

public class PolyController : ApiController
{
    private static readonly string ProductionDb = ...;
    private static readonly string LogDb = ...;

    public async Task<IHttpActionResult> PostAsync([FromBody]string value)
    {
        await DataAccess.InsertPurchaseOrderHeaderAsync(ProductionDb);
        // Log to a different data store.
        await DataAccess.LogAsync(LogDb, LogTableName);
        return Ok();
    }
}

Рекомендации

  • Разделите данные в зависимости от их использования и способа доступа. Например, не храните в том же хранилище данные журнала и бизнес-данные. Эти два типа имеют совсем другие требования и шаблоны доступа. Записи журнала по своей сути требуют последовательного доступа, а бизнес-данные чаще всего — произвольного (в большинстве случаев они реляционные).

  • Учтите шаблон доступа к каждому типу данных. Например, сохраните форматированные отчеты и документы в базе данных документов, например Azure Cosmos DB, но используйте Кэш Azure для Redis для кэширования временных данных.

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

Как определить проблему

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

Чтобы определить причину, сделайте следующее:

  1. Выполните инструментирование системы, чтобы собрать статистику производительности. Зафиксируйте время выполнения каждой операции и точек, в которых приложение читает и записывает данные.
  2. Если это возможно, проследите несколько дней за выполнением системы в рабочей среде, чтобы получить реальное представление о ее использовании. В противном случае, используя скрипты, выполните нагрузочные тесты с правдоподобным количеством виртуальных пользователей, выполняющих типичные операции.
  3. Определите периоды низкой производительности с помощью данных телеметрии.
  4. Определите, к каким хранилищам данных осуществлялся доступ в течение этих периодов.
  5. Определите ресурсы хранилища данных, в связи с которыми могут возникать конфликты.

Пример диагностики

В следующих разделах эти шаги применяются к примеру приложения, описанному ранее.

Инструментирование и мониторинг системы

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

Результаты производительности, полученные при нагрузочном тесте с использованием контроллера на основе SQL

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

Определение периодов низкой производительности

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

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

Определение хранилищ данных, к которым осуществляется доступ в течение этих периодов

На следующем графике показано использование единиц пропускной способности базы данных (DTU) во время нагрузочного теста. (DTU — это мера доступной емкости, и это сочетание использования ЦП, выделения памяти, скорости ввода-вывода.) Использование единиц DTU быстро достигло 100 %. Это примерно точка, в которой наблюдался скачок пропускной способности на предыдущем графике. До завершения теста показатель использования базы данных оставался очень высоким. Под конец произошло небольшое снижение через регулирование количества запросов, снижения количества подключений к базе данных или другие факторы.

Монитор на классическом портале Azure с показателями использования ресурсов базы данных

Анализ данных телеметрии хранилищ

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

Статистика доступа к данным примера приложения

Определение конфликта ресурсов

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

  • Логически разделенные данные записаны в одно хранилище. Данные, такие как журналы, отчеты и сообщения в очереди, не должны храниться в той же базе данных, что и бизнес-данные.
  • Несоответствие между выбранным хранилищем и типом данных, например большие двоичные объекты или XML-документы в реляционной базе данных.
  • Данные, шаблон использования которых значительно отличается, не должны храниться в одном хранилище, например данные с высокой активностью записи и низкой активностью чтения не должны храниться с данными с низкой активностью записи и высокой активностью чтения.

Реализация решения и проверка результатов

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

Результаты производительности, полученные при нагрузочном тесте с использованием контроллера Polyglot

Шаблон пропускной способности аналогичен приведенному на предыдущем графике, но точка, в которой наблюдается скачок производительности, выше примерно на 500 запросов в секунду. Среднее время ответа немного меньше. Тем не менее эти статистические данные описывают полную картину. На основе данных телеметрии рабочей базы данных можно увидеть, что скачок использования единиц DTU происходит на показателе примерно 75 %, а не 100 %.

Монитор на классическом портале Azure с показателями использования ресурсов базы данных в сценарии Polyglot

Аналогичным образом максимальное использование единиц DTU базы данных журналов происходит на показателе примерно 70 %. Производительность системы больше не зависит от баз данных.

Монитор на классическом портале Azure с показателями использования ресурсов базы данных журналов в сценарии Polyglot