파이프 및 필터 패턴

Azure Blob Storage
Azure 기능
Azure Queue Storage

복잡한 처리를 수행하는 작업을 다시 사용할 수 있는 일련의 개별 요소로 분해합니다. 이렇게 하면 처리를 수행하는 작업 요소를 독립적으로 배포 및 확장할 수 있도록 하여 성능, 확장성 및 재사용성을 향상시킬 수 있습니다.

컨텍스트 및 문제점

처리해야 하는 순차 작업의 파이프라인이 있습니다. 이 애플리케이션을 구현하는 간단하지만 유연하지 않은 접근 방식은 모놀리식 모듈에서 이 처리를 수행하는 것입니다. 그러나 이 방법은 애플리케이션의 다른 위치에서 동일한 처리 부분이 필요한 경우 코드를 리팩터링하거나 최적화하거나 재사용할 기회를 줄일 수 있습니다.

다음 다이어그램에서는 모놀리식 접근 방식을 사용하여 데이터를 처리하는 문제 중 하나인 여러 파이프라인에서 코드를 다시 사용할 수 없는 문제를 보여 줍니다. 이 예제에서 애플리케이션은 두 원본에서 데이터를 수신하고 처리합니다. 별도의 모듈은 애플리케이션의 비즈니스 논리에 결과를 전달하기 전에 데이터를 변환하는 일련의 작업을 수행하여 각 원본의 데이터를 처리합니다.

모놀리식 모듈로 구현된 솔루션을 보여 주는 다이어그램

모놀리식 모듈에서 수행하는 일부 작업은 기능적으로 유사하지만 두 모듈에서 코드를 반복해야 하며 모듈 내에서 긴밀하게 결합될 수 있습니다. 이 방법은 논리를 다시 사용할 수 없는 것 외에도 요구 사항이 변경될 때 위험을 초래합니다. 두 위치에서 코드를 업데이트해야 합니다.

여러 파이프라인 또는 재사용과 관련이 없는 모놀리식 구현의 다른 문제가 있습니다. 모놀리식에서는 다른 환경에서 특정 작업을 실행하거나 독립적으로 크기를 조정할 수 없습니다. 일부 작업은 계산 집약적일 수 있으며 강력한 하드웨어에서 실행하거나 여러 인스턴스를 병렬로 실행하는 것이 좋습니다. 다른 작업에는 동일한 요구 사항이 없을 수 있습니다. 또한 모놀리스를 사용하면 작업 순서를 다시 지정하거나 파이프라인에 새 작업을 삽입하는 것이 어렵습니다. 이러한 변경 내용을 변경하려면 전체 파이프라인을 다시 지정해야 합니다.

솔루션

각 스트림에 필요한 처리를 각각 단일 작업을 수행하는 일련의 독립 구성 요소(또는 필터)로 나눕니다. 필터는 파이프와 필터를 연결하여 파이프라인으로 구성됩니다. 필터는 인바운드 파이프에서 메시지를 수신하고 다른 아웃바운드 파이프에 메시지를 게시합니다. 파이프는 라우팅 또는 다른 논리를 수행하지 않습니다. 필터만 연결하여 한 필터의 출력 메시지를 입력으로 다음 필터로 전달합니다.

필터는 독립적으로 작동하며 다른 필터를 인식하지 못합니다. 입력 및 출력 스키마만 알고 있습니다. 따라서 필터의 입력 스키마가 이전 필터의 출력 스키마와 일치하는 한 필터를 순서대로 정렬할 수 있습니다. 모든 필터에 표준화된 스키마를 사용하면 필터의 순서를 다시 지정하는 기능이 향상됩니다.

필터를 느슨하게 결합하면 다음을 쉽게 수행할 수 있습니다.

  • 기존 필터로 구성된 새 파이프라인 만들기
  • 개별 필터에서 논리 업데이트 또는 바꾸기
  • 필요한 경우 필터 순서 다시 지정
  • 필요한 경우 다른 하드웨어에서 필터 실행
  • 병렬로 필터 실행

이 다이어그램은 파이프 및 필터로 구현된 솔루션을 보여 줍니다.

파이프 및 필터로 구현된 솔루션을 보여 주는 다이어그램

단일 요청을 처리하는 데 걸리는 시간은 파이프라인에서 가장 느린 필터의 속도에 따라 달라집니다. 특히 특정 데이터 원본의 스트림에 많은 수의 요청이 표시되는 경우 하나 이상의 필터가 병목 상태일 수 있습니다. 느린 필터의 병렬 인스턴스를 실행하는 기능을 통해 시스템은 부하를 분산하고 처리량을 향상시킬 수 있습니다.

다양한 컴퓨팅 인스턴스에서 필터를 실행하는 기능을 사용하면 독립적으로 크기를 조정하고 많은 클라우드 환경에서 제공하는 탄력성을 활용할 수 있습니다. 계산 집약적인 필터는 고성능 하드웨어에서 실행할 수 있으며, 덜 까다로운 다른 필터는 저렴한 상용 하드웨어에서 호스트할 수 있습니다. 필터는 동일한 데이터 센터 또는 지리적 위치에 있을 필요가 없으므로 파이프라인의 각 요소가 필요한 리소스에 가까운 환경에서 실행될 수 있습니다. 이 다이어그램은 원본 1의 데이터에 대한 파이프라인에 적용된 예제를 보여줍니다.

원본 1의 데이터에 대한 파이프라인에 적용된 예제를 보여 주는 다이어그램

필터의 입력 및 출력이 스트림으로 구성된 경우 각 필터에 대한 처리를 병렬로 수행할 수 있습니다. 파이프라인의 첫 번째 필터는 작업을 시작하고 결과를 출력할 수 있으며, 이 필터는 첫 번째 필터가 작업을 완료하기 전에 시퀀스의 다음 필터에 직접 전달됩니다.

보상 트랜잭션 패턴과 함께 파이프 및 필터 패턴을 사용하는 것은 분산 트랜잭션을 구현하는 대체 방법입니다. 분산 트랜잭션을 별도의 보상 가능한 작업으로 분할할 수 있으며, 각 작업은 보상 트랜잭션 패턴을 구현하는 필터를 통해 구현할 수 있습니다. 파이프라인의 필터를 기본 데이터 가까이에서 실행되는 별도의 호스트된 작업으로 구현할 수 있습니다.

문제 및 고려 사항

이 패턴을 구현하는 방법을 결정할 때 다음 사항을 고려합니다.

  • 복잡성. 특히 파이프라인의 필터가 여러 서버에 분산되어 있는 경우 이 패턴이 제공하는 유연성이 증가하면 복잡성이 발생할 수 있습니다.

  • 신뢰성. 파이프의 필터 간에 흐르는 데이터가 손실되지 않도록 하는 인프라를 사용합니다.

  • 멱등성. 메시지를 받은 후 파이프라인의 필터가 실패하고 작업이 필터의 다른 인스턴스로 다시 예약된 경우 작업의 일부가 이미 완료되었을 수 있습니다. 작업이 전역 상태의 일부 측면(예: 데이터베이스에 저장된 정보)을 업데이트하는 경우 단일 업데이트가 반복될 수 있습니다. 필터가 결과를 다음 필터에 게시한 후 작업을 성공적으로 완료했음을 나타내기 전에 필터가 실패하는 경우에도 비슷한 문제가 발생할 수 있습니다. 이러한 경우 필터의 다른 인스턴스가 이 작업을 반복하여 동일한 결과가 두 번 게시될 수 있습니다. 이 시나리오는 파이프라인에서 동일한 데이터를 두 번 처리하는 후속 필터가 발생할 수 있습니다. 따라서 파이프라인의 필터는 idempotent로 설계되어야 합니다. 자세한 내용은 Jonathan Oliver 블로그에서 멱등성 패턴을 참조하세요.

  • 반복 메시지. 파이프라인의 필터가 파이프라인의 다음 단계에 메시지를 게시한 후 실패하는 경우 필터의 다른 인스턴스가 실행될 수 있으며 동일한 메시지의 복사본을 파이프라인에 게시합니다. 이 시나리오에서는 동일한 메시지의 두 인스턴스가 다음 필터에 전달될 수 있습니다. 이 문제를 방지하려면 파이프라인에서 중복 메시지를 검색하고 제거해야 합니다.

    참고 항목

    메시지 큐(예: Azure Service Bus 큐)를 사용하여 파이프라인을 구현하는 경우 메시지 큐 인프라는 자동 중복 메시지 검색 및 제거를 제공할 수 있습니다.

  • 컨텍스트 및 상태입니다. 파이프라인에서 각 필터는 기본적으로 분리되어 실행되며 호출된 방식에 대한 가정을 해서는 안 됩니다. 따라서 각 필터는 해당 작업을 수행하기에 충분한 컨텍스트를 제공해야 합니다. 이 컨텍스트에는 상당한 양의 상태 정보가 포함될 수 있습니다. 필터가 데이터베이스 또는 외부 스토리지의 데이터와 같은 외부 상태를 사용하는 경우 성능에 미치는 영향을 고려해야 합니다. 모든 필터는 해당 상태를 로드, 작동 및 유지해야 하며, 이로 인해 외부 상태를 한 번 로드하는 솔루션에 오버헤드가 추가됩니다.

  • 메시지 허용 오차입니다. 필터는 들어오는 메시지에서 작동하지 않는 데이터에 대해 관대해야 합니다. 해당 데이터에 대해 작동하고 다른 데이터를 무시하고 출력 메시지에서 변경되지 않은 상태로 전달합니다.

  • 오류 처리 - 모든 필터는 호환성이 손상되는 오류가 발생할 경우 수행할 작업을 결정해야 합니다. 필터는 파이프라인에 실패하는지 또는 예외를 전파하는지 확인해야 합니다.

이 패턴을 사용해야 하는 경우

다음 경우에 이 패턴을 사용합니다.

  • 애플리케이션에 필요한 처리는 일련의 독립된 단계로 쉽게 나눌 수 있는 경우

  • 애플리케이션에서 수행하는 처리 단계는 확장성 요구 사항이 서로 다른 경우

    참고 항목

    동일한 프로세스에서 함께 확장해야 하는 필터를 그룹화할 수 있습니다. 자세한 내용은 컴퓨팅 리소스 통합 패턴을 참조하세요.

  • 애플리케이션이 수행하는 처리 단계의 순서를 다시 정렬하거나 기능을 통해 단계를 추가하고 제거할 수 있는 유연성이 필요합니다.

  • 여러 서버에 걸친 단계 처리를 배포함으로써 시스템이 장점을 취할 수 있는 경우

  • 데이터를 처리하는 동안 한 단계에서 오류의 영향을 최소화하는 신뢰할 수 있는 솔루션이 필요합니다.

다음의 경우에는 이 패턴이 유용하지 않습니다.

  • 애플리케이션은 요청-응답 패턴을 따릅니다.

  • 작업 처리는 요청/응답 시나리오와 같은 초기 요청의 일부로 완료되어야 합니다.

  • 애플리케이션에서 수행하는 처리 단계는 독립적이지 않거나 단일 트랜잭션의 일부로 함께 수행해야 합니다.

  • 단계에 필요한 컨텍스트 또는 상태 정보의 양은 이 접근 방식을 비효율적으로 만듭니다. 데이터베이스에 상태 정보를 유지할 수 있지만 데이터베이스의 추가 로드로 인해 과도한 경합이 발생하는 경우 이 전략을 사용하지 마세요.

워크로드 디자인

설계자는 파이프 및 필터 패턴을 워크로드 디자인에 사용하여 Azure Well-Architected Framework 핵심 요소에서 다루는 목표와 원칙을 해결하는 방법을 평가해야 합니다. 예시:

핵심 요소 이 패턴이 핵심 목표를 지원하는 방법
안정성 디자인 결정은 워크로드가 오작동에 대한 복원력을 갖도록 하고 오류가 발생한 후 완전히 작동하는 상태로 복구 되도록 하는 데 도움이 됩니다. 각 단계의 단일 책임은 집중된 주의를 끌 수 있게 하고 데이터 처리가 방해되는 것을 방지합니다.

- RE:01 단순성
- RE:07 백그라운드 작업

디자인 결정과 마찬가지로 이 패턴으로 도입될 수 있는 다른 핵심 요소의 목표에 대한 절충을 고려합니다.

예시

일련의 메시지 큐를 사용하여 파이프라인을 구현하는 데 필요한 인프라를 제공할 수 있습니다. 초기 메시지 큐는 파이프의 시작이 되는 처리되지 않은 메시지를 수신하고 패턴 구현을 필터링합니다. 필터 작업으로 구현된 구성 요소는 이 큐의 메시지를 수신 대기하고, 작업을 수행한 다음, 새 메시지 또는 변환된 메시지를 시퀀스의 다음 큐에 게시합니다. 다른 필터 작업은 파이프 및 필터 프로세스를 종료하는 마지막 단계까지 이 큐의 메시지를 수신 대기하고, 처리하고, 결과를 다른 큐에 게시하는 등의 작업을 수행할 수 있습니다. 이 다이어그램은 메시지 큐를 사용하는 파이프라인을 보여 줍니다.

메시지 큐를 사용하는 파이프라인을 보여 주는 다이어그램

이 패턴을 사용하여 이미지 처리 파이프라인을 구현할 수 있습니다. 워크로드가 이미지를 사용하는 경우 이미지는 다음과 같은 작업을 수행하기 위해 크게 독립적이고 순서가 다시 정렬 가능한 일련의 필터를 통과할 수 있습니다.

  • 콘텐츠 조정
  • 크기 조정
  • 워터 마크
  • 재교육
  • Exif 메타데이터 제거
  • CDN(콘텐츠 배달 네트워크) 게시

이 예제에서는 필터를 개별적으로 배포된 Azure Functions 또는 각 필터를 격리된 배포로 포함하는 단일 Azure Function 앱으로 구현할 수 있습니다. Azure Function 트리거, 입력 바인딩 및 출력 바인딩을 사용하면 필터 코드를 간소화하고 처리할 이미지에 검사 클레임을 사용하여 큐 기반 파이프에서 자동으로 작동할 수 있습니다.

일련의 Azure Functions 간에 Azure Queue Storage를 사용하는 이미지 처리 파이프라인을 보여 주는 다이어그램

다음은 Azure Function으로 구현된 하나의 필터가 이미지에 대한 클레임 검사를 사용하여 Queue Storage 파이프에서 트리거되고 다른 Queue Storage 파이프에 새 클레임 검사 작성하는 예제입니다. 간단히 하기 위해 주석에서 구현을 의사 코드로 대체했습니다. 이와 같은 더 많은 코드는 GitHub에서 사용할 수 있는 파이프 및 필터 패턴 의 데모에서 찾을 수 있습니다.

// This is the "Resize" filter. It handles claim checks from input pipe, performs the
// resize work, and places a claim check in the next pipe for anther filter to handle.
[Function(nameof(ResizeFilter))]
[QueueOutput("pipe-fjur", Connection = "pipe")]  // Destination pipe claim check
public async Task<string> RunAsync(
  [QueueTrigger("pipe-xfty", Connection = "pipe")] string imageFilePath,  // Source pipe claim check
  [BlobInput("{QueueTrigger}", Connection = "pipe")] BlockBlobClient imageBlob)  // Image to process
{
  _logger.LogInformation("Processing image {uri} for resizing.", imageBlob.Uri);

  // Idempotency checks
  // ...

  // Download image based on claim check in queue message body
  // ...
  
  // Resize the image
  // ...

  // Write resized image back to storage
  // ...

  // Create claim check for image and place in the next pipe
  // ...
  
  _logger.LogInformation("Image resizing done or not needed. Adding image {filePath} into the next pipe.", imageFilePath);
  return imageFilePath;
}

참고 항목

Spring Integration Framework에는 파이프 및 필터 패턴의 구현이 있습니다.

다음 단계

이 패턴을 구현할 때 다음 리소스가 유용할 수 있습니다.

다음 패턴은 이 패턴을 구현할 때도 관련이 있을 수 있습니다.

  • 클레임 확인 패턴입니다. 큐를 사용하여 구현된 파이프라인은 필터를 통해 전송되는 실제 항목이 아니라 처리해야 하는 데이터에 대한 포인터를 포함할 수 있습니다. 이 예제에서는 Azure Blob Storage에 저장된 이미지에 대해 Azure Queue Storage의 클레임 검사 사용합니다.
  • 경쟁 소비자 패턴 파이프라인에는 하나 이상의 필터의 여러 인스턴스가 포함될 수 있습니다. 이 방법은 느린 필터의 병렬 인스턴스를 실행하는 데 유용합니다. 이를 통해 시스템은 부하를 분산하고 처리량을 개선할 수 있습니다. 필터의 각 인스턴스는 다른 인스턴스와 입력을 위해 경쟁하지만 필터의 두 인스턴스는 동일한 데이터를 처리할 수 없습니다. 이 문서에서는 이 방법을 설명합니다.
  • 컴퓨팅 리소스 통합 패턴. 단일 프로세스로 함께 확장해야 하는 필터를 그룹화할 수 있습니다. 이 문서에서는 이 전략의 이점과 장단점에 대한 자세한 정보를 제공합니다.
  • 보상 트랜잭션 패턴. 필터를 되돌릴 수 있거나 오류가 있는 경우 상태를 이전 버전으로 복원하는 보상 작업이 있는 작업으로 구현할 수 있습니다. 이 문서에서는 이 패턴을 구현하여 최종 일관성을 달성하거나 기본 구현하는 방법을 설명합니다.
  • 파이프 및 필터 - 엔터프라이즈 통합 패턴.