
“メモリリーク”と“. NETアプリケーション”という用語は、しばしば一緒に使用されません。しかし、最近、. NET Core Webアプリケーションで一連のメモリ不足の例外が発生しました。この問題は、Entity Framework Coreの動作の変更によって引き起こされることが判明しました。最終的な解決策は非常に簡単ですが、それを達成するプロセスは困難で興味深いものでした。
该系统本身托管在 Azure 中,由 Angular SPA 前端和后端的.NET Core API 组成,使用 Entity Framework Core 与 Azure SQL 数据库进行通信。作为专门从事.NET 开发的软件咨询公司,我们之前已经编写了许多类似的应用程序。因此,内存不足崩溃是无法预料的,因此我们立即知道这是需要认真对待的事情。使用 Azure 门户中的指标,我们可以看到内存使用率稳步上升,然后突然下降:此下降是应用程序崩溃。

そこで、一見古典的なメモリリークの問題を解決するために、調査に時間をかけ、段階的に変更を加えました。.. NETリークの一般的な原因は、特定の問題が適切に処理されなかったことです。この場合はEF Coreデータベースコンテキストです。そこで、コンテキストを処理できない可能性のある潜在的な原因を探してソースコードを反復します。空白になりました
最近のアップデートには、さまざまなメモリリークの修正と全体的な効率の向上が含まれているため、Entity Framework Coreを最新バージョンにアップグレードしました。
また、使用しているApplication Insightsのバージョン(https://github.com/microsoft/Application Insights-dotnet/issues/594を参照)でメモリリークの可能性があることが判明したので、パッケージもアップグレードしました。
これでは問題が解決しないため、Azure App Service(https://blogs.msdn.microsoft.com/jpsanders/2017/0 2/02/how-to-get-a-full-memory-dump-in-azure-app-services/を参照)から取得したメモリダンプを解剖しました。
マネージドメモリの大部分はMemoryCacheクラスによって使用されることに気づきました。さらに詳しく調べると、キャッシュされたデータの大部分は生のSQLクエリの形式であることがわかりました。基本的に同じクエリである多数のイベントが複数回キャッシュされ、パラメータ自体がパラメータ化されるのではなくクエリ内にハードコードされていることがわかります。
例えば、クエリをキャッシュする代わりに、次のように:
SELECT TOP (1) UserId, FirstName, LastName, EmailAddress
FROM Users
WHERE UserId = @param_1
以下のようなクエリがいくつかあります。
SELECT TOP (1) UserId, FirstName, LastName, EmailAddress
FROM Users
WHERE UserId = 5
そこで、EFコアに関連する可能性のある問題を検索したところ、https//github.com/aspnet/EntityFrameworkCore/issues/10535に出くわしました。
この問題のトピックはこの問題を指摘しています。動的式ツリーを構築し、それを使用してw here節の引数を指定してExpressions. Expression.Constantを使用します。定数式を使用すると、Entity Framework CoreはSQLクエリをパラメータ化せず、Entity Framework 6の動作が変更されます。
この式ツリーをどこでも使ってIDで何かを取得していますだから大きな問題になります
そこで、私たちが行った変更は以下の通りです。
// Before
var param = Expressions.Expression.Parameter(typeof(T));
Expression = Expressions.Expression.Lambda<Func<T, bool>>(
Expressions.Expression.Call(
Expressions.Expression.Constant(valuesToFilter),
"Contains",
Type.EmptyTypes,
Expressions.Expression.Property(param, propertyName)),
param);
// After
var param = Expressions.Expression.Parameter(typeof(T));
// This is what we added
Expression<Func<List<int>>> valuesToFilterLambda = () => valuesToFilter;
Expression = Expressions.Expression.Lambda<Func<T, bool>>(
Expressions.Expression.Call(
valuesToFilterLambda.Body,
"Contains",
Type.EmptyTypes,
Expressions.Expression.Property(param, propertyName)),
param);
使用 lambda 表达式获取表达式主体会使Entity Framework Core对 SQL 查询进行参数化,因此仅缓存它的一个实例。
これは、リビジョンを含めた一定期間のメモリ使用量です。バージョンは赤でマークされており、大きな違いがわかります。安定したメモリ使用量は200MBを超えることはなく、クラッシュする前に1 GBを超え続けました。

最初に調査を行ったとき、本当の解決策は注意を払うことではなく、メモリダンプを調べ、証拠に従うことで最終的にそこに到達しました。
この調査から得られる教訓は以下のとおりです。
- メモリダンプは嘘をつかない-メモリリークがある場合は、まず証拠を確認してください。
- マイクロソフトはEF Coreのソースコードをオープンソース化しており、すべての問題を誰でも見ることができ、必要な開発者にとって非常に便利です。
- 単純なコード変更(この場合は1行)でも大きな影響を与えることができます。