2022/11/18 デバッグ時のキャッシュの状態を追記
初めに
Microsoft.Extensions.Caching.MemoryのMemoryCacheは便利ですが、有効期限が過ぎたキャッシュを自動では消しません。気を付けないとメモリリークの原因になります。
Microsoftのドキュメントにもさらっと書いてあって見落としがちです。
有効期限切れはバックグラウンドでは発生しません。
キャッシュで期限切れのアイテムをアクティブにスキャンするタイマーはありません。
キャッシュでのあらゆるアクティビティ (Get、Set、Remove) によって、
バックグラウンドでの期限切れ項目のスキャンをトリガーできます。
そうなのです。Get, Set, Removeを実行した場合、かつ、実行時に指定したキーのキャッシュのみが削除されるのです。
試してみる
有効期限が異なる2個のキャッシュを登録し、有効期限切れ後のキャッシュ数を取得してみます。
private static void Test1()
{
var memoryCache = new MemoryCache(new MemoryCacheOptions());
// set
memoryCache.Set("a", "a dummy", new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(1)));
memoryCache.Set("b", "b dummy", new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(10)));
Console.WriteLine("set");
// wait
Thread.Sleep(15000);
Console.WriteLine("... wait 15 sec");
// count
Console.WriteLine($"count : {memoryCache.Count}");
}
→実行後
set
... wait 15 sec
count : 2
有効期限が過ぎてもキャッシュが残っていることが確認できました。
では、
キャッシュでのあらゆるアクティビティ (Get、Set、Remove) によって、
バックグラウンドでの期限切れ項目のスキャンをトリガーできます。
についてはどういうことか。
こちらも試してみます。
"a"というキーをGetしてみました。
private static void Test2()
{
var memoryCache = new MemoryCache(new MemoryCacheOptions());
// set
memoryCache.Set("a", "a dummy", new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(1)));
memoryCache.Set("b", "b dummy", new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(10)));
Console.WriteLine("set");
// wait
Thread.Sleep(15000);
Console.WriteLine("... wait 15 sec");
// get
Console.WriteLine($"get a : {(memoryCache.TryGetValue("a", out string a) ? a : "nothing")}");
// count
Console.WriteLine($"count : {memoryCache.Count}");
}
→実行後
set
... wait 15 sec
get a : nothing
count : 1
はい。キャッシュの数が1個減りました。
Getしたことで初めて有効期限のチェックが行われ、該当キー(上記の例では"a")のキャッシュが削除されたのです。
一方でGetしなかった"b"のキャッシュは残ったままです。
有効期限が過ぎたキャッシュを全て削除するには
Compactメソッドを実行します。
memoryCache.Compact(0);
private static void Test3()
{
var memoryCache = new MemoryCache(new MemoryCacheOptions());
// set
memoryCache.Set("a", "a dummy", new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(1)));
memoryCache.Set("b", "b dummy", new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(10)));
Console.WriteLine("set");
// wait
Thread.Sleep(15000);
Console.WriteLine("... wait 15 sec");
// count
Console.WriteLine($"count : {memoryCache.Count}");
// compact
memoryCache.Compact(0);
Console.WriteLine($"compact");
// count after compact
Console.WriteLine($"count : {memoryCache.Count}");
}
→実行後
set
... wait 15 sec
count : 2
compact
count : 0
Compactメソッド実行後に有効期限が過ぎたキャッシュが全て消えました。
なお、Compactメソッドの引数に圧縮率を指定できますが、有効期限が過ぎたキャッシュのみ削除したい場合は"0"を指定すれば良いと思います。
"0"よりも大きい値を指定すると有効期限内のキャッシュも含めて(Priorityが低い順から)削除されます。
CompactメソッドはIMemoryCacheのインターフェースとして定義されていないため、Dependency Injectionを使用している場合はキャストすれば良いです。
IMemoryCache memoryCache;
:
if (memoryCache is MemoryCache)
{
((MemoryCache)memoryCache).Compact(0);
}