개발 일지/대규모 시스템 설계

[대규모 시스템 설계] 2. 처리율 제한 장치

배발자 2023. 7. 21.
반응형

필자는 처리율 제한 장치와 관련된 처리를 해본 경험이 없다.

프로젝트를 진행하면서 필수적으로 진행해야하는 것은 API 기능 명세에 작성된 주요 서비스 구현이 1순위이다. 그러다보니 트래픽에 대한 처리는 어느순간 후순위로 밀려나게 되었다. 하지만 이전 포스팅에서 언급한 것처럼 '성능 개선', '트래픽 처리' 등 개발자 관점에서 도전적으로 프로젝트에 접근하는 것을 권장한다. 물론, 하다보면 필자처럼 시간에 쫓겨 흐지부지될 때가 많지만 시간이 넉넉한 프로젝트를 한다면 새로운 기술에 접근해보자. 

 

이번 장에서는 클라이언트와 웹 서버 사이에 로드밸런서가 아닌 또 다른 친구가 존재하는데 그 친구를 한 번 포스팅하고자 한다. 

 

처리율 제한 장치가 무엇인가

네트워크 시스템에서 처리율 제한 장치는 클라이언트 또는 서비스가 보내는 트래픽의 처리율을 제어하기 위한 장치다. 예를 들면, 웹 서버에 HTTP 요청을 한다고 치자. 만약, 특정 사용자가 고의든, 고의가 아니든 특정 서비스에서 정의된 임계치를 넘어서는 요청을 계속해온다면 문제가 된다는 것이다. 무슨 말인지 이해가 안될거다. 밑에 사례를 보자. 

 

- 사용자는 초당 2회 이상 새 글을 올릴수 없다. 

- 같은 IP 주소로는 하루에 10개 이상의 계정을 생성할 수 없다. 

- 같은 디바이스로는 주당 5회 이상 리워드(reward)를 요청할 수 없다. 

 

조금 감이 잡히는가?

이번 장에서는 처리율 제한 장치에 대해 설계하는 것을 목표로 포스팅한다. 먼저 설계 이전에 API에 처리율 제한 장치를 두면 좋은 점을 살펴보고자 한다. 

 

Dos

공격에 의한 자원고갈을 방지할 수 있다. 대형 IT 기업들이 공개한 API는 대부분이 처리율 제한 장치를 가지고 있다고 한다. 

* Dos는 "서비스 거부 공격"을 의미한다. 이는 악의적인 사용자가 특정 컴퓨터, 서버 또는 네트워크를 공격하여 해당 시스템의 리소스를 과도하게 사용하도록 유도하여 정상적인 사용자들이 서비스를 이용하지 못하게 만드는 공격 형태이다. 

 

비용절감

추가 요청에 대한 처리를 웹 서버에 도달하기 전에 제한한다면, 과도한 HTTP 요청에 의한 서버 증설을 할 필요가 없어진다. 이는 서버 과부하를 막는 얘기이기도 한다. 

 

 

처리율 제한 장치의 위치

 

Q. 클라이언트 측에 둔다면? 

A. 클라이언트 요청은 쉽게 위변조가 가능해서 안정적으로 처리율 제한을 걸 수 없을 것이다. 

 

Q. 서버 측에 둔다면? 

A. 서버 측에 처리율 제한 장치를 두어도 된다. 사업의 필요에 맞는 처리율 제한 알고리즘을 찾자. 만약, 서버 측에서 모든 것을 구현하기로 했다면, 알고리즘은 자유롭게 선택하면 된다. 

 

Q. 또 다른 위치가 있는가? 

A. 처리율 제한 미들웨어를 만들면 된다. 지금부터 설명하겠다. 

 

 

 

 

API 서버의 처리율이 초당 2개의 요청으로 제한된다고 가정해보자. 

한 사용자가 API 서버에 초당 3번의 요청을 보낸다면 3번째 요청은 처리율 제한 미들웨어에 의해 막히고 HTTP 상태 코드 429(사용자가 너무 많은 요청)가 반환하게 된다. 

 

보통 처리율 제한 장치는 API 게이트웨이라 불리는 컴포넌트에 구현된다. API 게이트웨이는 처리율 제한, SSL 종단, 사용자 인증, IP 허용 목록 관리 등을 지원하는 완전 위탁 관리형 서비스이며 클라우드 업체가 유지 보수를 담당하는 서비스이다. 일단 API 게이트웨이가 처리율 제한 미들웨어라는 것을 알고 있자. 

 

 

개략적인 아키텍처 

위에서 언급한 것처럼 초당 3개의 요청을 진행한다면, 요청 횟수에 대한 카운터는 어떻게 할 것인가?

카운터를 사용자별로 추적할 것인지, IP 주소별로 할 것인지, API 엔드포인트나 서비스단위로 할 것인지 등 카운터의 값이 정해진 한도를 넘어가면 도착한 요청은 제한되어야 할것이다. 

 

그렇다면 카운터를 어딘가에 저장해야할텐데 디스크의 접근은 시간이 많이 걸리는 작업이라고 말했으니 데이터베이스는 최적의 방법은 아닐 것이다. 대신에 메모리 상에서 동작하는 캐시를 활용해보는 것이 좋은 선택이 될 수 있다. 특정 예로 레디스가 처리율 제한 장치를 구현할 때 자주 사용되는 메모리 기반 저장 장치라고 한다. 또한, 다음과 같이 두가지 명령어를 지원한다고 한다. 

 

INCR : 메모리에 저장된 카운터의 값을 1 증가

EXPIRE : 카운터에 타임아웃 설정. 만료 시 자동 삭제 

 

 

 

위의 그림을 보면서 과정을 이해하자. 

 

1. 클라이언트가 처리율 제한 미들웨어로 요청을 보낸다. 
2. 처리율 제한 미들웨어는 레디스의 지정 버킷에서 카운터를 가져와서 한도에 도달했는 체크한다. 
  2-1. 한도에 도달하면 거부한다. 
  2-2. 한도에 도달하지 않았다면 API 서버로 전달한다. 미들웨어는 카운터의 값을 증가 후 레디스에 다시 저장한다. 

 


 

분산 환경에서의 처리율 제한 장치의 구현 

단일 서버를 지원하는 처리율 제한 장치를 구현하는 것은 어려집 않지만, 여러 대의 서버와 병렬 스레드를 지원하도록 시스템을 확장하는 것은 또 다른 문제가 된다. 경쟁 조건과 동기화를 예로 들 수 있다. 

 

 

1. 경쟁 조건 

 

앞서 레디스를 공유 자원으로 활용하고 있다고 가정하자. 

 

1. 레디스에서 카운터의 값을 읽는다. 

2. counter+1의 값이 임계치를 넘는지 본다. 

3. 넘지 않는다면 레디스에 보관된 카운터 값을 1만큼 증가시킨다. 

 

병행성이 심한 환경에서는 하나의 스레드가 요청을 처리하기도 전에 다른 스레드가 변하기 전의 공유 자원을 읽는다는 것이다.  

 

 

 

 

읽는 작업만 수행한다면, 크게 문제 될 것은 없다. 하지만, 위의 그림처럼 요청 처리 작업이 해당 값의 변동이 발생한다면 그게 문제라는거다. 그림을 보면 원래 counter 값은 3인 것을 알 수 있다. 하지만 counter의 값을 +1을 해야하는 연산이 2개 요청이 들어왔다면 counter의 값은 5로 변경돼야한다. 그러나, 요청 1의 counter 변경이 이루어지기 전에 요청 2가 변경되지 않은 데이터를 읽고 덮어씌운 것이다. 

 

이를 해결하기위해 락이라는 기술이 있다. 하지만 락은 시스템의 성능을 상당히 떨어뜨린다는 문제점이 존재한다. 위 설계의 경우에는 락 대신 쓸 수 있는 해결책이 두 가지 있는데, 하나는 루아 스크립트이고 다른 하나는 정렬 집합이라 불리는 레디스 자료구조를 쓰는 것이다. 

 

이 주제에 대해서는 추후 포스팅 할 예정이다. 

 

2. 동기화 이슈 

 

분산 환경에서 수백만 사용자를 지원하려면 한 대의 처리율 제한 장치 서버로는 부족하다. 그래서 여러 처리율 제한 장치 서버를 여러 대 두게 될텐데 이때 동기화 작업이 필요하다. 이때는 레디스와 같은 중앙 집중형 데이터 저장소를 쓰는 것이다. 

 

 

 


 

이번장에는 처리율 제한 장치에 대해서 자세히 다뤄봤다. 

다음장에서는 처리율 제한을 어떤 알고리즘을 활용해서 사용되는지에 대해서 자세히 살펴볼 예정이다. 

 

도움이 되셨다면 광고 한 번씩 클릭하는 것을 부탁해요ㅎㅎ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

댓글