API 관리에서의 Rate Limiting

Qi Guo

Qi Guo

September 16, 2022

Technology

인터넷의 발전과 함께, 점점 더 많은 기업들이 클라우드 네이티브와 마이크로서비스를 도입하기 시작했습니다. 그러나 클라우드 네이티브와 마이크로서비스의 기술적 특성으로 인해, 우리는 동시에 수백 개의 다양한 서비스를 관리해야 합니다. 따라서 전체 시스템의 원활한 운영을 고려해야 할 뿐만 아니라, 모든 API 기반 서비스의 보안과 안정성도 신경 써야 합니다.

속도 제한은 API 기반 서비스의 안정성을 보장하기 위한 가장 중요한 솔루션 중 하나입니다. 그러나 각 서비스마다 속도 제한이 필요하다면 애플리케이션이 매우 비대해질 것입니다. 디지털 세계에서 모든 트래픽의 입구와 출구 역할을 하는 API 게이트웨이는 모든 서비스의 통합 API 관리를 달성하는 데 도움을 줍니다. 이는 시스템 서비스의 안정적인 운영을 보호합니다. 이 글에서는 Apache APISIX API 게이트웨이를 통해 속도 제한을 어떻게 달성하는지 보여주고, 속도 제한 전략과 기술에 대해 설명합니다.

속도 제한이란 무엇인가

속도 제한은 인터넷 트래픽을 제어하고 처리량을 극대화하기 위한 전략입니다. 속도 제한을 사용하면 특정 제약 조건 내의 요청만 시스템에 접근할 수 있도록 허용하며, 제약 조건을 초과하는 추가 요청은 대기열에 넣거나 우선순위를 낮추거나 심지어 거부하거나 버릴 수 있습니다. 속도 제한은 또한 트래픽 급증이나 악의적인 공격과 같은 예상치 못한 사고로부터 시스템을 보호하고, 시스템이 일관되고 안정적인 서비스를 제공할 수 있도록 합니다.

예를 들어, 트위터에서 유행하는 트윗으로 인해 트래픽이 급증할 때, 서버가 트래픽 과부하로 인해 다운되는 것을 방지하기 위해 속도 제한을 적용해야 합니다.

왜 속도 제한이 필요한가

먼저, 우리의 일상 생활에서 속도 제한을 사용하는 몇 가지 간단한 사례를 살펴보겠습니다. 예를 들어, 관광 명소는 휴가 티켓을 일정 수량만 판매할 수 있습니다. 또한, 인기 있는 레스토랑에서 음식을 즐기기 위해서는 미리 예약하거나 오랜 시간을 기다려야 합니다.

API 게이트웨이에서도 속도 제한은 많은 이점을 제공합니다. 속도 제한은 API 기반 서비스에 일부 제약을 두어 원활한 운영을 보장하고, 트래픽 급증으로 인한 서버 다운으로 인한 불필요한 손실을 방지합니다. 여기서는 다섯 가지 다른 실용적인 제약 조건을 나열했습니다:

  1. 요청 속도를 제한합니다.
  2. 시간 단위당 요청 수를 제한합니다.
  3. 요청을 지연시킵니다.
  4. 클라이언트 요청을 거부합니다.
  5. 응답 속도를 제한합니다.

언제 속도 제한이 필요한가

식별 및 인증과 함께, 속도 제한은 다음과 같은 방식으로 시스템의 가용성을 극대화하고 개선할 수 있습니다:

  1. 악의적인 공격을 방지합니다.
  2. 시스템의 안정적인 운영을 보장하고 트래픽 급증으로 인한 서버 다운을 방지합니다.
  3. 업스트림 또는 다운스트림 서비스에서 발생한 버그로 인한 요청 급증으로 인한 서버 다운을 방지합니다.
  4. 너무 빈번한 고가의 API 호출을 방지합니다.
  5. API 호출 빈도를 제한하여 불필요한 자원 낭비를 줄입니다.

속도 제한의 이론

이전 섹션에서 속도 제한의 이점을 소개했습니다. 이 섹션에서는 속도 제한의 이론을 알아보겠습니다! 속도 제한의 저수준 구현은 특정 알고리즘에 의해 달성됩니다. 일반적으로 사용되는 알고리즘은 다음과 같습니다:

  • 카운터 알고리즘
    • 고정 창
    • 슬라이딩 창
  • 누출 버킷 알고리즘
  • 토큰 버킷 알고리즘

카운터 알고리즘

카운터 알고리즘은 상대적으로 이해하기 쉽고, 두 가지 유형이 있습니다:

첫 번째 유형은 고정 창 알고리즘으로, 고정된 시간 단위 내에 카운터를 유지하고 시간 단위가 지나면 카운터를 0으로 재설정합니다.

두 번째 유형은 슬라이딩 창 알고리즘으로, 첫 번째 유형을 기반으로 개선된 것으로, 다음과 같은 단계를 거칩니다:

  1. 시간 단위를 여러 간격(각각 블록이라고 함)으로 나눕니다.
  2. 각 블록에는 카운터가 있으며, 들어오는 요청은 카운터를 1씩 증가시킵니다.
  3. 고정된 시간이 지나면 이 시간 창은 한 블록씩 앞으로 이동합니다.
  4. 시간 창 내의 모든 블록의 카운터를 합산하여 해당 시간 창 내의 총 요청 수를 계산하며, 총 요청 수가 제약 조건을 초과하면 해당 시간 창 내의 모든 요청을 버립니다.

누출 버킷 알고리즘

누출 버킷이 있다고 가정하면, 모든 요청은 먼저 대기열에 들어가고, 누출 버킷은 일정한 속도로 요청을 보냅니다.

누출 버킷 알고리즘

요청이 버킷의 용량을 초과하면 시스템은 넘친 요청을 버리고 거부합니다. 누출 버킷 알고리즘은 요청 속도를 제한하고 모든 요청이 일정한 속도로 전송되도록 보장하여 쉽게 들어가지만 나가기 어려운 모드를 만듭니다.

이 알고리즘의 핵심 단계:

  1. 모든 요청은 고정 크기의 버킷에 저장됩니다.
  2. 버킷은 버킷이 비워질 때까지 일정한 속도로 요청을 보냅니다.
  3. 버킷이 가득 차면 시스템은 추가 요청을 버립니다.

토큰 버킷 알고리즘

토큰 버킷 알고리즘은 토큰 생성과 폐기 두 부분으로 구성됩니다. 토큰 버킷은 일정한 속도로 토큰을 생성하고 고정된 저장 버킷에 저장합니다. 요청이 토큰 버킷을 통과할 때, 요청은 하나 이상의 토큰을 가져갑니다. 토큰 버킷의 토큰 수가 최대 용량에 도달하면 토큰 버킷은 새로 생성된 토큰을 버립니다. 또한 저장 버킷에 토큰이 남아 있지 않으면 들어오는 요청을 거부합니다.

토큰 버킷 알고리즘

토큰 버킷 알고리즘의 핵심 단계:

  1. 토큰 버킷은 일정한 속도로 토큰을 생성하고 저장 버킷에 넣습니다.
  2. 토큰 버킷이 가득 차면 새로 생성된 토큰은 바로 버려집니다. 요청이 도착하면 저장 버킷에서 하나 이상의 토큰을 가져갑니다.
  3. 토큰 버킷에 토큰이 남아 있지 않으면 시스템은 들어오는 요청을 거부합니다.

API 게이트웨이를 통한 속도 제한 달성

관리해야 할 API 기반 서비스가 몇 개뿐이라면, 서비스 내에서 직접 속도 제한 알고리즘을 사용할 수 있습니다. 예를 들어, Go를 사용하여 시스템을 개발하는 경우 tollbooth 또는 golang.org/x/time/rate를 사용하여 알고리즘을 구현할 수 있습니다. Lua를 사용하는 경우 NGINX의 limit_req, limit_conn, Lua-resty-limit-traffic 모듈을 사용하여 알고리즘을 구현할 수 있습니다.

속도 제한이 API 기반 서비스에 기반하여 구현되면, 속도 제한의 제약 조건은 서비스 자체에 의해 설정되며, 각 서비스는 서로 다른 제약 조건을 가질 수 있습니다. API 기반 서비스의 수가 크게 증가하면 이러한 제약 조건과 차이로 인해 관리 수준의 문제가 발생할 수 있습니다. 그럴 경우 API 게이트웨이를 사용하여 모든 API 서비스를 통합적으로 관리할 수 없게 됩니다. 또한 API 게이트웨이를 사용하여 속도 제한 문제를 해결할 때, 식별, 인증, 로그, 관측 가능성 등과 같은 관련 없는 비즈니스 기능을 게이트웨이에서 구현할 수도 있습니다.

Apache APISIX는 동적, 실시간, 고성능의 클라우드 네이티브 게이트웨이입니다. APISIX는 현재 80개 이상의 다양한 플러그인을 지원하며, 이미 풍부한 생태계를 구축했습니다. APISIX의 플러그인을 사용하여 API 기반 서비스의 트래픽을 관리할 수 있으며, 여기에는 limit-req, limit-conn, limit-count가 포함됩니다. 여기서는 APISIX 속도 제한 플러그인의 사용법을 보여주기 위해 사용 사례를 공유하겠습니다.

사용자가 로그인하는 데 도움을 주는 API 기반 서비스(/user/login)가 있다고 가정해 보겠습니다. 악의적인 공격과 자원 고갈을 방지하기 위해 시스템의 안정성을 보장하기 위해 속도 제한 기능을 활성화해야 합니다.

요청 제한

limit-req 플러그인은 요청 속도를 제한하며, 누출 버킷 알고리즘을 사용하며, 이를 해당 경로 또는 특정 고객과 연결합니다.

APISIX의 Admin API를 사용하여 다음과 같은 경로를 직접 생성할 수 있습니다:

X-API-Key는 APISIX 구성에서 admin_key입니다.

curl http://127.0.0.1:9080/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/user/login",
    "plugins": {
        "limit-req": {
            "rate": 3,
            "burst": 2,
            "rejected_code": 503,
            "key": "remote_addr"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:1980": 1
        }
    }
}'

이 코드 스니펫의 의미: 클라이언트의 IP 주소를 요청 속도 제한의 요구 사항으로 사용합니다.

  • 요청 속도가 초당 3회(rate) 미만이면 요청은 정상입니다;
  • 요청 속도가 초당 3회(rate) 이상이고 초당 5회(rate+burst) 미만이면 초과 요청의 우선순위를 낮춥니다;
  • 요청 속도가 초당 5회(rate+burst) 이상이면 최대 제약 조건을 초과하는 모든 요청은 HTTP 코드 503을 반환합니다.

limit-req에 대해 더 알아보려면 이 문서를 확인하세요: APISIX limit-req

연결 제한

limit-conn 플러그인은 병렬 요청(또는 병렬 연결)을 제한합니다. 다음은 /user/login에 대해 이 플러그인을 활성화하는 예제 코드 스니펫입니다:

curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/user/login",
    "id": 1,
    "plugins": {
        "limit-conn": {
            "conn": 3,
            "burst": 2,
            "default_conn_delay": 0.1,
            "rejected_code": 503,
            "key": "remote_addr"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:1980": 1
        }
    }
}'

이 코드 스니펫의 의미: 클라이언트의 IP 주소를 병렬 요청 제한의 요구 사항으로 사용합니다.

  • 동일한 클라이언트의 병렬 연결이 3회(conn) 미만이면 정상 상태 200을 반환합니다;
  • 병렬 연결이 3회(conn) 이상이고 5회(conn+burst) 미만이면 초과 요청을 느리게 하고 0.1초의 지연 시간을 추가합니다;
  • 병렬 연결이 5회(conn+burst) 이상이면 이 요청은 거부되고 HTTP 코드 503을 반환합니다.

limit-conn에 대해 더 알아보려면 이 문서를 확인하세요: APISIX limit-conn

요청 수 제한

limit-count 플러그인은 Github의 API 속도 제한과 유사합니다; 특정 시간 간격 내의 총 요청 수를 제한하고 남은 요청 수를 HTTP 헤더에 반환합니다. 다음은 /user/login에 대해 이 플러그인을 활성화하는 예제 코드 스니펫입니다:

curl -i http://127.0.0.1:9080/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/user/login",
    "plugins": {
        "limit-count": {
            "count": 3,
            "time_window": 60,
            "rejected_code": 503,
            "key": "remote_addr",
            "policy": "local"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:9001": 1
        }
    }
}'

이 코드 스니펫의 의미: 클라이언트의 IP 주소를 요청 수 제한의 요구 사항으로 사용합니다; 카운터는 메모리에 로컬로 저장됩니다.

60초(time_window) 내에 3회(count) 이상의 요청이 있으면 3회를 초과하는 요청은 HTTP 코드 503(rejected_code)을 반환합니다.

limit-count에 대해 더 알아보려면 이 문서를 확인하세요: APISIX limit-count

Apache APISIX 속도 제한의 장점

NGINX를 사용하여 트래픽을 관리할 때, API 요청 수가 급증하면 NGINX는 단점을 노출하며, 그 중 하나는 동적으로 구성을 로드할 수 없다는 것입니다. 반면, APISIX의 서비스(예: Route 및 Service)는 구성 핫 리로드를 지원합니다. 트래픽이 급증하더라도 APISIX는 즉시 속도 제한 및 기타 보안 플러그인 구성을 수정할 수 있습니다. etcd의 감시 메커니즘 덕분에, APISIX는 서비스를 재로드하지 않고도 밀리초 단위로 데이터 레이어를 업데이트할 수 있습니다.

또한, APISIX는 클러스터 수준의 속도 제한도 지원합니다. 예를 들어, limit-count를 사용하여 policy 구성을 redis 또는 redis-cluster로 수정할 수 있습니다. 따라서 서로 다른 APISIX 노드 간에 계산 결과를 공유하여 클러스터 수준의 속도를 제한할 수 있습니다.

DevOps로서, 모든 API 서비스를 관리하기 위해 그래픽 대시보드를 사용하면 생산성이 향상됩니다. APISIX는 깔끔한 시각적 관리 대시보드를 제공하여 API 구성 수정을 훨씬 더 편리하게 만듭니다.

결론

속도 제한은 실제 비즈니스 시나리오에서 일반적인 요구 사항이며, 트래픽 급증으로부터 시스템을 보호하고 원활한 운영을 보장하는 중요한 방법입니다. 속도 제한은 API 서비스 관리의 일부일 뿐이며, 중요한 보안 지원을 제공하고 사용자 경험을 개선하기 위해 많은 다른 기술을 사용할 수도 있습니다.

Tags: