대용량 트랜잭션을 처리하는 웹 애플리케이션, 서비스 지향 아키텍처(SOA), 그리드 컴퓨팅 및 기타 서버 애플리케이션이 빠르게 성장함에 따라 기존 데이터 저장 시스템은 종종 이러한 수요를 따라가지 못하고 있습니다. 그 이유는 데이터 저장 시스템이 서버 추가를 통해 확장할 수 있는 애플리케이션 아키텍처와 달리 확장이 불가능하기 때문입니다. 이 글에서는 분산 시스템에서 사용되는 캐싱 전략을 예시와 함께 설명합니다. NCache.
이러한 상황에서 인메모리 분산 캐시는 데이터 저장 병목 현상에 대한 탁월한 해결책을 제공합니다. 여러 서버를 클러스터로 분산시켜 메모리를 풀링하고 모든 노드에서 캐시를 동기화합니다. 이 클러스터는 애플리케이션 서버처럼 무한대로 확장할 수 있습니다. 이를 통해 기본 데이터 저장소의 부하를 줄여 확장성 병목 현상을 제거합니다.
사람들이 분산 캐시를 사용하는 두 가지 주요 방법이 있습니다.
| 제품 특장점 | 캐시-어사이드 | 전체 읽기 | 연속 기입 | 뒤에 쓰기 |
|---|---|---|---|---|
| 주요 책임 | 애플리케이션은 데이터베이스와의 상호 작용을 관리합니다. | 캐시는 데이터베이스 읽기를 관리합니다. | 캐시는 데이터베이스 쓰기(동기화)를 관리합니다. | 캐시는 데이터베이스 쓰기(비동기)를 관리합니다. |
| 코드 복잡성 | 높음 (앱의 DB 로직) | 낮음 (캐시 제공자의 DB 로직) | 낮음 (캐시 제공자의 DB 로직) | 낮음 (캐시 제공자의 DB 로직) |
| 읽기 확장성 | 중등도 ("떼지어 날뛰는" 위험) | 높음 (요청이 캐시에 통합됨) | N/A | N/A |
| 쓰기 성능 | 속도가 느립니다 (앱이 데이터베이스를 기다립니다). | N/A | 속도가 느립니다 (앱이 데이터베이스를 기다립니다). | 가장 빠름 (앱이 데이터베이스를 기다리지 않음) |
| 데이터 일관성 | 높음 | 높음 | 높음 | 최종적 (데이터베이스 업데이트 전 짧은 지연 시간) |
캐시 어사이드는 매우 강력한 기술로, 조인 및 중첩 쿼리를 포함하는 복잡한 데이터베이스 쿼리를 실행하고 원하는 방식으로 데이터를 조작할 수 있도록 해줍니다. 그럼에도 불구하고, 전체 읽기 / 연속 기입 아래에 언급된 것처럼 캐시 제외에 비해 다양한 이점이 있습니다.
읽기/쓰기는 애플리케이션의 모든 데이터 액세스에 사용하도록 설계된 것이 아닙니다. 데이터베이스에서 개별 행을 읽거나 개별 캐시 항목에 직접 매핑될 수 있는 데이터를 읽는 상황에 가장 적합합니다. 또한 데이터가 주기적으로 변경되더라도 자주 읽기 위해 캐시에 보관해야 하는 참조 데이터에도 이상적입니다.
읽기 제공자 NCache 사용자 정의 클래스입니다 IReadThruProvider 캐시에서 데이터를 찾을 수 없는 경우 소스(예: SQL Server)에서 데이터를 가져오는 .NET입니다. NCache LoadFromSource를 자동으로 호출합니다. 사용하려면 공급자를 구현하고 다음을 사용하여 배포합니다. NCache 관리 센터 or 명령줄 도구예산 및 가능 캐시 설정에 넣어 두세요.
using System.Collections;
using Alachisoft.NCache.Runtime.DatasourceProviders;
public class SampleReadThruProvider : IReadThruProvider
{
public void Init(IDictionary parameters, string cacheId)
{
// Initialize resources if needed
}
// Modern "Expression-bodied member" for conciseness
public ProviderCacheItem LoadFromSource(string key) =>
new(Database.GetData(key));
// Using LINQ instead of a foreach loop for cleaner bulk loading
public IDictionary<string, ProviderCacheItem> LoadFromSource(ICollection<string> keys)
{
return keys.ToDictionary(
key => key,
key => new ProviderCacheItem(Database.GetData(key))
);
}
public ProviderDataTypeItem<IEnumerable> LoadDataTypeFromSource(string key, DistributedDataType dataType)
{
// "Switch Expression" with "Collection Expressions"
return dataType switch
{
DistributedDataType.List => new([Database.GetData(key)]),
DistributedDataType.Dictionary => new(new Dictionary<string, object>
{
[key] = Database.GetData(key)
}),
DistributedDataType.Counter => new(1000),
_ => null! // null-forgiving operator if nullable context is enabled
};
}
public void Dispose()
{
// Clean up resources if needed
}
}
Init() 기본 데이터 소스에 대한 연결 설정과 같은 특정 리소스 할당 작업을 수행하는 반면 Dispose() 이러한 모든 할당을 재설정하기 위한 것입니다.
Write-Through 제공자 NCache 는 다음을 구현하는 사용자 정의 클래스입니다. IWriteThruProvider 캐시가 업데이트될 때마다 캐시 업데이트(추가, 업데이트, 제거)를 데이터베이스에 직접 유지합니다.
이를 사용하려면 인터페이스를 구현하고 공급자를 사용하여 배포합니다. NCache 관리 센터예산 및 가능 캐시 구성에 있습니다.
using System.Collections;
using Alachisoft.NCache.Runtime.DatasourceProviders;
public class SampleWriteThruProvider : IWriteThruProvider
{
public void Init(IDictionary parameters, string cacheId)
{
// Initialize resources if needed
}
public void Dispose()
{
// Clean up resources if needed
}
public OperationResult WriteToDataSource(WriteOperation operation)
{
var product = operation.ProviderItem.GetValue<Product>();
// Standard switch is still best for side-effects (void actions)
switch (operation.OperationType)
{
case WriteOperationType.Add:
// Database.Add(product);
break;
case WriteOperationType.Update:
// Database.Update(product);
break;
case WriteOperationType.Delete:
// Database.Delete(product);
break;
}
// Target-typed "new" infers the class type automatically
return new(operation, OperationResult.Status.Success);
}
// Modern "Expression-bodied member" using LINQ projection
public ICollection<OperationResult> WriteToDataSource(ICollection<WriteOperation> operations) =<
operations.Select(WriteToDataSource).ToList();
// Handling Data Structures with LINQ
public ICollection<OperationResult> WriteToDataSource(ICollection<DataTypeWriteOperation> operations) =>
operations.Select(op =>
new OperationResult(op, OperationResult.Status.Success)
).ToList();
}