首页 文章

创建实体框架的实例在负载下减慢速度

提问于
浏览
22

我们注意到一些非常小的Web服务调用花费的时间比我们预期的要长 . 我们做了一些调查并安排了一些定时器,我们将其缩小到创建我们的Entity Framework 6 DbContext的实例 . 不是查询本身,只是创建上下文 . 我已经把一些日志记录平均看到创建一个DbContext实例需要多长时间,它似乎是大约50ms .

应用程序预热后,上下文创建速度不慢 . 应用程序回收后,它开始时间为2-4毫秒(这是我们在开发环境中看到的) . 上下文创建似乎随着时间的推移而变慢 . 在接下来的几个小时内,它将爬升到50-80ms的范围并保持平稳 .

我们的上下文是一个相当大的代码优先上下文,包含大约300个实体 - 包括一些实体之间的一些非常复杂的关系 . 我们正在运行EF 6.1.3 . 我们正在执行“每个请求一个上下文”,但对于我们的大多数Web API调用,它只执行一个或两个查询 . 创建一个60毫秒的上下文,然后执行1毫秒的查询有点不满意 . 我们每分钟有大约10k个请求,所以我们不是一个很少使用的网站 .

这是我们所看到的快照 . 时间在MS,最重要的是部署回收app域 . 每行是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 .

我们尝试过的事情:

  • 创建一个简单的测试工具来复制 . 它失败了,我的猜测是因为我们没有导致时间增加 .

  • 我们've reduced the context significantly, it was 500 entities, now 300. Didn' t在速度方面有所作为 . 我的猜测是因为我们根本没有使用这200个实体 .

  • (编辑)我们使用SimpleInjector创建"context per request" . 要验证它不是't SimpleInjector I have spun up an instance of the Context by just new' . 同样慢的创建时间 .

  • (编辑)我们对ngen 'd EF. Didn'施加任何影响 .

我们可以进一步调查什么?据我所知,EF使用的缓存可以加快速度 . 缓存中有更多内容,会减慢上下文创建的速度吗?有没有办法确切地看到那个缓存中的内容可以充实那些奇怪的东西?有谁知道我们可以做些什么来加速上下文创建?

Update - 5/30/17

我拿了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();
            }
        }
    }

对于在我们的上下文中由DBSet定义的每个实体,该foreach的每次迭代都会命中 . 每次迭代都相对较短.1 - .3毫秒,但是当你添加254个实体时,我们就把它加起来了 . 我们仍然没有弄清楚为什么它在开始时速度很快而且速度减慢 .

1 回答

  • 2

    这是我开始解决问题的地方,没有转向更加企业友好的解决方案 .

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

    虽然随着时间的推移,EF已经有了很大的改进,但是一旦你到达100个实体,我仍然会开始认真考虑切碎事情(实际上我会在此之前开始,但这似乎是许多人所说的神奇数字 - 一致意见?) . 可以把它想象为“上下文”的设计,但是使用“域”这个词代替?这样你就可以出售你正在应用“域驱动设计”的高管来修复应用程序?也许你正在为未来的“微服务”进行设计,然后在一个段落中使用两个流行语 . ;-)

    我不是企业领域EF的忠实粉丝,所以我倾向于避免它用于高规模或高性能应用程序 . 你的旅费可能会改变 . 对于SMB,它可能完全没问题 . 但是,我确实遇到了使用它的客户 .

    我不确定以下想法是否完全是最新的,但根据经验我们会考虑其他一些事情 .

    • 预先发表您的看法 . 它们是查询中最昂贵的部分 . 对于大型模型,这将有更多帮助 .

    • 将模型移动到单独的装配体 . 在代码组织中,与我的一个小小的不同之处完全不同 .

    • 检查您的应用程序,模型,以获取缓存可能性 . 查询计划缓存通常可以节省很多时间 .

    • 使用CompileQuery .

    • 如果可行,请使用NoTracking . 如果您不需要该功能,这将节省大量资金 .

    它看起来像你已经在应用程序上运行某种类型的分析器,所以我假设您还查看了SQL查询和可能的性能提升 . 是的,我知道这不是您要解决的问题,但从用户的角度来看,它可以为整个问题做出贡献 .

    为了回应@ WiktorZichia关于不回答有关性能问题的问题的评论,在企业系统中摆脱这些类型问题的最佳方法是摆脱实体框架 . 每个决定都有权衡 . EF是一个很好的抽象,可以加速开发 . 但它带来了一些不必要的开销,可能会严重损害系统 . 现在,从技术上讲,我仍然没有回答“我如何解决这个问题,我试图解决它”问题,所以这可能仍然被视为失败 .

相关问题