게이트웨이 집계 패턴

Azure Traffic Manager

게이트웨이를 사용하여 여러 개별 요청을 단일 요청으로 집계합니다. 이 패턴은 클라이언트가 작업을 수행하기 위해 다른 백 엔드 시스템을 여러 차례 호출해야 하는 경우에 유용합니다.

컨텍스트 및 문제점

단일 작업을 수행하기 위해 클라이언트가 다양한 백 엔드 서비스를 여러 번 호출해야 할 수 있습니다. 여러 서비스를 사용하여 작업을 수행하는 애플리케이션은 각 요청마다 리소스를 확장해야 합니다. 새 기능 또는 서비스가 애플리케이션에 추가되면 추가 요청이 필요하며 리소스 요구 사항 및 네트워크 호출이 더욱 증가합니다. 클라이언트와 백 엔드 간의 이러한 대화는 애플리케이션의 성능과 규모에 부정적인 영향을 줄 수 있습니다. 여러 소규모 서비스로 빌드된 애플리케이션은 자연히 서비스 간 호출이 많으므로 마이크로 서비스 아키텍처에서는 이 문제가 보다 일반적입니다.

다음 다이어그램에서 클라이언트는 각 서비스(1,2,3)에 요청을 보냅니다. 각 서비스는 요청을 처리하고 응답을 애플리케이션으로 다시 보냅니다(4,5,6). 일반적으로 대기 시간이 긴 셀룰러 네트워크를 통해 이러한 방식으로 개별 요청을 사용하는 것은 비효율적이며 연결이 끊어지거나 요청이 불완전할 수 있습니다. 각 요청은 병렬로 수행될 수 있지만 애플리케이션은 각 요청에 대한 데이터를 보내고, 대기하고, 처리해야 하며, 모두 별도의 연결에서 실패할 가능성이 높아져야 합니다.

게이트웨이 집계 패턴에 대한 문제 다이어그램

솔루션

게이트웨이를 사용하여 클라이언트와 서비스 간의 대화 시간을 줄입니다. 게이트웨이는 클라이언트 요청을 수신하고, 다양한 백 엔드 시스템에 요청을 디스패치한 다음, 결과를 집계하고 요청 클라이언트로 다시 보냅니다.

이 패턴은 애플리케이션이 백 엔드 서비스를 만드는 요청 수를 줄이고 대기 시간이 긴 네트워크를 통해 애플리케이션 성능을 향상시킬 수 있습니다.

다음 다이어그램에서는 애플리케이션이 게이트웨이에 요청을 보냅니다(1). 요청에는 추가 요청 패키지가 포함되어 있습니다. 게이트웨이는 이를 분해하여 각 요청을 관련 서비스로 전송 처리합니다(2). 각 서비스는 게이트웨이에 응답을 반환합니다(3). 게이트웨이는 각 서비스의 응답을 결합하고 애플리케이션에 응답을 보냅니다(4). 애플리케이션은 단일 요청을 수행하고 게이트웨이에서 단일 응답만 수신합니다.

게이트웨이 집계 패턴에 대한 솔루션 다이어그램

문제 및 고려 사항

  • 게이트웨이는 백 엔드 서비스 간에 서비스 결합을 도입해서는 안 됩니다.
  • 대기 시간을 최대한 줄이려면 게이트웨이가 백 엔드 서비스 근처에 있어야 합니다.
  • 게이트웨이 서비스는 단일 실패 지점을 도입할 수 있습니다. 게이트웨이가 애플리케이션의 가용성 요구 사항을 충족하도록 올바르게 설계되었는지 확인합니다.
  • 게이트웨이에서 병목 현상이 발생할 수 있습니다. 게이트웨이가 부하를 처리하기에 적절한 성능을 갖추었으며 예상된 증가에 맞게 크기를 조정할 수 있는지 확인합니다.
  • 게이트웨이 부하 테스트를 수행하여 서비스가 연속으로 실패하지 않도록 합니다.
  • 격벽, 회로 분리, 재시도 및 시간 제한과 같은 기술을 사용하여 복원력 있는 디자인을 구현합니다.
  • 하나 이상의 서비스 호출이 너무 오래 걸리는 경우 시간 초과를 허용하고 일부 데이터 집합을 반환하는 것이 허용될 수 있습니다. 애플리케이션에서 이 시나리오를 처리하는 방법을 고려합니다.
  • 비동기 I/O를 사용하여 백 엔드의 지연이 애플리케이션에서 성능 문제를 야기하지 않도록 합니다.
  • 상관 관계 ID를 사용한 자동 분산 추적을 구현하여 각 개별 호출을 추적합니다.
  • 요청 메트릭 및 응답 크기를 모니터링합니다.
  • 캐시된 데이터를 장애 조치(failover) 전략으로 반환하여 오류를 처리하는 것이 좋습니다.
  • 게이트웨이에 집계를 빌드하는 대신 게이트웨이 뒤에 집계 서비스를 배치하는 것이 좋습니다. 요청 집계는 게이트웨이의 다른 서비스와 리소스 요구 사항이 다를 수 있으며 게이트웨이의 라우팅 및 오프로드 기능에 영향을 줄 수 있습니다.

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

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

  • 클라이언트는 작업을 수행하기 위해 여러 백 엔드 서비스와 통신해야 합니다.
  • 클라이언트는 셀룰러 네트워크와 같이 대기 시간이 긴 네트워크를 사용할 수 있습니다.

이 패턴은 다음과 같은 경우에 적합하지 않을 수 있습니다.

  • 여러 작업에서 클라이언트와 단일 서비스 간의 호출 수를 줄이려고 합니다. 이 시나리오에서는 서비스에 일괄 처리 작업을 추가하는 것이 나을 수 있습니다.
  • 클라이언트 또는 애플리케이션은 백 엔드 서비스 근처에 있으며 대기 시간은 중요한 요소가 아닙니다.

워크로드 디자인

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

핵심 요소 이 패턴이 핵심 목표를 지원하는 방법
안정성 디자인 결정은 워크로드가 오작동에 대한 복원력을 갖도록 하고 오류가 발생한 후 완전히 작동하는 상태로 복구 되도록 하는 데 도움이 됩니다. 이 토폴로지에서는 무엇보다도 일시적인 오류 처리를 클라이언트의 분산 구현에서 중앙 집중식 구현으로 전환할 수 있습니다.

- RE:07 일시적인 오류
보안 디자인 결정은 워크로드의 데이터 및 시스템의 기밀성, 무결성가용성을 보장하는 데 도움이 됩니다. 이 토폴로지는 종종 클라이언트가 시스템에 있는 터치 포인트 수를 줄여 공용 노출 영역 및 인증 지점을 줄입니다. 집계된 백 엔드는 클라이언트에서 완전히 네트워크 격리된 상태를 유지할 수 있습니다.

- SE:04 구분
- SE:08 강화
운영 우수성은 표준화된 프로세스와 팀 응집력을 통해 워크로드 품질을 제공하는 데 도움이 됩니다. 이 패턴을 사용하면 백 엔드 논리가 클라이언트와 독립적으로 발전할 수 있으므로 클라이언트 터치포인트를 변경하지 않고도 연결된 서비스 구현 또는 데이터 원본을 변경할 수 있습니다.

- OE:04 도구 및 프로세스
성능 효율성은 크기 조정, 데이터, 코드의 최적화를 통해 워크로드가 수요를 효율적으로 충족하는 데 도움이 됩니다. 이 디자인은 클라이언트가 여러 연결을 설정하는 디자인보다 대기 시간이 적을 수 있습니다. 집계 구현의 캐싱은 백 엔드 시스템에 대한 호출을 최소화합니다.

- PE:03 서비스 선택
- PE:08 데이터 성능

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

예시

다음 예제에서는 Lua를 사용하여 간단한 게이트웨이 집계 NGINX 서비스를 만드는 방법을 보여 줍니다.

worker_processes  4;

events {
  worker_connections 1024;
}

http {
  server {
    listen 80;

    location = /batch {
      content_by_lua '
        ngx.req.read_body()

        -- read json body content
        local cjson = require "cjson"
        local batch = cjson.decode(ngx.req.get_body_data())["batch"]

        -- create capture_multi table
        local requests = {}
        for i, item in ipairs(batch) do
          table.insert(requests, {item.relative_url, { method = ngx.HTTP_GET}})
        end

        -- execute batch requests in parallel
        local results = {}
        local resps = { ngx.location.capture_multi(requests) }
        for i, res in ipairs(resps) do
          table.insert(results, {status = res.status, body = cjson.decode(res.body), header = res.header})
        end

        ngx.say(cjson.encode({results = results}))
      ';
    }

    location = /service1 {
      default_type application/json;
      echo '{"attr1":"val1"}';
    }

    location = /service2 {
      default_type application/json;
      echo '{"attr2":"val2"}';
    }
  }
}