Entity Framework Core에서 `CreateExecutionStrategy()`는 **실행 전략(Execution Strategy)**을 创建하는 데 사용됩니다. 이 기능의 주된 목적은 데이터베이스 작업 중에서 발생하는 **일시적인 오류(Transient Failures)**를 처리하는 것입니다. 예를 들어 네트워크 플러그, 데이터베이스 연결 중단, 데드락과 같은 순간적인 오류가 있을 때 이 기제가 중요한 역할을 합니다. 특히 클라우드 환경(예: Azure SQL)이나 고并发 상황에서 이 기능은 애플리케이션의 **오류ทน성(Tolerance)**과 **튼튼함(Robustness)**을 크게 향상시킵니다.
실행 전략의 역할
실행 전략은 다음과 같이 작동합니다:
- **자동 재시도(Automatic Retries)**: 일시적인 오류가 발생할 때(예: `SqlException` 및 오류 코드가 일시적인 오류를 나타내는 경우) 작업을 자동으로 재시도합니다.
- **트랜잭션 일관성(Transaction Consistency)**: 재시도 과정에서 트랜잭션의 완전성과 일관성을 보장하여 부분 커밋을 방지합니다.
- **유연한 구성(Flexible Configuration)**: 재시도 횟수, 재시도 간격 및 예외 필터링 조건을 맞춤형으로 설정할 수 있습니다.
예를 들어 SQL Server의 기본 실행 전략(`SqlServerRetryingExecutionStrategy`)은 특정 일시적인 오류(예: 오류 번호 `40613`, `40197`)에 대해 자동으로 재시도합니다.
트랜잭션 코드에서 실행 전략이 왜필요한가?
트랜잭션 작업 중에 데이터베이스 연결이 일시적인 오류로 중단될 수 있습니다. 이 경우 단순한 트랜잭션 코드(예: `BeginTransaction` 및 `Commit`)만 사용하면 전체 작업이 회복 불가능하게 중단될 수 있습니다. 실행 전략은 트랜잭션 처리를 다음과 같이 최적화합니다:
사례 예시
아래 코드가 실행 전략 없이 트랜잭션을 처리하는 예입니다:
using var transaction = await _dbContext.BeginTransactionAsync();
try
{
// 작업 1: 주문 상태 업데이트
await _dbContext.SaveChangesAsync();
// 작업 2: 재고 차감
await _dbContext.SaveChangesAsync();
await transaction.CommitAsync();
}
catch (Exception ex)
{
await transaction.RollbackAsync();
throw;
}
문제: 작업 2가 일시적인 오류로 인해 실패하면 전체 트랜잭션이롤백됩니다. 사용자는 작업을 수동으로 다시 시작해야 합니다.
해결: 트랜잭션 코드를 실행 전략으로 감싸면 자동으로 작업을 재시도합니다.
실행 전략과 트랜잭션의 협업
제공된 코드 중:
var strategy = _dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using (var transaction = await _dbContext.BeginTransaction())
{
await next(); // 비즈니스 작업 실행(여러번의 SaveChanges 포함할 수 있음)
await _dbContext.CommitTransaction(transaction);
}
});
중요한 점:
- 트랜잭션 트랜잭션 전략에 포함: 실행 전략은 트랜잭션 블록(`BeginTransaction` → 비즈니스 작업 → `Commit`)을 일시적인 오류로 인해 실패할 때 자동으로 재시도합니다.
- 재시도의 원자성: 각 재시도는 새 트랜잭션을 생성하며, 각 시도는 독립적이므로 잔존 상태를 피합니다.
- 농leichbedingung(幂等성): 비즈니스 작업은 재시도시 중복 부작용을 유발하지 않아야 합니다. 예를 들어 고유 제약이나 평등 토큰을 통해 중복 작업을 방지할 수 있습니다.
실행 전략의 대표적인 구성
Entity Framework Core에서 실행 전략은 보통 `DbContextOptionsBuilder`를 통해 구성됩니다:
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(
connectionString,
options => options.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null
)
)
);
설명:
- EnableRetryOnFailure: SQL Server의 기본값 재시도 전략을 사용합니다.
- maxRetryCount: 최대 재시도 횟수.
- maxRetryDelay: 재시도 간의 최대 지연 시간.
코드 중에서 왜 명시적으로 `CreateExecutionStrategy()`를 호출해야 하나요?
기본적으로 실행 전략은 `EnableRetryOnFailure`를 통해 이미 활성화되어 있지만, 다음 상황에서 명시적으로 `CreateExecutionStrategy()`를 호출해야 합니다:
- **명시적인 트랜잭션 경계 필요 시**: 비즈니스 로직이 여러 번의 `SaveChanges`를 포함하거나 EF와 비-EF 작업을 혼합할 때 트랜잭션 경계를 명시적으로 제어할 필요가 있을 때.
- **수동적으로 재시도 로직 트IGGER링 시**: 예를 들어 MediatR 파ipeline Behavior에서 트랜잭션 및 재시도 로직을 통합적으로 관리할 때(예제 코드 참조).
실행 전략의 주의사항
- 트랜잭션 영역(Transaction Scope): 실행 전략은 전체 트랜잭션을(즉 `BeginTransaction` 및 `Commit`을 포함) 반드시 포함해야 합니다. 그렇지 않으면 재시도할 때 트랜잭션이 재설정되지 않아 실패할 수 있습니다.
- 부작용 방지: 트랜잭션 안의 작업은 평등해야 합니다. 즉, 재시도시 중복 부작용을 유발해서는 안 됩니다(예: 이메일 보내기, 외부 API 호출).
- 컨텍스트 상태(Context State): 재시도시 `DbContext`의 추적 엔티티 상태가 재설정됩니다. 따라서 각 재시도 작업은 완전해야 합니다(예: 데이터를 다시 로드해야 할 수 있습니다).
요약
제공된 코드에서 `CreateExecutionStrategy()`의 역할은:
- 오류 보호: 일시적인 오류로 인해 실패한 트랜잭션 작업을 자동으로 재시도합니다.
- 트랜잭션 완전성: 재시도할 때 트랜잭션 블록의 원자성을 유지합니다.
- 통합 관리: MediatR 파ipeline에서 트랜잭션 및 재시도 로직을 단일로 관리하여 코드의 유지 보수성을 향상시킵니다.
만약 실행 전략을 사용하지 않으면 일시적인 오류로 인해 트랜잭션이 즉시 실패하고 클라이언트가 수동으로 작업을 다시 시작해야 합니다. 이는 시스템의 신뢰성을 저하시킬 수 있습니다.