Подтвердить что ты не робот

Конструирование экземпляра Entity Framework Контекст замедляется при загрузке

Мы заметили, что некоторые очень маленькие вызовы веб-сервисов занимают гораздо больше времени, чем мы ожидали. Мы провели некоторое расследование и поставили некоторые таймеры, и мы сузили его, чтобы создать экземпляр нашего Entity Framework 6 DbContext. Не сам запрос, а просто создание контекста. С тех пор я несколько раз регистрировал журнал, чтобы узнать, сколько времени он действительно занимает, чтобы создать экземпляр нашего DbContext, и, похоже, он составлял около 50 мс.

После того как приложение разогревается, создание контекста не замедляется. После перезагрузки приложения он начинается с 2-4 мс (что мы видим в наших средах разработчиков). С течением времени создание контекста замедляется. В течение следующих двух часов он будет ползать до диапазона 50-80 мс и выровняться.

Наш контекст - довольно большой контекст кода с примерно 300 объектами, включая некоторые довольно сложные отношения между некоторыми объектами. Мы запускаем EF 6.1.3. Мы делаем "один контекст за запрос", но для большинства наших веб-API он вызывает только один или два запроса. Создание контекста, принимающего 60 + ms, а затем выполнение запроса 1 мс немного недовольство. У нас около 10 тыс. Запросов в минуту, поэтому мы не легко используем сайт.

Вот снимок того, что мы видим. Время в MS, большое падение - это развертывание, которое перерабатывает домен приложения. Каждая строка является одним из четырех разных веб-серверов. Обратите внимание, что это не всегда один и тот же сервер.

введите описание изображения здесь

Я взял свалку памяти, чтобы попытаться выяснить, что происходит, и вот статистика кучи:

00007ffadddd1d60    70821      2266272 System.Reflection.Emit.GenericFieldInfo
00007ffae02e88a8    29885      2390800 System.Linq.Enumerable+WhereSelectListIterator`2[[NewRelic.Agent.Core.WireModels.MetricDataWireModel, NewRelic.Agent.Core],[System.Single, mscorlib]]
00007ffadda7c1a0     1462      2654992 System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Object, mscorlib],[System.Object, mscorlib]][]
00007ffadd4eccf8    83298      2715168 System.RuntimeType[]
00007ffadd4e37c8    24667      2762704 System.Reflection.Emit.DynamicMethod
00007ffadd573180    30013      3121352 System.Web.Caching.CacheEntry
00007ffadd2dc5b8    35089      3348512 System.String[]
00007ffadd6734b8    35233      3382368 System.RuntimeMethodInfoStub
00007ffadddbf0a0    24667      3749384 System.Reflection.Emit.DynamicILGenerator
00007ffae04491d8    67611      4327104 System.Data.Entity.Core.Metadata.Edm.MetadataProperty
00007ffadd4edaf0    57264      4581120 System.Signature
00007ffadd4dfa18   204161      4899864 System.RuntimeMethodHandle
00007ffadd4ee2c0    41900      5028000 System.Reflection.RuntimeParameterInfo
00007ffae0c9e990    21560      5346880 System.Data.SqlClient._SqlMetaData
00007ffae0442398    79504      5724288 System.Data.Entity.Core.Metadata.Edm.TypeUsage
00007ffadd432898    88807      8685476 System.Int32[]
00007ffadd433868     9985      9560880 System.Collections.Hashtable+bucket[]
00007ffadd4e3160    92105     10315760 System.Reflection.RuntimeMethodInfo
00007ffadd266668   493622     11846928 System.Object
00007ffadd2dc770    33965     16336068 System.Char[]
00007ffadd26bff8   121618     17335832 System.Object[]
00007ffadd2df8c0   168529     68677312 System.Byte[]
00007ffadd2d4d08   581057    127721734 System.String
0000019cf59e37d0   166894    143731666      Free
Total 5529765 objects
Fragmented blocks larger than 0.5 MB:
            Addr     Size      Followed by
0000019ef63f2140    2.9MB 0000019ef66cfb40 Free
0000019f36614dc8    2.8MB 0000019f368d6670 System.Data.Entity.Core.Query.InternalTrees.SimpleColumnMap[]
0000019f764817f8    0.8MB 0000019f76550768 Free
0000019fb63a9ca8    0.6MB 0000019fb644eb38 System.Data.Entity.Core.Common.Utils.Set`1[[System.Data.Entity.Core.Metadata.Edm.EntitySet, EntityFramework]]
000001a0f6449328    0.7MB 000001a0f64f9b48 System.String
000001a0f65e35e8    0.5MB 000001a0f666e2a0 System.Collections.Hashtable+bucket[]
000001a1764e8ae0    0.7MB 000001a17659d050 System.RuntimeMethodHandle
000001a3b6430fd8    0.8MB 000001a3b6501aa0 Free
000001a4f62c05c8    0.7MB 000001a4f636e8a8 Free
000001a6762e2300    0.6MB 000001a676372c38 System.String
000001a7761b5650    0.6MB 000001a776259598 System.String
000001a8763c4bc0    2.3MB 000001a8766083a8 System.String
000001a876686f48    1.4MB 000001a8767f9178 System.String
000001a9f62adc90    0.7MB 000001a9f63653c0 System.String
000001aa362b8220    0.6MB 000001aa36358798 Free

Это похоже на немного метаданных и typeusage.

Что мы пробовали:

  • Создание простого тестового жгута для репликации. Это не удалось, я предполагаю, что мы не меняем трафик или меняем тип запросов. Просто загрузка контекста и выполнение нескольких запросов снова и снова не приводили к увеличению времени.
  • Мы значительно сократили контекст, это было 500 объектов, теперь 300. Не изменили скорость. Я предполагаю, что мы не использовали эти 200 объектов вообще.
  • (Edit) Мы используем SimpleInjector для создания нашего "контекста за запрос". Чтобы проверить, что это не SimpleInjector, я создал экземпляр контекста, просто обновив его. То же самое медленное время создания.
  • (Edit) У нас есть ngen'd EF. Не оказал никакого влияния.

Что мы можем исследовать дальше? Я понимаю, что кеш, используемый EF, обширен, чтобы ускорить работу. Чем больше вещей в кеше замедляет создание контекста? Есть ли способ увидеть, что именно в этом кеше можно найти в каких-нибудь странных вещах? Кто-нибудь знает, что конкретно мы можем сделать, чтобы ускорить создание контекста?

Обновление - 5/30/17

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

public void ForceOSpaceLoadingForKnownEntityTypes()
    {
        if (!_oSpaceLoadingForced)
        {
            // Attempting to get o-space data for types that are not mapped is expensive so
            // only try to do it once.
            _oSpaceLoadingForced = true;

            Initialize();
            foreach (var set in _genericSets.Values.Union(_nonGenericSets.Values))
            {
                set.InternalSet.TryInitialize();
            }
        }
    }

Каждая итерация этого foreach попадает для каждого из объектов, определенных DBSet в нашем контексте. Каждая итерация относительно короткая .1-.3 мс, но когда вы добавляете в 254 сущности, которые у нас есть, она складывается. Мы до сих пор не выяснили, почему это быстро в начале и замедляется.

4b9b3361

Ответ 1

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

Our context is a fairly large code-first context with around 300 entities 

В то время как EF значительно улучшилось со временем, я все равно начну серьезно смотреть на измельчение вещей, как только вы доберетесь до 100 объектов (на самом деле я начну задолго до этого, но это кажется магическим числом, о котором многие говорили - консенсус?). Подумайте об этом как о дизайне для "контекстов", но вместо этого используйте слово "домен"? Таким образом, вы можете продавать свои execs, которые вы применяете для разработки приложения для управления доменом? Возможно, вы разрабатываете для будущих "микросервисов", тогда вы используете два слова в одном абзаце.; -)

Я не являюсь большим поклонником EF в пространстве Enterprise, поэтому я стараюсь избегать его для приложений с высокой или высокой производительностью. Ваш пробег может отличаться. Для SMB это, наверное, прекрасно. Однако я сталкиваюсь с клиентами, которые его используют.

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

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

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

В ответ на комментарий @WiktorZichia о том, что вы не отвечаете на вопрос о проблеме производительности, лучший способ избавиться от этих проблем в Enterprise System - избавиться от Entity Framework. В каждом решении есть компромиссы. EF - отличная абстракция и ускоряет развитие. Но это связано с некоторыми излишними накладными расходами, которые могут повредить системы в масштабе. Теперь, технически, я до сих пор не ответил на вопрос "как решить эту проблему, как я ее пытаюсь решить", так что это все равно может считаться сбоем.