

# Lambda 지속성 함수 재시도
<a name="durable-execution-sdk-retries"></a>

지속성 함수는 일시적 실패에 대한 애플리케이션의 복원력을 갖추는 자동 재시도 기능을 제공합니다. SDK는 비즈니스 로직 실패에 대한 단계 재시도와 인프라 실패에 대한 백엔드 재시도라는 2가지 수준에서 재시도를 처리합니다.

## 단계 재시도
<a name="durable-step-retries"></a>

단계 내에 발견되지 않은 예외가 발생하면 SDK는 구성된 재시도 전략에 따라 단계를 자동으로 재시도합니다. 단계 재시도는 SDK가 실행을 일시 중지하고 나중에 진행 상황 손실 없이 재개할 수 있도록 하는 체크포인트가 지정된 작업입니다.

### 단계 재시도 동작
<a name="durable-step-retry-behavior"></a>

다음 표에서는 SDK가 단계 내에서 예외를 처리하는 방법을 설명합니다.


| 시나리오 | 발생한 상황 | 영향 측정 | 
| --- | --- | --- | 
| 단계 내 예외 발생(재시도 횟수가 남아 있음) | SDK는 재시도를 위해 체크포인트를 생성하고 함수를 일시 중지합니다. 다음 간접 호출에서 단계는 백오프 지연이 구성된 상태에서 재시도합니다. | 작업 1개 \+ 오류 페이로드 크기 | 
| 단계 내 예외 발생(재시도 횟수가 남아 있지 않음) | 단계가 실패하고 예외가 발생합니다. 핸들러 코드가 이 예외를 포착하지 못하면 전체 실행이 실패합니다. | 작업 1개 \+ 오류 페이로드 크기 | 

단계에 재시도가 필요한 경우 SDK는 재시도 상태에 체크포인트를 지정하고 실행 중인 다른 작업이 없는 경우 Lambda 간접 호출을 종료합니다. 이를 통해 SDK는 컴퓨팅 리소스를 소비하지 않고 백오프 지연을 구현할 수 있습니다. 백오프 기간이 지나면 함수가 자동으로 재개됩니다.

### 단계 재시도 전략 구성
<a name="durable-step-retry-configuration"></a>

단계에서 실패를 처리하는 방식을 제어하도록 재시도 전략을 구성합니다. 최대 시도 횟수, 백오프 간격 및 재시도 조건을 지정할 수 있습니다.

**다음은 최대 시도 횟수가 포함된 지수 백오프입니다.**

------
#### [ TypeScript ]

```
const result = await context.step('call-api', async () => {
  const response = await fetch('https://api.example.com/data');
  if (!response.ok) throw new Error(`API error: ${response.status}`);
  return await response.json();
}, {
  retryStrategy: (error, attemptCount) => {
    if (attemptCount >= 5) {
      return { shouldRetry: false };
    }
    // Exponential backoff: 2s, 4s, 8s, 16s, 32s (capped at 300s)
    const delay = Math.min(2 * Math.pow(2, attemptCount - 1), 300);
    return { shouldRetry: true, delay: { seconds: delay } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    if attempt_count >= 5:
        return RetryDecision(should_retry=False)
    # Exponential backoff: 2s, 4s, 8s, 16s, 32s (capped at 300s)
    delay = min(2 * (2 ** (attempt_count - 1)), 300)
    return RetryDecision(should_retry=True, delay=delay)

result = context.step(
    lambda _: call_external_api(),
    name='call-api',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**다음은 고정 간격 백오프입니다.**

------
#### [ TypeScript ]

```
const orders = await context.step('query-orders', async () => {
  return await queryDatabase(event.userId);
}, {
  retryStrategy: (error, attemptCount) => {
    if (attemptCount >= 3) {
      return { shouldRetry: false };
    }
    return { shouldRetry: true, delay: { seconds: 5 } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    if attempt_count >= 3:
        return RetryDecision(should_retry=False)
    return RetryDecision(should_retry=True, delay=5)

orders = context.step(
    lambda _: query_database(event['userId']),
    name='query-orders',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**다음은 조건부 재시도입니다(특정 오류만 재시도).**

------
#### [ TypeScript ]

```
const result = await context.step('call-rate-limited-api', async () => {
  const response = await fetch('https://api.example.com/data');
  
  if (response.status === 429) throw new Error('RATE_LIMIT');
  if (response.status === 504) throw new Error('TIMEOUT');
  if (!response.ok) throw new Error(`API_ERROR_${response.status}`);
  
  return await response.json();
}, {
  retryStrategy: (error, attemptCount) => {
    // Only retry rate limits and timeouts
    const isRetryable = error.message === 'RATE_LIMIT' || error.message === 'TIMEOUT';
    
    if (!isRetryable || attemptCount >= 3) {
      return { shouldRetry: false };
    }
    
    // Exponential backoff: 1s, 2s, 4s (capped at 30s)
    const delay = Math.min(Math.pow(2, attemptCount - 1), 30);
    return { shouldRetry: true, delay: { seconds: delay } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    # Only retry rate limits and timeouts
    is_retryable = str(error) in ['RATE_LIMIT', 'TIMEOUT']
    
    if not is_retryable or attempt_count >= 3:
        return RetryDecision(should_retry=False)
    
    # Exponential backoff: 1s, 2s, 4s (capped at 30s)
    delay = min(2 ** (attempt_count - 1), 30)
    return RetryDecision(should_retry=True, delay=delay)

result = context.step(
    lambda _: call_rate_limited_api(),
    name='call-rate-limited-api',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**재시도를 비활성화합니다.**

------
#### [ TypeScript ]

```
const isDuplicate = await context.step('check-duplicate', async () => {
  return await checkIfOrderExists(event.orderId);
}, {
  retryStrategy: () => ({ shouldRetry: false })
});
```

------
#### [ Python ]

```
is_duplicate = context.step(
    lambda _: check_if_order_exists(event['orderId']),
    name='check-duplicate',
    config=StepConfig(
        retry_strategy=lambda error, attempt: {'should_retry': False}
    )
)
```

------

재시도 전략에서 `shouldRetry: false`가 반환되면 재시도 없이 단계가 즉시 실패하게 됩니다. 멱등성 검사 또는 안전하게 반복할 수 없는 부가 효과가 있는 작업과 같이 재시도해서는 안 되는 작업에 사용합니다.

## 단계 외부의 예외
<a name="durable-handler-exceptions"></a>

핸들러 코드에서 발견되지 않은 예외가 발생하지만 단계에서 벗어난 경우 SDK는 실행을 실패로 표시합니다. 이를 통해 애플리케이션 로직의 오류가 올바르게 파악 및 보고됩니다.


| 시나리오 | 발생한 상황 | 영향 측정 | 
| --- | --- | --- | 
| 임의 단계 외부의 핸들러 코드에서 예외 발생 | SDK는 실행을 FAILED로 표시하고 오류를 반환합니다. 예외는 자동으로 재시도되지 않습니다. | 오류 페이로드 크기 | 

오류가 발생하기 쉬운 코드에 자동 재시도를 활성화하려면 재시도 전략을 통해 단계로 래핑합니다. 단계는 구성 가능한 백오프로 자동 재시도를 제공하는 반면, 단계 외부의 코드는 즉시 실패합니다.

## 간접 호출 재시도
<a name="durable-invocation-retries"></a>

간접 호출 수준 재시도는 Lambda 지속성 함수를 간접 호출하려고 시도하는 방식에 따라 다르게 처리됩니다. 다음 표에서는 여러 간접 호출 유형이 간접 호출 수준 재시도에 어떤 영향을 미칠 수 있는지 설명합니다.


| 간접 호출 유형 | 발생한 상황 | 
| --- | --- | 
| 동기식 간접 호출 |  Lambda는 지속성 함수 실행 중에 오류가 발생하면 간접 호출을 자동으로 재시도하지 않습니다. 간접 호출에 실패할 때 재시도는 동기식 간접 호출의 소스에 따라 달라집니다. 예를 들어 AWS SDK, InternalFailure 및 ThrottlingException을 사용하면 기본적으로 자동 재시도됩니다. | 
| 비동기식 간접 호출 |  지속성 함수 실행이 실패하면(예: FAILED, STOPPED 또는 TIMED\_OUT 상태로 전환) Lambda는 실행을 재시도하지 않습니다. 이는 비동기식 간접 호출에 실패했을 때 Lambda가 함수를 재시도하는 표준 Lambda 함수와 다릅니다. 비동기식 간접 호출에 대한 MaximumRetryAttempts 설정은 지속성 실행에 적용되지 않습니다. 함수에 대한 Dead Letter Queue(DLQ)를 구성하면 Lambda는 트리거 이벤트를 DLQ로 보냅니다. | 
| 이벤트 소스 매핑(ESM) |  Lambda는 기본적으로 성공할 때까지 전체 배치를 재시도합니다. 스트림 소스(DynamoDB 및 Kinesis)의 경우, 함수에서 오류가 반환될 때 Lambda가 재시도하는 최대 횟수를 구성할 수 있습니다. [이벤트 소스 매핑 배치 처리](invocation-eventsourcemapping.md#invocation-eventsourcemapping-batching)를 참조하세요. Amazon SQS ESM의 경우 원래 Amazon SQS 대기열에서 DLQ를 통해 최대 재시도 횟수를 구성할 수 있습니다. [Amazon SQS ESM 구성](services-sqs-configure.md)을 참조하세요. 또는 함수 수준에서 DLQ를 고려할 수 있으며, Lambda는 실패한 트리거 이벤트를 DLQ로 전송합니다. [함수 DLQ](invocation-async-retain-records.md#invocation-dlq)를 참조하세요. 모든 처리 시도에 실패한 이벤트 또는 성공적인 처리 시도에 대한 이벤트의 레코드를 수신하는 데 관심이 있는 경우 ESM의 대상을 구성할 수 있습니다. [간접 호출 비동기 대상](invocation-async-retain-records.md#invocation-async-destinations)을 참조하세요. | 
| 직접 트리거 |  이는 '트리거'에 따라 달라집니다. 예를 들어 Lambda는 Amazon S3 이벤트 알림에 의해 트리거된 함수를 비동기식으로 처리합니다. [Lambda를 사용하여 Amazon SQS 이벤트 알림 처리](with-sqs.md)를 참조하세요. Lambda는 Amazon SNS 이벤트 알림에 의해 트리거된 함수를 비동기식으로 처리합니다. [Amazon SNS 알림을 사용하여 Lambda 함수 간접 호출](with-sns.md)을 참조하세요. 비동기식 간접 호출 재시도 동작은 '비동기식 간접 호출' 테이블 항목 위에 있습니다. Amazon SNS가 Lambda에 도달할 수 없거나 메시지가 거부되는 경우, Amazon SNS는 몇 시간 동안 간격을 늘리면서 재시도합니다. 자세한 내용은 Amazon SNS FAQ에서 [안정성](https://aws.amazon.com/sns/faqs/#Reliability)을 참조하세요. API Gateway는 Lambda를 동기식으로 간접 호출하고 실제 오류 응답을 요청자에게 다시 반환합니다. [간접 호출 재시도](invocation-retries.md)를 참조하세요. 동기식 간접 호출 재시도 동작은 '동기식 간접 호출' 테이블 항목 위에 있습니다. 자세한 내용은 [각 직접 트리거](invocation-eventsourcemapping.md#eventsourcemapping-trigger-difference)를 참조하세요. | 

## 백엔드 재시도
<a name="durable-backend-retries"></a>

백엔드 재시도는 Lambda에서 인프라 실패, 런타임 오류가 발생하거나 SDK가 지속성 실행 서비스와 통신할 수 없는 경우에 발생합니다. Lambda는 지속성 함수가 일시적인 인프라 문제로부터 복구될 수 있도록 이러한 실패를 자동으로 재시도합니다.

### 백엔드 재시도 시나리오
<a name="durable-backend-retry-scenarios"></a>

다음 시나리오에서 Lambda는 함수를 자동으로 재시도합니다.
+ **내부 서비스 오류** - Lambda 또는 지속성 실행 서비스가 5xx 오류를 반환하여 일시적인 서비스 문제를 나타내는 경우.
+ **스로틀링** - 동시성 제한 또는 서비스 할당량으로 인해 함수가 스로틀링되는 경우.
+ **제한 시간 초과** - 제한 시간 내에 SDK가 지속성 실행 서비스에 연결할 수 없는 경우.
+ **샌드박스 초기화 실패** - Lambda가 실행 환경을 초기화할 수 없는 경우.
+ **런타임 오류** - Lambda 런타임에서 메모리 부족 오류 또는 프로세스 충돌과 같은 함수 코드 외부의 오류가 발생하는 경우.
+ **유효하지 않은 체크포인트 토큰 오류** - 일반적으로 서비스 측 상태 변경으로 인해 체크포인트 토큰이 더 이상 유효하지 않은 경우.

다음 표는 SDK에서 이러한 시나리오에 어떻게 대처하는지 설명합니다.


| 시나리오 | 발생한 상황 | 영향 측정 | 
| --- | --- | --- | 
| 지속성 핸들러 외부의 런타임 오류(OOM, 제한 시간 초과, 충돌) | Lambda는 호출을 자동으로 재시도합니다. SDK는 마지막 체크포인트에서 재생되고, 완료된 단계를 건너뜁니다. | 오류 페이로드 크기 \+ 재시도당 작업 1개 | 
| CheckpointDurableExecution/GetDurableExecutionState API를 호출할 때 서비스 오류(5xx) 또는 제한 시간 초과 | Lambda는 호출을 자동으로 재시도합니다. SDK는 마지막 체크포인트에서 재생됩니다. | 오류 페이로드 크기 \+ 재시도당 작업 1개 | 
| CheckpointDurableExecution/GetDurableExecutionState API를 호출할 때 스로틀링(429) 또는 잘못된 체크포인트 토큰 | Lambda는 지수 백오프를 포함한 간접 호출을 자동으로 재시도합니다. SDK는 마지막 체크포인트에서 재생됩니다. | 오류 페이로드 크기 \+ 재시도당 작업 1개 | 
| CheckpointDurableExecution/GetDurableExecutionState API에서 클라이언트 오류(4xx, 429 및 잘못된 토큰 제외) | SDK는 실행을 FAILED로 표시합니다. 오류가 영구적인 문제를 나타내므로 자동 재시도는 발생하지 않습니다. | 오류 페이로드 크기 | 

백엔드 재시도는 지수 백오프를 사용하고 함수가 성공하거나 실행 제한 시간에 도달할 때까지 계속합니다. 재생 중에 SDK는 함수가 완료된 작업을 다시 실행하지 않도록 완료된 체크포인트를 건너뛰고 마지막으로 성공한 작업에서 계속 실행합니다.

## 재시도 모범 사례
<a name="durable-retry-best-practices"></a>

재시도 전략을 구성할 때 다음 모범 사례를 따릅니다.
+ **명시적인 재시도 전략 구성** - 프로덕션 환경에서 기본 재시도 동작에 의존하지 않습니다. 사용 사례에 적합한 최대 시도 횟수와 백오프 간격으로 명시적 재시도 전략을 구성합니다.
+ **조건부 재시도 사용** - 일시적 오류(속도 제한, 제한 시간 초과)만 재시도하고 영구적 오류(검증 실패, 찾을 수 없음) 시에는 빠르게 실패하는 `shouldRetry` 로직을 구현합니다.
+ **적절한 최대 시도 횟수 설정** - 복원력과 실행 시간 사이의 균형을 맞춥니다. 재시도 횟수가 너무 많으면 실패 감지가 지연되는 반면, 재시도 횟수가 너무 적으면 불필요한 실패가 발생할 수 있습니다.
+ **지수 백오프 사용** - 지수 백오프는 다운스트림 서비스의 부하를 줄이고 일시적 실패로부터 복구할 가능성을 높입니다.
+ **단계에서 오류가 발생하기 쉬운 코드 래핑** - 단계 외부의 코드는 자동으로 재시도할 수 없습니다. 재시도 전략을 사용하여 외부 API 호출, 데이터베이스 쿼리 및 기타 오류가 발생하기 쉬운 작업을 단계별로 래핑합니다.
+ **재시도 지표 모니터링** - Amazon CloudWatch에서 단계 재시도 작업 및 실행 실패를 추적하여 패턴을 식별하고 재시도 전략을 최적화합니다.