쿼리캐싱
안녕하세요! C# Blazor에서 딕셔너리에 쿼리 키와 쿼리 결과를 저장하고, 특정 시간이 지나면 자동으로 삭제되게 하는 방법을 알려드리겠습니다.
이런 요구사항을 구현하는 좋은 방법은 `MemoryCache` 또는 커스텀 캐시 솔루션을 사용하는 것입니다. 아래 두 가지 접근법을 설명해 드리겠습니다.
## 1. MemoryCache 사용하기
`Microsoft.Extensions.Caching.Memory` 패키지를 사용하면 쉽게 구현할 수 있습니다:
```csharp
@page "/"
@using Microsoft.Extensions.Caching.Memory
@inject IMemoryCache Cache
@code {
private void StoreQueryResult(string queryKey, object queryResult)
{
// 만료 시간 설정 (예: 10분 후 만료)
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
// 캐시에 결과 저장
Cache.Set(queryKey, queryResult, cacheEntryOptions);
}
private object GetQueryResult(string queryKey)
{
// 캐시에서 결과 가져오기 (없으면 null 반환)
Cache.TryGetValue(queryKey, out object result);
return result;
}
}
```
## 2. 커스텀 캐시 서비스 구현하기
더 많은 제어가 필요한 경우, 자체 캐시 서비스를 구현할 수 있습니다:
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
namespace YourNamespace.Services
{
public class CacheItem<T>
{
public T Value { get; set; }
public DateTime ExpiryTime { get; set; }
}
public interface ITimeoutCacheService
{
void Set<T>(string key, T value, TimeSpan expiryTime);
bool TryGetValue<T>(string key, out T value);
void Remove(string key);
}
public class TimeoutCacheService : BackgroundService, ITimeoutCacheService
{
private readonly ConcurrentDictionary<string, object> _cache = new ConcurrentDictionary<string, object>();
private readonly TimeSpan _cleanupInterval;
public TimeoutCacheService(TimeSpan? cleanupInterval = null)
{
_cleanupInterval = cleanupInterval ?? TimeSpan.FromMinutes(1);
}
public void Set<T>(string key, T value, TimeSpan expiryTime)
{
var expiryDateTime = DateTime.UtcNow.Add(expiryTime);
var cacheItem = new CacheItem<T>
{
Value = value,
ExpiryTime = expiryDateTime
};
_cache[key] = cacheItem;
}
public bool TryGetValue<T>(string key, out T value)
{
value = default;
if (_cache.TryGetValue(key, out var item) && item is CacheItem<T> typedItem)
{
if (typedItem.ExpiryTime > DateTime.UtcNow)
{
value = typedItem.Value;
return true;
}
else
{
// 이미 만료된 아이템이면 삭제
_cache.TryRemove(key, out _);
}
}
return false;
}
public void Remove(string key)
{
_cache.TryRemove(key, out _);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
CleanupExpiredItems();
await Task.Delay(_cleanupInterval, stoppingToken);
}
}
private void CleanupExpiredItems()
{
var now = DateTime.UtcNow;
foreach (var key in _cache.Keys)
{
if (_cache.TryGetValue(key, out var item))
{
// 모든 타입의 CacheItem에서 ExpiryTime 속성에 접근
var expiryTime = item.GetType().GetProperty("ExpiryTime")?.GetValue(item) as DateTime?;
if (expiryTime.HasValue && expiryTime.Value <= now)
{
_cache.TryRemove(key, out _);
}
}
}
}
}
}
이제 이 서비스를 Blazor 애플리케이션에 등록하고 사용하는 방법을 설명해 드리겠습니다:
// Program.cs에 서비스 등록
builder.Services.AddSingleton<ITimeoutCacheService, TimeoutCacheService>();
builder.Services.AddHostedService(provider => provider.GetRequiredService<TimeoutCacheService>());
// Blazor 컴포넌트에서 사용
@page "/cache-example"
@using YourNamespace.Services
@inject ITimeoutCacheService CacheService
<h3>캐시 예제</h3>
<button @onclick="StoreData">데이터 저장</button>
<button @onclick="RetrieveData">데이터 검색</button>
<p>결과: @result</p>
@code {
private string result = "";
private void StoreData()
{
// 쿼리 키와 결과 저장 (예: 5분 후 만료)
string queryKey = "example-query";
string queryResult = "쿼리 결과 데이터 " + DateTime.Now.ToString();
CacheService.Set(queryKey, queryResult, TimeSpan.FromMinutes(5));
result = $"저장됨: {queryResult} (5분 후 만료)";
}
private void RetrieveData()
{
string queryKey = "example-query";
if (CacheService.TryGetValue(queryKey, out string value))
{
result = $"검색됨: {value}";
}
else
{
result = "데이터가 없거나 만료되었습니다.";
}
}
}
## 3. 사용 시 고려 사항
1. **메모리 관리**: 캐시는 메모리를 사용하므로, 너무 많은 데이터를 캐시하면 메모리 사용량이 증가할 수 있습니다. 필요한 데이터만 적절한 시간 동안 캐시하세요.
2. **만료 시간 설정**: 데이터의 중요도와 갱신 빈도에 따라 적절한 만료 시간을 설정하세요.
- 자주 변경되는 데이터: 짧은 만료 시간 (몇 분)
- 비교적 정적인 데이터: 긴 만료 시간 (몇 시간)
3. **서버 측 Blazor vs 클라이언트 측 Blazor**:
- 서버 측 Blazor에서는 모든 사용자의 캐시가 서버에 저장됩니다.
- 클라이언트 측 Blazor WebAssembly에서는 각 사용자가 자신의 브라우저에 캐시를 저장합니다.
4. **분산 환경**: 여러 서버를 사용하는 경우, Redis와 같은 분산 캐시 솔루션을 고려하세요.
## 더 간단한 접근법: ConcurrentDictionary + Timer
아래는 더 간단한 방식으로 ConcurrentDictionary와 Timer를 사용한 예제입니다:
using System;
using System.Collections.Concurrent;
using System.Threading;
public class SimpleTimeoutCache<TKey, TValue>
{
private class CacheEntry
{
public TValue Value { get; set; }
public DateTime ExpiryTime { get; set; }
}
private readonly ConcurrentDictionary<TKey, CacheEntry> _cache = new ConcurrentDictionary<TKey, CacheEntry>();
private readonly Timer _cleanupTimer;
public SimpleTimeoutCache(TimeSpan cleanupInterval)
{
// 주기적으로 만료된 항목을 제거하는 타이머 설정
_cleanupTimer = new Timer(CleanupCallback, null, cleanupInterval, cleanupInterval);
}
public void Set(TKey key, TValue value, TimeSpan expiryTime)
{
var entry = new CacheEntry
{
Value = value,
ExpiryTime = DateTime.UtcNow.Add(expiryTime)
};
_cache[key] = entry;
}
public bool TryGetValue(TKey key, out TValue value)
{
value = default;
if (_cache.TryGetValue(key, out var entry))
{
if (entry.ExpiryTime > DateTime.UtcNow)
{
value = entry.Value;
return true;
}
else
{
// 이미 만료된 항목 제거
_cache.TryRemove(key, out _);
}
}
return false;
}
public void Remove(TKey key)
{
_cache.TryRemove(key, out _);
}
private void CleanupCallback(object state)
{
var now = DateTime.UtcNow;
foreach (var key in _cache.Keys)
{
if (_cache.TryGetValue(key, out var entry) && entry.ExpiryTime <= now)
{
_cache.TryRemove(key, out _);
}
}
}
// 리소스 해제
public void Dispose()
{
_cleanupTimer?.Dispose();
}
}
// 사용 예제
public class QueryService
{
private readonly SimpleTimeoutCache<string, object> _queryCache =
new SimpleTimeoutCache<string, object>(TimeSpan.FromMinutes(5));
public object GetQueryResult(string queryKey)
{
if (_queryCache.TryGetValue(queryKey, out var result))
{
return result;
}
// 쿼리 실행 로직
var queryResult = ExecuteQuery(queryKey);
// 캐시에 결과 저장 (30분 동안 유효)
_queryCache.Set(queryKey, queryResult, TimeSpan.FromMinutes(30));
return queryResult;
}
private object ExecuteQuery(string queryKey)
{
// 실제 쿼리 실행 로직
return new { Data = "쿼리 결과", Timestamp = DateTime.Now };
}
}
## 요약
Blazor에서 시간 기반 캐시를 구현하는 방법은 다음과 같습니다:
1. **기본 MemoryCache 사용**: 가장 간단한 방법으로, Microsoft.Extensions.Caching.Memory 패키지를 사용합니다.
2. **커스텀 캐시 서비스**: 더 많은 제어가 필요한 경우 BackgroundService를 확장하여 구현할 수 있습니다.
3. **간단한 ConcurrentDictionary + Timer**: 단순한 시나리오에서는 ConcurrentDictionary와 Timer를 조합하여 사용할 수 있습니다.
선택하신 방법이 애플리케이션의 요구 사항에 맞게 쿼리 결과를 캐시하고 특정 시간이 지나면 자동으로 삭제되도록 도움을 줄 것입니다. 구현 시 추가 도움이 필요하시다면 말씀해 주세요!