2017-04-20 63 views
21

我們注意到一些非常小的Web服務調用花費的時間比我們預期的要長得多。我們做了一些調查,並安排了一些定時器,並將其縮小到創建實體框架6 DbContext的實例。不是查詢本身,只是創建上下文。自從我做了一些日誌記錄之後,平均需要多長時間才能創建一個DbContext的實例,看起來大概是50ms。創建實體框架實例上下文環境下降

應用程序被預熱後,上下文創建並不慢。在應用程序回收後,它以2-4ms開始(這是我們在開發環境中看到的)。上下文創建似乎隨着時間的推移而減慢。在接下來的幾個小時內,它將爬升到50-80ms的範圍並保持平穩。

我們的上下文是一個相當大的代碼優先的上下文,有大約300個實體 - 包括一些實體之間的一些非常複雜的關係。我們正在運行EF 6.1.3。我們正在做「每個請求一個上下文」,但對於我們的大多數Web API調用,它只能執行一個或兩個查詢。創建一個60 + ms的上下文,然後執行一個1ms的查詢有點不滿意。我們每分鐘有大約10k個請求,所以我們不是一個輕微使用的站點。

下面是我們所看到的快照。時代在MS,大潮是一個可以回收應用領域的部署。每行是4個不同的Web服務器之一。注意它並不總是相同的服務器。

enter image description here

我沒有采取內存轉儲,試圖充實發生了什麼事情,這裏是堆統計:

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的。

的事情,我們已經試過:

  1. 創建一個簡單的測試工具來複制。它失敗了,我的猜測是因爲我們沒有改變流量,或者改變了查詢運行的類型。只是加載上下文並反覆執行一些查詢並不會導致時間增加。
  2. 我們已經明顯減少了上下文,它是500個實體,現在是300個。速度沒有變化。我的猜測是因爲我們根本沒有使用這200個實體。
  3. (編輯)我們使用SimpleInjector創建我們的「每個請求的上下文」。驗證它不是SimpleInjector我已經通過new'in up了一個Context的實例。創建時間同樣緩慢。
  4. (編輯)我們沒有EF。沒有產生任何影響。

我們可以進一步調查什麼?我瞭解EF使用的緩存非常廣泛,可以加快速度。在緩存中做更多事情,是否會減慢上下文的創建速度?有沒有一種方法可以確切地看到緩存中的內容,以充實那裏的任何奇怪的東西?有誰知道我們可以做些什麼來加速上下文創建?

更新 - 17年5月30日

我把EF6源和編譯我們自己的版本在某些時刻堅持。我們運行一個很受歡迎的網站,以便收集定時的大量信息是棘手的,我沒有得到儘可能我想要的,但基本上我們發現,所有的放緩從this method

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個實體時,我們將它累加起來。我們仍然沒有弄清楚它爲什麼一開始就很快並且放慢速度。

+0

'DbContext'實例創建你的唯一減速? – mxmissile

+0

我的直覺說,「每個請求的一個上下文」就是這裏的問題。爲什麼不是「每個會話一個背景」,至少? – Alex

+0

這裏是討論DbContext生活時間的另一個線程http://stackoverflow.com/questions/10777630/questions-about-entity-framework-context-lifetime – Alex

回答

2

這裏是我開始解決問題的地方,無需轉向更加企業友好的解決方案。

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

雖然EF大大隨着時間的推移改善,我仍然會開始認真地砍東西一旦你到100層的實體(其實我想在這之前開始很好,但,這似乎是一個神奇的數字很多人有說明 - 共識?)。把它看作是爲「上下文」設計的,但是用「域」這個詞代替?這樣,你可以出售你的高管,你正在應用「域驅動設計」來修復應用程序?也許你正在設計未來的「微服務」,然後你在一個段落中使用兩個熱門詞彙。 ;-)

我不是EF企業領域的巨大粉絲,所以我傾向於避免它在高規模或高性能應用程序。你的旅費可能會改變。對於中小企業來說,這可能是非常好的。但是,我遇到了使用它的客戶端。

我不確定以下想法是否完全最新,但根據經驗我會考慮其他一些事項。

  • Pre-gen您的意見。它們是查詢中最昂貴的部分。這對於大型模型更有幫助。
  • 將模型移至單獨的程序集。與代碼組織中的寵物相比,這不是一件完美的事情。
  • 檢查您的應用程序,模型,以便緩存可能性。查詢計劃緩存通常可以縮短一段時間。
  • 使用CompileQuery。
  • 如果可行,請使用NoTracking。如果你不需要這個功能,這是一項巨大的節省。

看起來您已經在應用程序上運行某種類型的分析器,所以我將假設您也查看了您的SQL查詢和可能的性能增益。是的,我知道這不是您要解決的問題,但從用戶的角度來看,這可能會導致整個問題。

爲了迴應@ WiktorZichia關於不回答有關性能問題的評論,在企業系統中擺脫這些類型問題的最佳方法是擺脫實體框架。每個決定都有權衡。 EF是一個很好的抽象並加速了開發。但它帶來了一些不必要的開銷,可能會損害系統的規模。從技術上講,我仍然沒有回答「我如何解決這個問題,我試圖解決這個問題」的問題,所以這可能仍然被視爲失敗。

+0

嗨,我讀過,EF 6已經完成了與編譯查詢:http://stackoverflow.com/questions/26191721/entity-framework-6-compiled-linq-query。那是錯的嗎? –

+3

這似乎並沒有回答實際問題。你更願意提出你對EF的看法(「不是很大的粉絲」)以及一些一般的,完全不相關的建議。你也可以重複別人的觀點(「神奇數字很多人」),沒有任何實際的參考。對不起,說實話。 –

+0

@WiktorZychla - 我沒有回答「如何解決這個問題,我已經試圖解決這個問題」,如果這就是你的意思。但是我確實解決了更大的性能問題。雖然它不會完全解決傾角問題,但會降低傾角。至於解決浸漬問題,我的建議被添加。 –