Этот пост рассказывает о моем опыте в сокращении времени выполнения Рабочей роли с 5 часов до 1 часа. Эта рабочая роль настроена для вызова некоторых внешних API, чтобы получить список элементов с их рекламными акциями и сохранить их локально. Типичный процесс пакетного обновления, который вы увидите во многих приложениях. Наш клиент интересовался только быстрыми исправлениями, которые помогли бы им сократить время, необходимое для запуска рабочей роли. Нам не разрешалось менять архитектуру или вносить большие изменения, так как срок выпуска был ограничен через несколько недель. Итак, вот что я сделал.
Профилирование
Прежде чем я начал вносить какие-либо изменения, я начал с профилирования приложения, чтобы увидеть узкое место. Довольно часто можно встретить людей, которые проводят «оптимизацию и исправление ошибок» без заранее определенных показателей. Это большая ошибка, так как вы не можете измерить то, что не видите. Сначала необходимо определить проблему, а затем начать применять ваши изменения.
База данных
Когда мы начали видеть статистику того, насколько медленно выполнялась рабочая роль, мы начали понимать, что существует проблема в том, как мы взаимодействуем с базой данных. Рабочая роль удаляет все элементы, а затем вставляет их снова. Не спрашивайте меня, зачем удалять и вставлять снова, на этот вопрос должен ответить архитектор / разработчик рабочей роли. Вставка данных происходит в огромных объемах (миллионы записей). Чтобы сократить время, необходимое для этих транзакций, я сделал следующие изменения:
1. Отключение Entity Framework Отслеживание изменений
Entity Framework отслеживает все изменения любого объекта Entity в памяти, чтобы упростить вставку / обновление / удаление записей из базы данных. Хотя это хорошо, когда вы используете один / несколько объектов, это убийственно, когда вы работаете с миллионами записей одновременно. Для этого вам просто нужно настроить контекст EF, чтобы отключить отслеживание изменений:
dbContext.Configuration.AutoDetectChangesEnabled = false;
2. Отключение функции проверки Entity Framework
Подобно первому изменению, нам не нужно добавлять дополнительные издержки только для проверки, если мы уверены в наших данных. Итак, мы отключили проверку EF:
dbContext.Configuration.ValidateOnSaveEnabled = false;
3. Индивидуальная вставка против массовой вставки
Одна из вещей, которую я обнаружил в рабочей роли, заключается в том, что она вставляет записи одну за другой в оператор foreach. Это может хорошо работать для нескольких предметов, и вы не заметите разницу, но когда дело доходит до огромных объемов, это снижает производительность. Поэтому я подумал о создании расширения для контекста EF для массовой вставки данных, но, к счастью, я обнаружил, что кто-то уже это сделал. EF.BulkInsert — это расширение для EF, которое позволяет вставлять в большом количестве. В основном это группа методов расширения вашего контекста EF. Это очень легкий и отлично работает. На главной странице проекта авторы показывают, что массовая вставка более чем в 20 раз быстрее, чем отдельные вставки., При использовании такого расширения не забудьте правильно настроить параметры. Такие вещи, как BatchSize, Timeout, DataStreaming и т. Д.
4. Транзакции
Я вижу это довольно часто, когда разработчики окружают свой код БД транзакцией, и это может быть полезно на бумаге, но вы должны понимать смысл этой транзакции. Такие транзакции замедляют весь процесс, добавляют огромную перегрузку на сервер БД и сервер приложений, а также затрудняют откат или фиксацию. Более того, EF 6 и выше уже добавляет область транзакции для ваших изменений при их фиксации, так что вы либо сохраните свои изменения, либо откатите их, поэтому мы не нуждались в такой области транзакции, я избавился от нее.
Вычислительный
Еще одно узкое место, которое я обнаружил, заключалось в том, как мы генерируем теги. Это просто метаданные об элементах и их группировке. Потребовалось огромное количество времени, чтобы перебрать все элементы и создать эти теги, категории, группы и т. Д. Все это происходило в памяти. Изменение, которое я внес в это, было очень простым, но существенным, просто заставил его работать параллельно, вот так:
Parallel.ForEach(products, (product) => { // code for generating tags, categories, etc });
Если вы пытаетесь это сделать, убедитесь, что ваш код является поточно-ориентированным. Я должен был исправить несколько проблем здесь или там, так как в моем случае код не был ориентирован на многопотоковое исполнение, но это было небольшое изменение. Кроме того, если вы используете общий список между потоками, то вы можете рассмотреть возможность использования поточно-безопасной коллекции, такой как ConcurrentDictionary или ConcurrentBag.
Внешние Ресурсы
Доступ к списку товаров и рекламных акций осуществлялся из внешнего API. Рабочая роль обращалась к этому списку для 7 основных конечных точек API (7 различных хранилищ) перед началом обработки данных. Это было очень медленно. Чтобы ускорить это, мне пришлось запускать несколько запросов параллельно, аналогично тому, что я сделал с генерацией тегов, как показано ниже:
var endPoints = new[] {1, 2, 3, 4, 5, 6}; Parallel.ForEach(endPoints, (apiEndpoint) => { // code for calling external API });
Кроме того, мы начали кэшировать некоторую информацию, к которой мы обращались через API локально, это также сэкономило нам много времени.
Выполнение этих небольших изменений выше заняло у меня меньше дня, но это оказало огромное влияние на работу рабочей роли. Если приложение будет работать в облаке в течение такого долгого времени, это может стоить вам больших денег, которые мой клиент не возражает То, что они действительно ненавидели, было фактом, что это терпело неудачу так много раз. Тот факт, что вам нужно подождать 5 часов, означает, что он более подвержен ошибкам, соединения могут обрываться, время ожидания базы данных и т. Д. Кроме того, всякий раз, когда разработчики вносят какие-либо изменения, им приходилось долго ждать при тестировании рабочей роли. локально или на сервере.
В заключение, внесение небольших изменений значительно помогло моему клиенту, и они были очень довольны. Я надеюсь, что вы найдете эти советы полезными, и я хотел бы услышать ваши мысли. Если вы боретесь с длительным процессом, свяжитесь с нами, и мы с радостью поможем вам.