일반적인 지속성 작업 SDK 문제 해결

이 문서는 이식 가능한 지속성 작업 SDK를 사용하여 애플리케이션을 빌드할 때 발생하는 일반적인 문제를 진단하고 해결하는 데 도움이 됩니다. 다음 목록에서 시나리오를 찾고 연결된 단계에 따라 문제를 진단하고 해결합니다.

일반적인 시나리오

연결 및 설정

오케스트레이션

활동

gRPC

로깅 및 진단

언어별

이러한 SDK는 Durable Task Scheduler 백 엔드에 연결하고 Azure Container Apps, Kubernetes 및 VM을 비롯한 모든 호스팅 플랫폼에서 실행됩니다.

메모

이 가이드에서는 이식 가능한 지속성 작업 SDK에 대해 설명합니다. 지속성 작업 스케줄러 서비스와 관련된 문제는 지속성 작업 스케줄러 문제 해결을 참조하세요. Durable Functions 확장과 관련된 문제는 Durable Functions 문제 해결 가이드 참조하세요.

팁 (조언)

지속성 작업 스케줄러 모니터링 대시보드는 오케스트레이션 상태를 검사하고 실행 기록을 보고 오류를 식별하는 데 유용합니다. 이 가이드와 함께 사용하여 문제 해결 속도를 높일 수 있습니다.

문제 찾기

오류 메시지 또는 증상 섹션
connection refused 또는 failed to connect 시작 시 에뮬레이터가 실행되고 있지 않거나 연결할 수 없음
시작 시 연결 문자열 구문 분석 오류 또는 인증 오류 연결 문자열 형식이 잘못되었습니다.
작업자 연결하지만 오케스트레이션이 시작되지 않음 작업 허브가 없습니다.
Azure에서 발생하는 아이디/역할 오류 401 Unauthorized Azure에서의 신원 기반 인증 실패
오케스트레이션이 "Pending" 상태에 정체됨 오케스트레이션이 "보류 중" 상태로 중단됨
오케스트레이션이 "Running" 상태에 정체됨 오케스트레이션이 "실행 중" 상태로 중단됨
재생 실패, 무한 루프 또는 예기치 않은 동작 비결정적 오케스트레이터 코드
형식 불일치 또는 JSON serialization 오류 직렬화 및 역직렬화 오류
activity not found 활동을 찾을 수 없음
RESOURCE_EXHAUSTED 또는 message too large gRPC 메시지 크기 제한을 초과했습니다.
CANCELLED: Cancelled on client 종료 중 종료 중 스트림 취소 오류
CS0419 / VSTHRD105 경고로 빌드가 중단됨 소스 생성기 경고로 빌드가 중단됨(C#)
OrchestratorBlockedException(Java) OrchestratorBlockedException(Java)
retry_policy(Python)를 사용하는 경우 도움이 되지 않는 오류 다시 시도 정책에는 max_retry_interval(Python)이 필요합니다

연결 및 설정 문제

에뮬레이터가 실행되고 있지 않거나 연결할 수 없음

"연결 거부됨" 또는 "연결 실패"와 같은 연결 오류로 시작 시 앱이 실패하는 경우 지속성 작업 스케줄러 에뮬레이터가 실행 중이고 액세스할 수 있는지 확인합니다.

  1. 에뮬레이터 Docker 컨테이너가 실행 중인지 확인합니다.

    docker ps | grep durabletask
    
  2. 포트 매핑이 올바른지 확인합니다. 에뮬레이터는 두 개의 포트를 노출합니다.

    • 8080 — gRPC 엔드포인트(앱에서 사용)
    • 8082 - 대시보드 UI

    사용자 지정 포트 매핑을 사용하는 경우 컨테이너 포트 8080 매핑된 호스트 포트와 일치하도록 연결 문자열 업데이트합니다.

  3. gRPC 엔드포인트에 대한 연결을 테스트합니다.

    curl -v http://localhost:8080
    

    연결 거부는 컨테이너가 실행되고 있지 않거나 포트 매핑이 잘못되었음을 나타냅니다.

연결 문자열 형식이 잘못되었습니다.

연결 문자열 오류는 시작 실패의 일반적인 원인입니다. 연결 문자열 예상 형식과 일치하는지 확인합니다.

로컬 개발 (에뮬레이터):

Endpoint=http://localhost:8080;Authentication=None

Azure(관리 ID):

Endpoint=https://<scheduler-name>.durabletask.io;Authentication=ManagedIdentity

Azure(사용자 할당 관리 ID):

Endpoint=https://<scheduler-name>.durabletask.io;Authentication=ManagedIdentity;ClientID=<client-id>

일반적인 실수:

  • 로컬 에뮬레이터에 사용 https (에뮬레이터에서 사용 http)
  • Azure 엔드포인트에 http 사용(Azure https 필요)
  • 매개 변수 생략 Authentication
  • gRPC 포트(8082) 대신 대시보드 포트(8080) 사용

클라이언트 또는 작업자가 연결하지 못함

클라이언트와 작업자가 올바른 연결 문자열 및 작업 허브 이름으로 구성되어 있는지 확인합니다.

using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;

var connectionString = "Endpoint=http://localhost:8080;Authentication=None";

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddDurableTaskWorker()
    .AddTasks(registry =>
    {
        registry.AddOrchestrator<MyOrchestrator>();
        registry.AddActivity<MyActivity>();
    })
    .UseDurableTaskScheduler(connectionString);

builder.Services.AddDurableTaskClient()
    .UseDurableTaskScheduler(connectionString);

작업 허브가 없습니다.

오케스트레이션을 시작하지 못하거나 작업자가 연결하지만 작업을 처리하지 않는 경우 작업 허브가 스케줄러에 없을 수 있습니다. 에뮬레이터는 일반적으로 환경 변수를 사용하여 자동으로 작업 허브를 DTS_TASK_HUB_NAMES 만듭니다.

에뮬레이터가 올바른 작업 허브 이름으로 시작되었는지 확인합니다.

docker run -d -p 8080:8080 -p 8082:8082 \
  -e DTS_TASK_HUB_NAMES="my-taskhub" \
  mcr.microsoft.com/dts/dts-emulator:latest

Azure 호스팅 스케줄러의 경우 Azure CLI 사용하여 작업 허브를 만듭니다.

az durabletask taskhub create \
  --resource-group <resource-group> \
  --scheduler-name <scheduler-name> \
  --name <taskhub-name>

Azure ID 기반 인증 실패

앱이 로컬로 실행되지만 Azure 배포할 때 실패하는 경우 문제는 인증과 관련이 있을 수 있습니다.

  1. 관리 ID가 앱에 할당되어 있는지 확인합니다(시스템 할당 또는 사용자 할당).
  2. ID에 스케줄러 리소스 또는 특정 작업 허브에 대한 지속성 작업 데이터 기여자 역할이 있는지 확인합니다.
  3. 연결 문자열 올바른 Authentication 값(ManagedIdentity)을 사용하는지 확인합니다. Python 연결 문자열 사용하는 대신 DefaultAzureCredential() 인스턴스를 token_credential 매개 변수로 전달합니다.
  4. 사용자 할당 ID의 경우, 연결 문자열의 ClientID이 해당 ID의 클라이언트 ID와 일치하는지 확인하십시오.

자세한 지침은 지속성 작업 스케줄러에 대한 관리 ID 구성을 참조하세요.

오케스트레이션 문제

오케스트레이션이 "보류 중" 상태로 중단됨

"보류 중" 상태의 오케스트레이션은 예약은 되었지만, 작업자가 아직 가져가지 않았음을 나타냅니다. 다음 항목을 확인합니다.

  • 작업자가 실행 중입니다. 작업자 프로세스가 실행 중이고 오케스트레이션이 예약된 동일한 작업 허브에 연결되어 있는지 확인합니다.
  • 작업 허브 이름이 일치합니다. 작업자와 클라이언트가 모두 동일한 작업 허브 이름을 참조하는지 확인합니다. 불일치가 발생하면 작업자가 다른 작업 허브를 폴링하게 됩니다.
  • 오케스트레이터가 등록됩니다. 예약 시 참조되는 오케스트레이터 함수 또는 클래스를 작업자에 등록해야 합니다.

시작 중에 오케스트레이터 클래스가 작업자에 등록되어 있는지 확인합니다. 원본 생성기([DurableTask] 특성)를 사용하는 경우 등록이 자동으로 수행됩니다. 그렇지 않으면 수동으로 등록합니다.

builder.Services.AddDurableTaskWorker()
    .AddTasks(registry =>
    {
        registry.AddOrchestrator<MyOrchestrator>();
        registry.AddActivity<MyActivity>();
    })
    .UseDurableTaskScheduler(connectionString);

오케스트레이션이 "실행 중" 상태로 중단됨

오케스트레이션이 "실행 중"에서 중단된 것은 일반적으로 완료되지 않은 작업을 기다리고 있음을 의미합니다. 진단하려면 지속성 작업 스케줄러 대시보드를 열고 오케스트레이션의 실행 기록을 검사합니다. 마지막으로 완료된 이벤트를 찾아보세요. 시퀀스에서 다음 이벤트는 차단되는 이벤트입니다.

일반적인 원인:

  • 활동이 등록되지 않았습니다. 오케스트레이션은 작업자에 등록되지 않은 활동 이름을 호출합니다. 관련된 TaskScheduled가 없는 TaskCompleted 이벤트가 대시보드에 표시됩니다. 작업 이름이 오케스트레이터 코드와 작업자 등록 간에 일치하는지 확인합니다( 활동을 찾을 수 없음 참조).
  • 외부 이벤트를 기다리고 있습니다. 오케스트레이션이 waitForExternalEvent를 호출하고 이벤트는 아직 발생하지 않았습니다. 대시보드에 이벤트가 예상되었지만 누락된 것으로 표시됩니다 EventRaised . 이벤트 이름과 발신자가 올바른 오케스트레이션 인스턴스 ID를 대상으로 지정하고 있는지 확인합니다.
  • 내구성 타이머를 기다리고 있습니다. 오케스트레이션은 아직 만료되지 않은 타이머를 만듭니다. 대시보드에 TimerCreated 이벤트가 표시됩니다. 타이머가 실행될 때까지 기다리거나 타이머 기간이 예상보다 긴지 확인합니다.
  • 작업이 처리되지 않은 예외를 발생시킵니다. 대시보드에 TaskFailed 이벤트가 표시됩니다. 예외 메시지 및 스택 추적에 대한 오류 세부 정보를 확인합니다.

비결정적 오케스트레이터 코드

오케스트레이터 코드는 결정적이어야 합니다. 비결정적 코드로 인해 재생 오류가 발생하여 예기치 않은 동작, 무한 루프 또는 오류가 발생합니다. 오케스트레이터 코드에서 직접 현재 시간, 난수, GUID 또는 I/O(예: HTTP 호출)를 사용하지 마세요. 컨텍스트 제공 대안을 사용하거나 활동에 위임합니다.

// ❌ Wrong - non-deterministic
var now = DateTime.UtcNow;
var id = Guid.NewGuid();
var data = await httpClient.GetAsync("https://example.com/api");

// ✅ Correct - deterministic
var now = context.CurrentUtcDateTime;
var id = context.NewGuid();
var data = await context.CallActivityAsync<string>("FetchData");

직렬화 및 역직렬화 오류

오케스트레이션 입력, 출력 또는 활동 결과에 사용되는 형식이 호출자와 호출자 간에 일치하지 않는 경우 serialization 오류가 발생합니다. 이러한 오류는 예기치 않은 null 값, JsonException 또는 오케스트레이션 기록에서의 형 변환 실패 형태로 나타날 수 있습니다.

진단 방법:

  1. 지속성 작업 스케줄러 대시보드를 열고 오케스트레이션 기록을 검사합니다. 실패한 활동의 InputResult 필드를 확인하십시오.
  2. 오케스트레이터에서 예상하는 형식이 작업에서 반환된 형식과 일치하는지 확인합니다. 예를 들어 작업이 string를 반환하지만 오케스트레이터가 int를 예상하는 경우 역직렬화가 실패합니다.
  3. 직렬화할 수 없는 형식을 확인합니다. JSON으로 직렬화할 수 없는 사용자 지정 형식(예: 순환 참조가 있거나 기본 생성자가 없는 형식)은 자동으로 실패하거나 예외를 throw합니다.

알려진 문제(Java):String을 활동에 바로 전달하면 쌍따옴표로 묶인 문자열이 생성될 수 있습니다(예: "\"hello\"" 대신 "hello"가 생성됨). 이 동작은 알려진 문제입니다. 결과를 명시적으로 형변환하거나 래퍼 객체를 사용하십시오.

팁 (조언)

오케스트레이션 및 작업 입력 및 출력에는 간단한 데이터 형식(문자열, 숫자, 배열 및 일반 개체 또는 POJO/POCO/데이터 클래스)을 사용합니다. 복잡한 형식과 사용자 지정 직렬화 논리의 사용을 피하십시오.

활동 문제

활동을 찾을 수 없음

"활동을 찾을 수 없음" 오류로 오케스트레이션이 실패하는 경우 작업자에 등록된 활동 이름이 오케스트레이션 코드에 사용된 이름과 일치하지 않습니다.

.NET 활동은 클래스 이름으로 등록하거나 원본 생성기와 함께 [DurableTask] 특성을 사용하여 등록할 수 있습니다. 작업 클래스가 작업자 등록에 포함되어 있는지 확인합니다.

builder.Services.AddDurableTaskWorker()
    .AddTasks(registry =>
    {
        registry.AddActivity<SayHello>();
    })
    .UseDurableTaskScheduler(connectionString);

오케스트레이터에서 활동을 호출할 때 클래스 이름을 사용합니다.

string result = await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo");

활동 오류 처리

작업에서 예외가 발생하면 오케스트레이터는 TaskFailedException(또는 언어 동등 항목)을(를) 수신합니다. 이 예외를 처리하고 내부 오류 세부 정보를 확인해 근본 원인을 파악하세요. C#에서 오류 유형 및 메시지에 액세스하고 ex.FailureDetails 특정 예외 유형을 확인하는 데 사용합니다IsCausedBy<T>().

각 언어의 자세한 오류 처리 및 재시도 정책 예제는 오류 처리 및 다시 시도를 참조하세요.

gRPC 문제

gRPC 메시지 크기 제한을 초과했습니다.

RESOURCE_EXHAUSTED 또는 message too large 오류가 발생하면 오케스트레이션 또는 작업 입력/출력이 gRPC 기본 최대 메시지 크기인 4MB를 초과합니다.

완화:

  • 입력 및 출력의 크기를 줄입니다. Azure Blob Storage 같은 대용량 페이로드를 외부 스토리지에 저장하고 참조만 전달합니다.
  • 대규모 팬아웃 결과는 하위 오케스트레이션을 통해 처리되는 더 작은 배치로 나누십시오.

종료 중 스트림 취소 오류

작업자를 중지할 때 CANCELLED: Cancelled on client 오류가 표시될 수 있습니다. 이러한 오류는 일반적으로 무해하며 종료 중에 작업자와 스케줄러 간의 gRPC 스트림이 닫히기 때문에 발생합니다. .NET, Python 및 Java SDK는 이러한 오류를 내부적으로 처리합니다.

JavaScript에서 SDK는 Stream error Error: 1 CANCELLED: Cancelled on client을 호출할 때 worker.stop()를 던질 수 있습니다. 이 오류는 알려진 문제입니다. try-catch에서 중지 호출을 래핑하여 오류가 종료 논리에 영향을 미칠 경우에 대비하십시오.

try {
  await worker.stop();
} catch (error) {
  // Ignore stream cancellation errors during shutdown
  if (!error.message.includes("CANCELLED")) {
    throw error;
  }
}

로깅 및 진단

자세한 로깅 구성

로그 세부 정보를 늘려 gRPC 통신 및 오케스트레이션 재생 이벤트를 포함하여 SDK 작업에 대한 자세한 정보를 가져옵니다.

appsettings.json 구성 또는 로깅 설정 파일에서:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.DurableTask": "Debug"
    }
  }
}

오케스트레이션 재생 중에 로그 항목이 중복되지 않도록 재생 안전 로거를 사용합니다.

public override async Task<string> RunAsync(
    TaskOrchestrationContext context, string input)
{
    ILogger logger = context.CreateReplaySafeLogger<MyOrchestrator>();
    logger.LogInformation("Processing input: {Input}", input);
    // ...
}

Application Insights 통합

프로덕션 애플리케이션의 경우 지속성 작업 SDK 애플리케이션에서 원격 분석을 수집하도록 Application Insights 를 구성합니다. 통합 방법은 호스팅 플랫폼에 따라 달라집니다.

호스팅 플랫폼 설치 지침
Azure Container Apps (Azure 컨테이너 애플리케이션) Azure Container Apps에서 Log Analytics로 로그를 모니터링하기
Azure App Service Azure App Service 앱에 진단 로깅을 활성화합니다
Azure Kubernetes Service Azure Kubernetes Service를 모니터링하기

진단에 대한 자세한 내용은 지속성 작업 SDK의 진단을 참조하세요.

언어별 문제

C#

소스 생성기 경고가 빌드를 실패로 만듭니다.

프로젝트에서 사용하는 <TreatWarningsAsErrors>true</TreatWarningsAsErrors> 경우 지속성 작업 원본 생성기에서 빌드를 중단하는 경고(CS0419, VSTHRD105)를 생성할 수 있습니다. 다음과 같은 특정 경고를 표시하지 않습니다.

<PropertyGroup>
  <NoWarn>$(NoWarn);CS0419;VSTHRD105</NoWarn>
</PropertyGroup>

이 알려진 문제는 GitHub 향후 릴리스에서 해결됩니다.

Roslyn 분석기는 foreach 루프에서 예외를 발생시킵니다.

지속형 작업 Roslyn 분석기는 오케스트레이터 람다 코드가 ArgumentNullException 루프 내부에 있을 때 foreach을(를) throw할 수 있습니다. 이 동작은 런타임 동작에 영향을 주지 않는 알려진 문제 입니다. 최신 분석기 패키지 버전으로 업데이트하여 수정합니다.

java

Gradle 권한 거부 오류

macOS 또는 Linux에서 "사용 권한 거부" 오류로 실행 ./gradlew 이 실패할 수 있습니다. 파일을 실행 가능하게 하여 이 오류를 수정합니다.

chmod +x gradlew

OrchestratorBlockedException

오케스트레이터 코드에서 SDK가 잠재적으로 비결정적일 수 있는 차단 작업을 수행하면 OrchestratorBlockedException이 발생합니다. 이 예외는 오케스트레이터 코드가 오케스트레이터 코드 제약 조건을 위반하지 않도록 방지하는 보호 장치입니다.

일반적인 원인:

  • 오케스트레이터 함수 코드에서 블로킹 외부 API 호출
  • Thread.sleep()을(를) ctx.createTimer() 대신에 직접 사용.
  • 오케스트레이터 코드에서 파일 또는 네트워크 I/O를 수행합니다.

모든 차단 또는 I/O 작업을 활동으로 이동합니다.

파이썬

재시도 정책에는 max_retry_interval 필요합니다.

Python retry_policy 구성하면 max_retry_interval 매개 변수를 생략하면 원인을 명확하게 나타내지 않는 오류가 발생합니다. 항상 max_retry_interval을(를) 지정하십시오.

from datetime import timedelta
from durabletask import task

retry_policy = task.RetryPolicy(
    max_number_of_attempts=3,
    first_retry_interval=timedelta(seconds=5),
    max_retry_interval=timedelta(minutes=1),  # Required
)

WhenAllTask 예외 동작

여러 작업을 병렬로 실행하는 데 사용하는 when_all 경우 하나 이상의 작업이 실패하면 예외 동작이 예상과 일치하지 않을 수 있습니다. 첫 번째 예외만 발생하고 나머지 작업 예외가 손실될 수 있습니다. 전체 오류 정보가 필요한 경우 개별 작업 결과를 검사합니다.

tasks = [ctx.call_activity(process_item, input=item) for item in items]
try:
    results = yield task.when_all(tasks)
except TaskFailedError as e:
    # Only the first failure is raised
    # Check individual tasks for comprehensive error handling
    print(f"At least one task failed: {e}")

지원받기

질문 및 보고 버그의 경우 관련 SDK에 대한 GitHub 리포지토리에서 문제를 엽니다. 버그를 보고하는 경우 다음을 포함합니다.

  • 영향을 받는 오케스트레이션 인스턴스 ID
  • 문제를 보여 주는 UTC의 시간 범위
  • 애플리케이션 이름 및 배포 지역(관련된 경우)
  • SDK 버전 및 호스팅 플랫폼
  • 관련 로그 또는 오류 메시지
SDK GitHub 리포지토리
.NET microsoft/durabletask-dotnet
java microsoft/durabletask-java
JavaScript microsoft/durabletask-js
파이썬 microsoft/durabletask-python