MemoryCache

2023. 6. 19. 17:19Language/C#

개요

프로젝트의 성능을 향상 시키기 위해서 캐싱을 많이 사용합니다. 캐싱으로 유명한 Redis를 사용 할 수도 있지만, 작은 프로젝트에서는 Redis를 따로 사용하지 못하는 경우도 있습니다.

그래서 C#에서는 캐시를 구현 할 수 있는 MemoryCache 클래스를 제공합니다.

이 클래스는 객체의 삽입, 갱신, 삭제와 같은 작업을 메모리상에서 처리하기 때문에 데이터베이스에 부담을 줄이고 좀 더 개발하기 편하게 해줍니다.

IMemoryCache

IMemoryCache는 Microsoft.Extensions.Caching.Memory 패키지에서 사용 할 수 있습니다.

간단하게 Visual Studio를 사용해서 .NET 7 ASP.NET Core 웹 API 프로젝트를 생성해서 진행하도록 하겠습니다.

프로젝트를 생성하면 Controllers 폴더에 WeatherForecastController.cs 파일이 존재합니다.

프로젝트를 실행시켜 보면 Swagger가 실행되며 /WeatherForecast 주소로 HttpGet을 통해 데이터를 받아 올 수 있습니다.

[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get(int param)
{
    return Enumerable.Range(1, param).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(param)),
            TemperatureC = param + 20,
            Summary = Summaries[param]
        }).ToArray();
}

Get 메서드에 파라미터를 넣어 해당 파라미터로 조회 할 때 같은 값이 나오도록 수정했습니다.

이제 저 param 이 캐싱의 Key가 되어 사용 됩니다.

DI 추가하기

IMemoryCache는 아래와 같이 Program.cs에 간단하게 DI를 할 수 있습니다.

builder.Services.AddMemoryCache();

그리고 생성자를 통해 객체를 받습니다.

private readonly ILogger<WeatherForecastController> _logger;
private readonly IMemoryCache _memoryCache;
public WeatherForecastController(
    ILogger<WeatherForecastController> logger,
    IMemoryCache memoryCache
    )
{
    _memoryCache = memoryCache;
    _logger = logger;
}

캐싱 구현하기

[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get(int param)
{
    var cachedResult = _memoryCache.Get<IEnumerable<WeatherForecast>>(param);

    if( cachedResult is null )
    {
        var result = Enumerable.Range(1, param).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(param)),
            TemperatureC = param + 20,
            Summary = Summaries[param]
        }).ToArray();
        _memoryCache.Set(param, result);
        return result;
    }
    return cachedResult;
}

위 코드는 간단 합니다. _memoryCache.Get<IEnumerable<WeatherForecast>>(param);를 통해 캐싱 되어 있는 데이터가 있는지 확인해서 없으면 생성해서 반환하고 있으면 캐싱 된 데이터를 바로 반환하면 됩니다.

MemoryCacheEntryOptions

캐싱이 데이터베이스에 부담을 줄이고 속도를 개선 할 수 있지만, 메모리의 한계와 캐싱 된 데이터를 갱신 해야하기 때문에 특정 시점에 캐싱 된 데이터를 삭제해야 합니다.

그러기 위해서는 MemoryCacheEntryOptions을 통해 간단하게 구현 할 수 있습니다.

AbsoluteExpiration

DateTimeOffset 타입의 절대 만료 시각을 설정합니다. 만료 시각이 지나게 되면 캐시에서 해당 데이터가 삭제됩니다.

// 10분 이후에 만료됨
var options = new MemoryCacheEntryOptions
{
    AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10)
};

AbsoluteExpirationRelativeToNow

현재 시각(DateTimeOffset.Now)을 기준으로 상대적인 만료 시각을 설정합니다. 만료 시각이 지나게 되면 캐시에서 해당 데이터가 삭제됩니다.

// 10분 이후에 만료됨
var options = new MemoryCacheEntryOptions
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
};

SlidingExpiration

마지막으로 캐시된 시각 이후에 지정한 시간(TimeSpan)이 경과하면 만료됩니다. 이 시간이 지나기 전에 해당 데이터가 다시 캐시되면 만료 시간이 다시 시작됩니다.

// 데이터에 대한 마지막 접근 이후 10분이 지나면 만료됨
var options = new MemoryCacheEntryOptions
{
    SlidingExpiration = TimeSpan.FromMinutes(10)
};

Priority

캐시 데이터에 대한 우선순위를 나타내는 CacheItemPriority 열거형입니다. 캐시 데이터에 어떤 우선순위를 부여하면 메모리 부족 상황에서 캐시 데이터를 삭제할 때 이 우선순위에 따라 삭제할 데이터를 결정합니다.

// 우선 순위가 높음 (캐시를 보관하려는 가치가 높음)
var options = new MemoryCacheEntryOptions
{
    Priority = CacheItemPriority.High
};

Size

캐시 데이터의 크기 (바이트)를 설정합니다. 이 속성으로 캐시 데이터 총 크기의 상한선을 설정할 수 있는데, 이 설정에 따라 캐시 데이터가 메모리에 남아있을 수 있는지 여부를 결정합니다.

// 최대 크기 1KB 를 허용함
var options = new MemoryCacheEntryOptions
{
    SizeLimit = 1024
};

PostEvictionCallbacks

캐시에서 데이터가 제거될 때 실행될 콜백들을 저장할 수 있는 PostEvictionDelegate 델리게이트의 리스트입니다. 예를 들어 캐시된 데이터가 제거되면 로그 기록 등의 작업을 실행할 수 있습니다.

// 데이터가 제거될 때 로그 기록 실행
var options = new MemoryCacheEntryOptions
{
    PostEvictionCallbacks =
    {
        new PostEvictionCallbackRegistration
        {
            EvictionCallback = (key, value, reason, state) =>
            {
                Console.WriteLine($"[{DateTime.Now}] 캐시 제거 - Reason: {reason}, Key: {key}, Value: {value}");
            }
        }
    }
};

'Language > C#' 카테고리의 다른 글

Task vs. ValueTask  (0) 2023.06.17
dotnet cli nuget 저장소 지정  (0) 2023.06.16
.NET AOP DynamicProxy  (0) 2023.06.05
StringBuilder vs String Join  (0) 2023.06.04
상속에서 Dispose 패턴  (0) 2023.05.31