속도 제한 패턴

Azure Service Bus
Azure Queue Storage
Azure Event Hubs

많은 서비스가 제한 패턴을 통해 사용하는 리소스를 제어하여 다른 애플리케이션 또는 서비스에서 액세스할 수 있는 속도를 제한합니다. 속도 제한 패턴을 사용하면 이러한 제한과 관련된 제한 오류를 방지하거나 최소화하고 처리량을 보다 정확하게 예측할 수 있습니다.

속도 제한 패턴은 많은 시나리오에 적합하지만 일괄 처리와 같은 대규모 반복 자동화 작업에 특히 유용합니다.

컨텍스트 및 문제점

제한된 서비스를 사용하여 많은 수의 작업을 수행하면 거부된 요청을 추적한 다음 해당 작업을 다시 시도해야 하므로 트래픽 및 처리량이 증가할 수 있습니다. 작업 수가 증가함에 따라 제한에 여러 번의 데이터 재전송 패스가 필요할 수 있으므로 성능에 더 큰 영향을 미칠 수 있습니다.

예를 들어 Azure Cosmos DB로 데이터를 수집하기 위한 오류 프로세스에서 다음과 같은 순수한 재시도를 고려합니다.

  1. 애플리케이션은 Azure Cosmos DB에 10,000개의 레코드를 수집해야 합니다. 각 레코드는 수집에 10RU(요청 단위)의 비용이 들며, 작업을 완료하려면 총 100,000RU가 필요합니다.
  2. Azure Cosmos DB 인스턴스에는 프로비저닝된 용량이 20,000RU입니다.
  3. 10,000개의 레코드를 모두 Azure Cosmos DB로 보냅니다. 2,000개의 레코드가 작성되고 8,000개의 레코드가 거부됩니다.
  4. 나머지 8,000개의 레코드를 Azure Cosmos DB로 보냅니다. 2,000개의 레코드가 작성되고 6,000개의 레코드가 거부됩니다.
  5. 나머지 6,000개의 레코드를 Azure Cosmos DB로 보냅니다. 2,000개의 레코드가 작성되고 4,000개의 레코드가 거부됩니다.
  6. 나머지 4,000개의 레코드를 Azure Cosmos DB로 보냅니다. 2,000개의 레코드가 작성되고 2,000개의 레코드가 거부됩니다.
  7. 나머지 2,000개의 레코드를 Azure Cosmos DB로 보냅니다. 모두 작성되었습니다.

수집 작업이 완료되었지만 전체 데이터 세트가 10,000개의 레코드로만 구성된 경우에도 Azure Cosmos DB에 30,000개의 레코드를 보낸 후에만 완료되었습니다.

위의 예제에서 고려해야 할 추가 요소가 있습니다.

  • 많은 수의 오류로 인해 이러한 오류를 기록하고 결과 로그 데이터를 처리하는 추가 작업이 발생할 수도 있습니다. 이 순수한 접근 방식은 20,000개의 오류를 처리했으며 이러한 오류를 로깅하면 처리, 메모리 또는 스토리지 리소스 비용이 부과될 수 있습니다.
  • 수집 서비스의 제한을 모르는 순수한 접근 방식은 데이터 처리에 걸리는 기간에 대한 기대치를 설정할 방법이 없습니다. 속도 제한을 사용하면 수집에 필요한 시간을 계산할 수 있습니다.

해결 방법

속도 제한은 지정된 기간 동안 서비스로 전송되는 레코드 수를 줄여 트래픽을 줄이고 처리량을 향상시킬 수 있습니다.

서비스는 다음과 같이 시간에 따라 다른 메트릭을 기반으로 제한할 수 있습니다.

  • 작업 수(예: 초당 요청 20개)
  • 데이터 양(예: 분당 2GiB)
  • 작업의 상대적 비용(예: 초당 20,000RU)

제한에 사용되는 메트릭에 관계없이 속도 제한 구현에는 특정 기간 동안 서비스에 전송되는 작업의 수 및/또는 크기를 제어하고 제한 용량을 초과하지 않으면서 서비스 사용을 최적화하는 작업이 포함됩니다.

API가 제한된 수집 서비스에서 허용하는 것보다 더 빠르게 요청을 처리할 수 있는 시나리오에서는 서비스를 사용할 수 있는 속도를 관리해야 합니다. 그러나 제한만 데이터 속도 불일치 문제로 처리하고, 제한된 서비스가 따라잡을 때까지 수집 요청을 버퍼링하는 것은 위험합니다. 이 시나리오에서 애플리케이션이 충돌하는 경우 이 버퍼링된 데이터가 손실될 위험이 있습니다.

이러한 위험을 방지하려면 전체 수집 속도를 처리할 수 있는 지속형 메시징 시스템으로 레코드를 보내는 것이 좋습니다 (Azure Event Hubs와 같은 서비스는 초당 수백만 개의 작업을 처리할 수 있음). 그런 다음 하나 이상의 작업 프로세서를 사용하여 제한된 서비스의 한도 내에 있는 제어된 속도로 메시징 시스템에서 레코드를 읽을 수 있습니다. 메시징 시스템에 레코드를 제출하면 지정된 시간 간격 동안 처리할 수 있는 레코드만 큐에서 제거할 수 있으므로 내부 메모리를 절약할 수 있습니다.

Azure는 다음을 포함하여 이 패턴에 사용할 수 있는 몇 가지 지속성 메시징 서비스를 제공합니다.

제한된 서비스로 호출하는 3개의 작업 프로세서가 있는 지속형 메시징 흐름

레코드를 보낼 때 레코드를 해제하는 데 사용하는 기간은 서비스가 제한하는 기간보다 더 세분화될 수 있습니다. 시스템은 쉽게 파악하고 작업할 수 있는 시간 간격에 따라 제한을 설정하는 경우가 많습니다. 그러나 서비스를 실행하는 컴퓨터의 경우 이러한 기간은 정보를 처리할 수 있는 속도에 비해 매우 길 수 있습니다. 예를 들어 시스템은 초당 또는 분당으로 제한할 수 있지만 일반적으로 코드는 나노초 또는 밀리초 순서로 처리됩니다.

필요하지는 않지만 처리량을 개선하기 위해 더 적은 양의 레코드를 더 자주 보내는 것이 좋습니다. 따라서 릴리스에 대해 1초 또는 1분에 한 번 일괄 처리를 시도하는 대신 리소스 소비(메모리, CPU, 네트워크 등)가 더 균일한 속도로 흐르도록 유지하는 것보다 더 세분화되어 갑작스러운 요청 버스트로 인한 잠재적 병목 현상을 방지할 수 있습니다. 예를 들어 서비스에서 초당 100개의 작업을 허용하는 경우 속도 제한기의 구현은 다음 그래프와 같이 200밀리초마다 20개의 작업을 릴리스하여 요청을 균등하게 처리할 수 있습니다.

시간이 지남에 따른 속도 제한을 보여 주는 차트

또한 조정되지 않은 여러 프로세스가 제한된 서비스를 공유해야 하는 경우도 있습니다. 이 시나리오에서 속도 제한을 구현하려면 서비스의 용량을 논리적으로 분할한 다음 분산 상호 제외 시스템을 사용하여 해당 파티션에 대한 배타적 잠금을 관리할 수 있습니다. 그런 다음, 정렬되지 않은 프로세스는 용량이 필요할 때마다 해당 파티션에 대한 잠금을 위해 경쟁할 수 있습니다. 프로세스가 잠금을 보유하는 각 파티션에 대해 일정 용량이 부여됩니다.

예를 들어 제한된 시스템에서 초당 500개의 요청을 허용하는 경우 초당 25개 요청당 20개의 파티션을 만들 수 있습니다. 프로세스가 100개의 요청을 발급해야 하는 경우 분산 상호 제외 시스템에 4개의 파티션을 요청할 수 있습니다. 시스템에서 10초 동안 두 개의 파티션을 부여할 수 있습니다. 그런 다음 프로세스는 초당 50개 요청으로 제한을 평가하고 2초 안에 작업을 완료한 다음 잠금을 해제합니다.

이 패턴을 구현하는 한 가지 방법은 Azure Storage를 사용하는 것입니다. 이 시나리오에서는 컨테이너에서 논리 파티션당 하나의 0바이트 Blob을 만듭니다. 그러면 애플리케이션은 짧은 시간(예: 15초)동안 해당 Blob에 대해 직접 배타적 임대를 얻을 수 있습니다. 애플리케이션이 부여되는 모든 임대에 대해 해당 파티션의 용량을 사용할 수 있습니다. 그런 다음 애플리케이션은 임대 시간이 만료되면 부여된 용량 사용을 중지할 수 있도록 임대 시간을 추적해야 합니다. 이 패턴을 구현할 때 용량이 필요할 경우 각 프로세스에서 임의 파티션을 임대하려고 하는 경우가 많습니다.

대기 시간을 더 줄이기 위해 각 프로세스에 대해 소량의 배타적 용량을 할당할 수 있습니다. 그런 다음 프로세스는 예약된 용량을 초과해야 하는 경우에만 공유 용량에 대한 임대를 얻으려고 합니다.

Azure Blob 파티션

Azure Storage 대신 Zookeeper, Consul, etcd, Redis/Redsync 등의 기술을 사용하여 이러한 종류의 임대 관리 시스템을 구현할 수도 있습니다.

문제 및 고려 사항

이 패턴을 구현할 방법을 결정할 때 다음을 고려하세요.

  • 속도 제한 패턴은 제한 오류 수를 줄일 수 있지만 애플리케이션은 발생할 수 있는 모든 제한 오류를 제대로 처리해야 합니다.
  • 애플리케이션에 동일한 제한 서비스에 액세스하는 여러 작업 스트림이 있는 경우 모든 워크스트림을 속도 제한 전략에 통합해야 합니다. 예를 들어 데이터베이스에 레코드를 대량 로드할 뿐만 아니라 동일한 데이터베이스의 레코드에 대한 쿼리도 지원할 수 있습니다. 모든 작업 스트림이 동일한 속도 제한 메커니즘을 통해 제어되도록 하여 용량을 관리할 수 있습니다. 또는 각 작업 스트림에 대해 별도의 용량 풀을 예약할 수 있습니다.
  • 제한된 서비스는 여러 애플리케이션에서 사용할 수 있습니다. 일부(전부는 아님)에서는 위와 같이 해당 사용량을 조정할 수 있습니다. 예상보다 많은 제한 오류가 표시되기 시작할 경우 서비스에 액세스하는 애플리케이션이 경합한다는 징후일 수 있습니다. 이 경우 다른 애플리케이션의 사용량이 낮아질 때까지 속도 제한 메커니즘에 의해 부과되는 처리량을 일시적으로 줄이는 것을 고려해야 할 수 있습니다.

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

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

  • 제한 서비스에서 발생하는 제한 오류를 줄입니다.
  • 오류 접근 방식의 순수한 재시도와 비교하여 트래픽을 줄입니다.
  • 레코드를 처리할 용량이 있는 경우에만 레코드를 큐에서 해제하여 메모리 사용량을 줄입니다.

워크로드 디자인

설계자는 Azure Well-Architected Framework 핵심 요소에서 다루는 목표와 원칙을 해결하기 위해 워크로드 디자인에 속도 제한 패턴을 사용하는 방법을 평가해야 합니다. 예시:

핵심 요소 이 패턴이 핵심 목표를 지원하는 방법
안정성 디자인 결정은 워크로드가 오작동에 대한 복원력을 갖도록 하고 오류가 발생한 후 완전히 작동하는 상태로 복구 되도록 하는 데 도움이 됩니다. 이 전술은 서비스가 과도한 사용을 방지하고자 할 때 서비스와 통신하는 제한 사항과 비용을 인정하고 적용하여 클라이언트를 보호합니다.

- RE:07 자기 보존

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

예시

다음 예제 애플리케이션에서는 사용자가 다양한 유형의 레코드를 API에 제출할 수 있습니다. 각 레코드 유형에 대해 다음 단계를 수행하는 고유한 작업 프로세서가 있습니다.

  1. 유효성 검사
  2. 보강
  3. 데이터베이스에 레코드 삽입

애플리케이션의 모든 구성 요소(API, 작업 프로세서 A 및 작업 프로세서 B)는 독립적으로 확장될 수 있는 별도의 프로세스입니다. 프로세스는 서로 직접 통신하지 않습니다.

임대를 위해 분할된 스토리지가 있는 다중 큐, 다중 프로세서 흐름으로 데이터베이스에 작성

이 다이어그램은 다음 워크플로를 통합합니다.

  1. 사용자가 API에 A 형식의 레코드 10,000개 제출
  2. API는 큐 A에 이러한 10,000개의 레코드를 큐에 추가합니다.
  3. 사용자는 B 형식의 레코드 5,000개를 API에 제출합니다.
  4. API는 큐 B에 이러한 5,000개의 레코드를 큐에 추가합니다.
  5. 작업 프로세서 A는 큐 A에 레코드가 있다고 보고 Blob 2에서 단독 임대를 얻으려고 시도합니다.
  6. 작업 프로세서 B는 큐 B에 레코드가 있고 Blob 2에서 단독 임대를 얻으려고 시도합니다.
  7. 작업 프로세서 A가 임대를 가져오지 못합니다.
  8. 작업 프로세서 B는 15초 동안 Blob 2에서 임대를 가져옵니다. 이제 데이터베이스에 대한 제한 요청을 초당 100의 속도로 평가할 수 있습니다.
  9. 작업 프로세서 B는 큐 B에서 100개의 레코드를 큐에서 제거하고 씁니다.
  10. 1초가 지나갑니다.
  11. 작업 프로세서 A는 큐 A에 더 많은 레코드가 있다고 보고 Blob 6에서 배타적 임대를 얻으려고 시도합니다.
  12. 작업 프로세서 B는 큐 B에 더 많은 레코드가 있다고 보고 Blob 3에서 배타적 임대를 얻으려고 시도합니다.
  13. 작업 프로세서 A는 15초 동안 Blob 6에서 임대를 가져옵니다. 이제 데이터베이스에 대한 제한 요청을 초당 100의 속도로 평가할 수 있습니다.
  14. 작업 프로세서 B는 15초 동안 Blob 3에서 임대를 가져옵니다. 이제 데이터베이스에 대한 제한 요청을 초당 200의 속도로 평가할 수 있습니다 (Blob 2에 대한 임대도 보유).
  15. 작업 프로세서 A는 큐 A에서 100개의 레코드를 큐에서 제거하고 씁니다.
  16. 작업 프로세서 B는 큐 B에서 200개의 레코드를 큐에서 제거하고 씁니다.
  17. 1초가 지나갑니다.
  18. 작업 프로세서 A는 큐 A에 더 많은 레코드가 있다고 보고 Blob 0에서 배타적 임대를 얻으려고 시도합니다.
  19. 작업 프로세서 B는 큐 B에 더 많은 레코드가 있고 Blob 1에서 배타적 임대를 얻으려고 시도합니다.
  20. 작업 프로세서 A는 Blob 0에서 15초 동안 임대를 가져옵니다. 이제 데이터베이스에 대한 제한 요청을 초당 200의 속도로 평가할 수 있습니다 (Blob 6에 대한 임대도 보유).
  21. 작업 프로세서 B는 15초 동안 Blob 1에서 임대를 가져옵니다. 이제 데이터베이스에 대한 제한 요청을 초당 300의 속도로 평가할 수 있습니다 (Blob 2 및 3에 대한 임대도 보유).
  22. 작업 프로세서 A는 큐 A에서 200개의 레코드를 큐에서 제거하고 씁니다.
  23. 작업 프로세서 B는 큐 B에서 300개의 레코드를 큐에서 제거하고 씁니다.
  24. 이후 계속...

15초 후에도 하나 또는 두 작업이 모두 완료되지 않습니다. 임대가 만료되면 프로세서는 큐에서 제거하고 쓰는 요청 수도 줄여야 합니다.

GitHub 로고 이 패턴의 구현은 다양한 프로그래밍 언어로 사용할 수 있습니다.

  • Go 구현은 GitHub에서 사용할 수 있습니다.
  • Java 구현은 GitHub에서 사용할 수 있습니다.

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

  • 제한. 여기서 설명하는 속도 제한 패턴은 일반적으로 제한된 서비스에 대한 응답으로 구현됩니다.
  • 다시 시도. 제한된 서비스에 대한 요청으로 인해 제한 오류가 발생하는 경우 일반적으로 적절한 간격 후에 다시 시도하는 것이 적절합니다.

큐 기반 부하 평준화는 유사하지만 다음과 같은 몇 가지 주요 방법으로 속도 제한 패턴과 다릅니다.

  1. 속도 제한은 반드시 큐를 사용하여 부하를 관리할 필요는 없지만 지속형 메시징 서비스를 사용해야 합니다. 예를 들어 속도 제한 패턴은 Apache Kafka 또는 Azure Event Hubs와 같은 서비스를 사용할 수 있습니다.
  2. 속도 제한 패턴은 파티션에 분산 상호 제외 시스템의 개념을 도입하여 동일한 제한 서비스와 통신하는 여러 조정되지 않은 프로세스의 용량을 관리할 수 있도록 합니다.
  3. 큐 기반 부하 평준화 패턴은 서비스 간에 성능이 일치하지 않거나 복원력을 향상시킬 때마다 적용할 수 있습니다. 이렇게 하면 속도 제한보다 더 광범위한 패턴이 되며, 이는 더 구체적으로 제한된 서비스에 효율적으로 액세스하는 것과 관련이 있습니다.