인메모리 캐시에 대한 나의 견해

· 3분 읽기

저는 엄청난 양의 데이터를 처리하는 작업을 해왔습니다. 그렇게 하는 동안 나는 그것의 큰 덩어리가 결코 변하지 않거나 적어도 어느 정도 고정된 시간 동안은 변하지 않는다는 것을 깨달았습니다.

그래서 개인 캐시 저장소를 만드는 것이 유용할 것이라고 생각했습니다. 물론 이것은 새로운 것은 아닙니다. 몇 주 전에 Nick Craver가 작성한 StackOverflow의 포스트에서 애플리케이션 캐시를 관리하는 방법에 대해 읽었습니다.

또한 저는 항상 캐시를 사용하여 작업하고 싶었습니다. 캐시가 어떻게 작동하는지, 논리를 어떻게 작동하게 만들 수 있는지, 그래서… 안 될 이유가 없군요!

흐름

다음은 사용자가 값을 요청할 때 캐시를 제어하는 클래스가 어떻게 작동하는지에 대한 간단한 흐름입니다.

Gyazo의 이미지

구현

시작하기 전에

알아요, 알아요. 메모리 캐시를 처리하는 System.Runtime.Caching가 있습니다. 하지만 제가 직접 만들기로 결정했습니다. 해당 클래스를 사용하려면 여기에서 방법을 확인하세요.

캐시항목

첫 번째 단계는 객체의 값과 만료 날짜를 저장하는 클래스를 만드는 것입니다. 아마도 더 좋은 방법이 있을 것입니다. 하지만 제 생각에는 다음과 같습니다.

public class CacheItem
{
    public string Identifier { get; set; }
    public object Value { get; set; }
    public DateTime ValidUntil { get; set; }

    public CacheItem(string identifier, object value, TimeSpan valid)
    {
        Identifier = identifier;
        Value = value;
        ValidUntil = DateTime.UtcNow.Add(valid);
    }
}

캐시 저장소

그런 다음 객체를 처리하고 어딘가에 저장하는 클래스가 필요합니다(CacheItem). 나는 접미사 Repository가 있는 클래스의 모든 데이터/모델을 처리하고 싶지만 꼭 그럴 필요는 없으므로 캐싱을 위해 하나 만들어 보겠습니다.

public class CacheRepository
{
    private static Dictionary<string, string> Cache = new Dictionary<string, string>();
    private static T Set<T>(string key, Func<T> lookup, TimeSpan durationMinutes)
    {
        var item = new Models.Item(key, lookup(), durationMinutes);
        return Save<T>(key, item);
    }
    private static T Save<T>(string key, Item item)
    {
        Cache[key] = JsonConvert.SerializeObject(item);
        return (T)item.Value;
    }
    private static T Get<T>(string key)
    {
        var cached = Cache.FirstOrDefault(x => x.Key == key).Value;
        var item = string.IsNullOrEmpty(cached) ? null : JsonConvert.DeserializeObject<Item>(cached);
        return (item == null) 
        ? default : (item.ValidUntil > DateTime.UtcNow) 
        ? JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(item.Value)) : default;
    }
    public static T GetOrSet<T>(string key, Func<T> lookup, TimeSpan durationMinutes)
    {
        var cache = Get<T>(key);
        return cache == null ? Set(key, lookup, durationMinutes) : cache;
    }
}

지금까지 모든 항목이 저장될 Cache라는 정적 사전이 있습니다. 이는 애플리케이션이 실행되는 동안에만 지속되므로 이 자습서 제목에 메모리 내 캐싱이 있는 이유를 기억하세요.

Cache 항목은 CacheRepository 클래스가 로드되면 초기화된다는 점에 유의하세요.

CacheRepository 클래스를 호출할 때 사용할 수 있는 유일한 메서드는 세 가지 매개 변수가 필요한 GetOrSet(string key, Func<T> lookup, TimeSpan durationMinutes)입니다.

  1. key: 저장할 객체의 식별자입니다.
  2. lookup: 캐시가 만료되었거나 null인 경우의 콜백 함수입니다.
  3. durationMinutes: 현재 시간(UTC)에 추가될 기간(분)입니다.

캐시할 시간

이제 캐싱 저장소를 사용하여 어딘가에서 일부 데이터를 가져옵니다.

이 모든 것이 이해되도록 하기 위해 몇 가지 속성이 포함된 예제 개체를 만든 다음 해당 개체의 목록을 가져와 채우는 저장소를 만들어 보겠습니다.

public class User 
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
public class UserRepository 
{
    public List<User> Get()
    {
        // Code to get users
    }
}

User 목록을 채울 수 있는 방법이 있으므로 CacheRepository 클래스를 활용해 보겠습니다.

private UserRepository _userRepository = new UserRepository();
private List<User> _user;
public List<User> User
{
    get
    {
        _user = CacheRepository.GetOrSet($"users", usersRepo.Get, TimeSpan.FromMinutes(10));
        return _user;
    }
}

마찬가지로 User 변수에 액세스할 때마다 users 키가 있는 객체의 값을 CacheRepository에 요청합니다.

해당 키가 있으면 만료 날짜를 확인합니다. 이러한 조건 중 하나라도 거짓이면 콜백을 사용하여 객체의 값(usersRepo.Get 사용)을 설정하고 만료 날짜가 DateTime.UtcNow + TimeSpan.FromMinutes(10)로 설정된 캐시에 저장한 후 반환합니다.