|
| 1 | +# 4장 처리율 제한 장치 설계 |
| 2 | +- 네트워크 시스템에서 트래픽 처리율을 제어하기 위한 장치 설계 |
| 3 | +- API 요청 횟수가 제한 장치에 정의된 임계치(threshold)를 넘어서면 추가로 도달한 모든 호출은 중단 |
| 4 | + |
| 5 | +## 1. 문제 이해 및 설계 범위 확정 |
| 6 | + |
| 7 | +- 면접관과 소통하며 요구사항을 도출해내는 것이 중요 ! |
| 8 | +- 설정된 처리율을 초과하는 요청은 정확하게 제한 |
| 9 | +- 낮은 응답시간 |
| 10 | +- 적은 메모리 사용 |
| 11 | +- 분산형 처리율 제한: 여러 서버, 프로세스가 하나의 처리율 제한 장치를 공유 |
| 12 | +- 예외 처리: 제한 시 사용자에게 보여주어야 함 |
| 13 | +- 장애 발생 시 전체 시스템 영향 X |
| 14 | + |
| 15 | +## 2. 개략적 설계안 |
| 16 | + |
| 17 | +### 처리율 제한 장치의 위치 |
| 18 | + |
| 19 | +- 클라이언트 |
| 20 | + - 요청이 쉽게 위변조가 가능하므로 적절하지 X |
| 21 | +- 미들웨어 |
| 22 | + - API 서버로 가는 요청을 통제 |
| 23 | + - 429 반환 |
| 24 | + - 클라우드 마이크로서비스의 경우 API 게이트웨이 컴포넌트로 구현 |
| 25 | + |
| 26 | +### 처리율 제한 알고리즘 |
| 27 | + |
| 28 | +- 토큰 버킷 |
| 29 | + - 컨테이너를 만들어서 요청이 처리될 때마다 하나의 토큰 사용 |
| 30 | + - 충분한 토큰이 있으면 토큰을 꺼낸 후 요청을 시스템에 전달 |
| 31 | + - 없으면 요청을 버림 |
| 32 | + - 토큰 공급률에 따라 토큰을 채운다. |
| 33 | + - 버킷 크기에 따라 최대 개수가 제한된다. |
| 34 | + - 보통 API 엔드포인트마다 별도의 버킷을 둔다. |
| 35 | +- 누출 버킷 알고리즘 |
| 36 | + - 요청 처리율이 고정되어 있음. |
| 37 | + - FIFO 큐로 구현한다. |
| 38 | + - 요청이 도착하면 큐가 가득 차 있는지 본다. |
| 39 | + - 빈자리가 있는 경우 큐에 요청을 추가한다. |
| 40 | + - 큐가 가득 차 있는 경유에는 새 요청은 버린다. |
| 41 | + - 지정된 시간마다 큐에서 요청을 꺼내어 처리한다. |
| 42 | + - 버킷 크기와 처리율(지정된 시간당 몇 개의 항목을 처리할지) 인자로 사용 |
| 43 | +- 고정 윈도 카운터 |
| 44 | + - 타임라인을 고정된 간격의 윈도로 나누고, 각 윈도마다 카운터를 붙인다. |
| 45 | + - 요청이 접수되면 카운터 값을 증가한다. |
| 46 | + - 카운터 값이 임계치에 도달하면 새로운 요청은 새 윈도가 열릴 때까지 버려진다. |
| 47 | + - 경계 부근에 순간적으로 많은 트래픽이 집중될 경우 더 많은 요청이 처리될 수 있음 |
| 48 | + - 윈도 경계 부근에 많은 트래픽이 집중되면 일반 처리율보다 더 높아짐 |
| 49 | +- 이동 윈도 로깅 알고리즘 |
| 50 | + - 고정 윈도 카운터 알고리즘의 경계 부근 트래픽 문제로 인해 나온 알고리즘 |
| 51 | + - 요청 타임스탬프를 추적한다. |
| 52 | + - 새 요청이 오면 만료된 타임스탬프는 제거한다. |
| 53 | + - 만료된 스탬프는 그 값이 현재 윈도의 시작 시점보다 오래된 타임스탬프를 말한다. |
| 54 | + - 새 요청의 타임스탬프를 로그에 추가한다. |
| 55 | + - 로그의 크기가 허용치보다 같거나 작으면 요청을 시스템에 전달한다. 그렇지 않으면 처리를 거부한다. |
| 56 | + - 단점은 로그를 저장해야 하기 때문에 다량의 메모리를 사용한다. |
| 57 | +- 이동 윈도 카운터 알고리즘 |
| 58 | + - 고정 윈도 카운터 알고리즘 + 이동윈도 로깅 알고리즘 |
| 59 | + - 이전 시간대의 평균 처리율에 따라 현재 윈도의 상태를 계산하므로 짧은 시간에 몰리는 트래픽에도 잘 대응한다. |
| 60 | + - 메모리 효율이 좋다. |
| 61 | + - 직전 시간대에 도착한 요청이 균등하게 분포되어 있다고 가정한 상태에서 추정치를 계산하기 때문에 다소 느슨하다. |
| 62 | + - 0.003%정도의 오차율을 가진다. |
| 63 | + |
| 64 | +### 개략적인 아키텍처 |
| 65 | + |
| 66 | +- 카운터를 보관할 아키텍처 선정 |
| 67 | + - 데이터베이스는 디스크 접근 때문에 적절 X |
| 68 | + - 메모리상에서 동작하는 캐시가 바람직 |
| 69 | + - 빠르고 시간에 기반한 만료 정책을 지원 |
| 70 | +- 클라이언트에 처리율 제한 미들웨어에 요청 |
| 71 | +- 미들웨어는 레디스의 지정 버킷에서 카운터를 가져와서 한도에 도달했는지 아닌지 검사 |
| 72 | + - 한도에 도달했다면 요청 거부 |
| 73 | + - 도달하지 않았다면 API 서버로 전달 및 카운터 값 증가 |
| 74 | + |
| 75 | +## 3. 상세 설계 |
| 76 | + |
| 77 | +- 처리율 제한 규칙 |
| 78 | +- 처리율 한도 초과 트래픽 처리 |
| 79 | + - 429 응답 |
| 80 | + - 메시지를 큐에 보관 후 나중에 처리 |
| 81 | + - HTTP 응답 헤더로 클라이언트에게 고지 가능 |
| 82 | + - X-Ratelimit-Remaining: 윈도 내에 남은 처리 가능 요청의 수 |
| 83 | + - X-Ratelimit-Limie: 매 윈도마다 클라이언트가 전송할 수 있는 요청의 수. |
| 84 | + - X-Ratelimit-Retry-After: 한도 제한에 걸리지 않으려면 몇 초 뒤에 요청을 다시 보내야 하는지 알림 |
| 85 | + |
| 86 | +### 분산 환경에서의 처리율 제한 장치 구현 |
| 87 | + |
| 88 | +- Race Condition |
| 89 | + - 카운터 값에 대해 동시성 이슈 제어가 필요 |
| 90 | + - Lock으로 해결 가능 → 그러나 시스템 성능이 떨어짐 |
| 91 | + - Lua Script or Sorted set으로 해결 가능 |
| 92 | +- Synchronization |
| 93 | + - 여러 대의 제한 장치를 뒀을 때 어떻게 처리율 제한 장치를 동기화할 것 인지? |
| 94 | + - Sticky Session을 이용할 수 있음 |
| 95 | + - 같은 장치로 보낼 수 있도록 하는 것 |
| 96 | + - 같은 장치로 계속 보내야하기 때문에 확장성에 있어서 유연하지 않음 |
| 97 | + - 레디스를 중앙 집중형 데이터 저장소로 쓸 수 있음 |
| 98 | + |
| 99 | +### 성능 최적화 |
| 100 | + |
| 101 | +- 여러 데이터센터를 지원해 사용자의 트래픽을 가장 가까운 에지 서버로 전달 |
| 102 | +- 최종 일관성 모델 채택 |
| 103 | + |
| 104 | +## 4. 마무리 |
| 105 | + |
| 106 | +- hard, soft 처리율 제한 |
| 107 | + - hard: 요청의 개수는 임계치를 넘을 수 없다. |
| 108 | + - soft: 요청의 개수는 잠시동안 임계치를 넘어설 수 있다. |
| 109 | +- 다양한 계층에서의 처리율 제한 |
| 110 | + - 7계층인 애플리케이션 뿐만 아니라 Iptables를 사용해도 적용 가능(3계층) |
| 111 | +- 회피 방법 |
| 112 | + - 클라이언트 캐시를 통해 API 호출 줄임 |
| 113 | + - 재시도 로직은 충분한 백오프 시간을 둔다. 등등 |
| 114 | + |
| 115 | +## 알아 볼 점 |
| 116 | + |
| 117 | +### 왜 Sorted Set, Lua Script은 Race Condition으로 부터 자유로울까? |
| 118 | + |
| 119 | +Redis의 Sorted Set CAS 연산을 지원하기 때문에 레이스 컨디션 없이 데이터 추가 및 업데이트 가능 |
| 120 | + |
| 121 | +- CAS의 동작 원리 |
| 122 | + 1. **메모리 위치(address)**: 업데이트하려는 데이터가 저장된 메모리 위치. |
| 123 | + 2. **기대 값(expected value)**: 업데이트하기 전, 현재 데이터가 이 값과 일치하는지 확인. |
| 124 | + 3. **새로운 값(new value)**: 기대 값이 일치하면 이 값으로 데이터 업데이트. |
| 125 | + |
| 126 | + **CAS 동작 프로세스**: |
| 127 | + |
| 128 | + 1. 특정 메모리 위치의 값이 기대 값과 같은지 확인. |
| 129 | + 2. 같으면, 해당 값을 새로운 값으로 교체. |
| 130 | + 3. 다르면, 아무 작업도 수행하지 않고 실패를 반환. |
| 131 | +- CAS의 특징 |
| 132 | + - 비교 후 교체: 기대 값과 메모리 값이 일치하지 않으면 데이터 업데이트를 수행하지 않음. |
| 133 | + - 원자성 보장: 모든 CAS 연산은 한 번에 수행되어 중간 상태가 발생하지 않음. |
| 134 | + - 락-프리(lock-free): CAS는 잠금 없이 데이터 업데이트를 수행하므로, 성능이 뛰어나고 데드락을 방지할 수 있음. |
| 135 | +- CAS 문제점 |
| 136 | + - Spin Lock |
| 137 | + - CAS가 계속 실패하면 CPU 리소스를 낭비할 수 있음. |
| 138 | + - 해결 방법: 지수 백오프(Exponential Backoff) 사용. |
| 139 | + - ABA 문제 |
| 140 | + - 메모리 값이 A였다가 B로 바뀌고, 다시 A로 돌아오면 CAS는 이를 감지하지 못함. |
| 141 | + - 즉, A→B→A로 바꾸고 A→B로 바꾸면 중간 A→B→A로 바뀐 변경을 감지하지 못함. |
| 142 | + - 해결 방법: 버전 번호 추가. |
| 143 | +- Lua도 원자적으로 처리 |
| 144 | +- 그러나 둘 다 단일 인스턴스 기준이기 때문에 다중마스터(클러스터 환경)에서는 다른 전략이 필요함 |
| 145 | + - 데이터 샤딩으로 한 인스턴스에서만 다루기 |
| 146 | + - 메시징 큐 이용 등등 |
| 147 | + |
| 148 | +### Iptables를 통한 처리율 제한 방법 |
| 149 | + |
| 150 | +- Iptables은 리눅스 커널의 네트워크 패킷 필터링 기능을 제어하는 도구 |
| 151 | + - 넷필터는 IP 패킷을 검사, 수정, 드롭 또는 전달하는 기능을 제공하는데 이 규칙을 설정하고 관리하는 사용자 공간 도구를 Iptables라고 한다. |
| 152 | + - 네트워크 스택의 3계층에서 동작 |
| 153 | + - 네트워크 카드에서 패킷을 받아 사용자 공간으로 전달되기 전에 처리 |
| 154 | + - IP 주소와 프로토콜 기반으로 작동 |
| 155 | + - 4계층(TCP/UDP)과의 연결도 제어 가능 |
| 156 | +- Netfilter 패킷 처리 흐름 |
| 157 | + - 네트워크 인터페이스로 들어오거나 나갈 때 5개의 체인을 통해 패킷을 처리함 |
| 158 | + 1. PREROUTING |
| 159 | + - 패킷이 네트워크 인터페이스로 들어오자마자 처리 |
| 160 | + - 패킷의 목적지 변경 및 수정 |
| 161 | + 2. INPUT |
| 162 | + - 패킷이 로컬 호스트로 들어오는 경우 처리 |
| 163 | + - 로컬 시스템에서 처리될 트래픽을 필터링 |
| 164 | + 3. FORWARD |
| 165 | + - 로컬 호스트가 아닌 다른 네트워크로 전달되는 패킷 처리 |
| 166 | + - 라우터 역할을 수행하는 경우 해당 체인을 거침 |
| 167 | + 4. OUTPUT |
| 168 | + - 로컬 호스트에서 생성된 패킷을 처리 |
| 169 | + - 외부로 나가는 패킷을 제어 |
| 170 | + 5. POSTROUTING |
| 171 | + - 패킷이 네트워크 인터페이스로 나가기 직전에 처리 |
| 172 | + - 출발지 주소 변환 |
| 173 | + |
| 174 | + **동작 과정 요약** |
| 175 | + |
| 176 | + 1. **패킷이 들어옴** → PREROUTING. |
| 177 | + 2. **로컬 호스트로 가는지 확인**: |
| 178 | + - 로컬로 간다면 → INPUT. |
| 179 | + - 라우터를 거친다면 → FORWARD. |
| 180 | + 3. **패킷이 로컬에서 생성됨** → OUTPUT. |
| 181 | + 4. **패킷이 나감** → POSTROUTING. |
| 182 | + |
| 183 | +1. 처리율 제한을 위한 `limit` 모듈 |
| 184 | + |
| 185 | +**특징** |
| 186 | + |
| 187 | +- 단일 규칙의 처리율을 제한합니다. |
| 188 | +- 지정된 초당 요청 수를 초과하면 나머지 트래픽을 차단하거나 다른 규칙으로 이동시킵니다. |
| 189 | +- ICMP 핑을 이용한 패킷 제한, SSH 연결 속도 제한 등 |
| 190 | + |
| 191 | +2. IP별 처리율 제한을 위한 `hashlimit` 모듈 |
| 192 | + |
| 193 | +**특징** |
| 194 | + |
| 195 | +- 요청을 보내는 IP 주소별로 개별적으로 처리율을 제한. |
| 196 | +- 악의적인 트래픽을 생성하는 IP를 제어하는 데 유용. |
0 commit comments