설계의 부품
어느 설계에나 다시 쓰이는 부품들이다. 처리율 제한, 일관된 해싱, 키-값 저장소, 고유 ID 생성, 캐시와 로드 밸런서, API 게이트웨이와 서킷 브레이커.
목차 (13)
처리율 제한: 왜, 어디에
처리율 제한(rate limiting)은 한 클라이언트가 정해진 시간에 보낼 수 있는 요청 수를 묶어 둔다. 실수로 무한 반복하는 스크립트, 비밀번호 대입 공격, 한 고객의 폭주가 다른 고객 모두를 느리게 만드는 일을 막고, 비싼 외부 API의 사용량과 비용도 지킨다. 사용자 ID, API 키, IP 주소처럼 무엇을 기준으로 셀지가 먼저 정할 일이다.
- 어디에 두나: 클라이언트 쪽 제한은 우회하기 쉬워 보조일 뿐이다. 서버 코드 안에 둘 수도 있지만, 대개 API 게이트웨이나 리버스 프록시 같은 앞단에 두어 모든 서비스가 같은 규칙을 쓰게 한다. NGINX의 limit_req 모듈이 그 예다.
- 카운터는 공유한다: 서버마다 따로 세면 서버 수만큼 한도가 늘어난다. Redis 같은 공유 저장소에서 세되, "읽고 비교하고 늘리기"가 사이에 끼어들지 못하게 원자적인 증가 명령이나 서버 쪽 스크립트로 한 번에 한다.
- 한도를 넘으면: HTTP 상태 코드 429 Too Many Requests로 답한다. 429는 RFC 6585가 2012년에 정의했고, 언제 다시 시도하면 되는지
Retry-After헤더로 알려 줄 수 있다고 적었다. - 남은 양 알려 주기: 지금까지는 GitHub의 x-ratelimit-* 헤더처럼 서비스마다 제각각인 이름을 썼다. IETF httpapi 작업 그룹이 이를
RateLimit-Policy(정책: 한도 q, 창 길이 w)와RateLimit(지금 남은 양 r, 초기화까지 t초) 두 필드로 표준화하는 draft-ietf-httpapi-ratelimit-headers를 만들고 있다. 2026년 5월에 나온 -11판까지도 아직 RFC가 아닌 인터넷 초안이고, 판이 바뀌며 헤더 이름과 문법이 여러 번 달라졌다. 쓰기 전에 최신 판을 확인한다.
Stripe는 Scaling your API with rate limiters에서 사용자별 요청 수 제한 말고도 동시에 처리 중인 요청 수 제한, 그리고 서버가 바쁠 때 중요하지 않은 요청부터 버리는 부하 차단(load shedding)을 함께 쓴다고 설명한다. 처리율 제한은 한 겹이 아니라 여러 겹의 방어다.
토큰 버킷
가장 널리 쓰는 알고리즘이다. 클라이언트마다 통이 하나 있고, 통에는 정해진 속도로 토큰이 채워진다. 요청 하나가 토큰 하나를 쓰고, 토큰이 없으면 거절한다. 통이 가득 차면 더 채우지 않는다. 설정값은 두 개뿐이다. 통의 크기는 한 번에 몰려도 받아 줄 수 있는 양이고, 채우는 속도는 길게 봤을 때의 평균 한도다.
0초: 통이 가득 찬 채 시작한다. 요청 3개 중 3개 통과. 남은 토큰 1개.
구현할 때 실제로 초마다 토큰을 채우는 타이머를 돌릴 필요는 없다. 클라이언트마다 "마지막으로 본 시각"과 "그때의 토큰 수"만 저장해 두고, 요청이 오면 흐른 시간만큼 채워진 토큰을 계산해 더한다. 저장할 것이 두 값뿐이라 공유 저장소에 두기도 쉽다. 위 Stripe 글도 Redis에 둔 토큰 버킷을 쓴다.
누출 버킷
누출 버킷(leaky bucket)은 바닥에 구멍 난 양동이처럼 동작한다. 요청은 정해진 크기의 대기열에 들어가고, 대기열은 일정한 속도로만 요청을 내보낸다. 대기열이 차면 새 요청은 버린다. 토큰 버킷이 몰림을 "허락"한다면 누출 버킷은 몰림을 "고르게 펴서" 뒤의 서버가 늘 같은 속도로 일을 받게 한다.
NGINX의 limit_req 문서는 자신이 누출 버킷 방식을 쓴다고 밝힌다. burst가 대기열 크기이고, 대기열을 넘친 요청은 오류(기본 503, 설정으로 429)로 답한다. nodelay를 켜면 대기열 안의 요청도 기다리게 하지 않고 바로 넘기는데, 그러면 동작이 토큰 버킷과 거의 같아진다. 두 알고리즘은 한 동전의 양면에 가깝고, 차이는 "몰린 요청을 기다리게 하느냐"다.
창으로 세기
"1분에 100번"처럼 시간 창 안의 횟수로 세는 방법도 있다. 고정 창 카운터는 시간을 1분 칸으로 자르고 칸마다 카운터 하나를 둔다. 가장 단순하고 저장할 것도 칸마다 숫자 하나다. 문제는 칸의 경계다. 59초에 한도만큼, 61초에 또 한도만큼 보내면 2초 사이에 한도의 두 배가 지나간다.
- 이동 로그(sliding log): 통과시킨 요청의 시각을 모두 기록하고, 요청이 올 때마다 지난 60초 안의 기록을 센다. 정확하지만 클라이언트마다 요청 수만큼 시각을 저장해야 해 메모리가 많이 든다.
- 이동 창 카운터(sliding window counter): 고정 창의 카운터 두 개(직전 칸, 이번 칸)로 이동 로그를 어림한다. 직전 칸 안에서 요청이 고르게 왔다고 치고, 직전 칸 수에 지금 창과 겹치는 비율을 곱해 이번 칸 수에 더한다. 예를 들어 직전 칸 42개, 이번 칸 18개, 이번 칸이 시작한 지 15초면 42 × 45/60 + 18 = 49.5개로 본다.
Cloudflare는 How we built rate limiting capable of scaling to millions of domains에서 바로 이 어림을 쓴다고 밝혔다. 실제 요청 4억 건으로 확인했더니 잘못 통과하거나 막힌 요청이 0.003%였다고 한다. 칸마다 숫자 두 개로 이 정도면, 대부분의 서비스에 충분한 정확도다.
일관된 해싱
데이터나 요청을 서버 여러 대에 나눌 때 "키의 해시 % 서버 수"를 쓰면, 서버 한 대를 더하거나 빼는 순간 거의 모든 키의 자리가 바뀐다(규모 확장 노트의 샤딩 절). 일관된 해싱은 해시 값의 범위를 원(링)으로 보고 서버도 키도 그 원 위에 올린다. 키는 시계 방향으로 처음 만나는 서버가 맡는다. 서버가 빠지면 그 서버의 몫만 다음 서버로 넘어가고, 나머지 키는 그대로다.
서버 하나를 링 위의 한 점에만 두면 구간 길이가 들쭉날쭉해 부하가 고르지 않고, 서버가 빠질 때 그 몫이 바로 옆 서버 하나에 몰린다. 그래서 서버마다 여러 점(가상 노드)을 둔다. 점이 많을수록 몫이 고르게 나뉘고, 성능이 좋은 서버에는 점을 더 주어 더 많이 맡길 수 있다. 원리와 재분배 비용의 계산은 데이터베이스의 복제·샤딩·합의 노트, 해시 함수 자체는 해시 노트에 있다.
키-값 저장소: 나누기, 복제, 정족수
키 하나로 값을 넣고 꺼내는 저장소를 여러 대로 만든다고 하자. 지금까지의 부품이 모두 들어간다. Amazon이 2007년에 낸 Dynamo 논문은 "언제나 쓰기를 받아야 한다"는 목표로 이 문제를 푼 설계를 자세히 공개했고, 이후 Cassandra와 Riak 같은 저장소의 본보기가 됐다. 이 절과 다음 절은 그 설계의 뼈대를 따라간다.
- 나누기: 일관된 해싱으로 키를 노드에 나눈다. 노드가 들어오고 나가도 옮길 데이터가 적다.
- 복제: 키마다 링에서 다음 N개 노드(선호 목록)에 사본을 둔다. 같은 물리 서버의 가상 노드는 건너뛰어 사본이 서로 다른 서버에 놓이게 한다.
- 정족수: 쓰기는 W개, 읽기는 R개 노드의 답을 받으면 성공으로 친다. R + W > N이면 읽는 노드와 쓴 노드가 적어도 하나 겹친다. 논문은 여러 서비스가 흔히 쓰는 값으로 (N, R, W) = (3, 2, 2)를 든다. W를 줄이면 쓰기가 빨라지고, R을 줄이면 읽기가 빨라지는 대신 옛 값을 볼 가능성이 커진다(정족수의 자세한 경우는 복제·샤딩·합의 노트).
- 잠깐의 장애: 선호 목록의 노드가 죽어 있으면 목록 밖의 다음 노드가 "원래 주인은 C"라는 힌트를 붙여 대신 받아 두고, C가 돌아오면 넘겨준다(hinted handoff). 정족수를 원래 노드가 아닌 살아 있는 노드로 채우므로 느슨한 정족수(sloppy quorum)라 부른다. 쓰기를 거의 거절하지 않는 대신, 그 사이에는 R + W > N이어도 최신 값을 못 볼 수 있다.
- 장애 감지: 중앙 관리자 없이 노드끼리 무작위로 상대를 골라 자기가 아는 회원 목록을 주고받는다(가십 프로토콜). 소문이 퍼지듯 몇 차례만에 모두가 같은 목록을 갖게 된다.
키-값 저장소: 버전 충돌과 복구
어느 노드나 쓰기를 받으면, 네트워크가 갈라진 사이 두 클라이언트가 같은 키를 서로 다른 노드에서 고칠 수 있다. 어느 쪽이 나중인지 벽시계로 가리면 노드마다 시계가 어긋나 엉뚱한 쪽이 이긴다. Dynamo는 벡터 시계를 쓴다. 값마다 "노드별로 몇 번 고쳤나"를 적은 작은 표를 붙이고, 고칠 때마다 쓴 노드의 칸을 하나 올린다.
두 버전이 동시라면 저장소는 고르지 않고 둘 다 돌려준다. 장바구니라면 두 목록을 합치는 식으로 앱이 해결하고, 합친 값을 다시 쓴다. 벡터 시계는 쓰는 노드가 많아지면 표가 길어지므로 오래된 칸을 잘라 내는 규칙을 둔다. 이 복잡함이 싫으면 마지막 쓰기 승리(last write wins)로 단순하게 가는 저장소도 많다. 대신 동시에 쓴 것 중 하나는 조용히 사라진다.
잠깐의 장애는 hinted handoff로 메우지만, 힌트를 들고 있던 노드마저 죽거나 노드가 오래 빠져 있었다면 복제본끼리 내용이 어긋난 채 남는다. 이를 백그라운드에서 맞추는 일을 반엔트로피(anti-entropy)라 하고, 비교에는 머클 트리를 쓴다. 키 범위마다 내용의 해시를 잎으로 두고, 위로 올라가며 두 자식의 해시를 다시 해시한다.
뿌리 해시 하나만 비교해 같으면 전체가 같다는 뜻이니 끝이다. 다르면 자식끼리 비교해 다른 가지로만 내려간다. 키가 수억 개여도 실제로 어긋난 범위만 찾아 주고받으므로, 비교에 드는 데이터가 아주 적다. Cassandra의 repair도 같은 방식이다(문서와 와이드 컬럼 노트). 키-값 저장소를 실제로 고르는 법은 키-값 데이터베이스 노트에서 다룬다.
고유 ID 생성
데이터베이스 한 대라면 자동 증가(auto-increment) 열이 알아서 1, 2, 3을 준다. 여러 대로 나누면 서로 겹치지 않는 ID를 따로 만들어야 한다. 바라는 성질은 대개 네 가지다. 겹치지 않을 것, 중앙 서버에 묻지 않고 만들 것, 64비트처럼 짧을 것, 만든 순서대로 대략 정렬될 것(최근 글 찾기와 B-트리 인덱스에 유리하다).
- DB 자동 증가를 나눠 쓰기: Flickr는 Ticket Servers 글에서 ID만 발급하는 MySQL 서버 두 대에 증가 폭을 2로, 시작값을 1과 2로 주어 한 대는 홀수, 한 대는 짝수만 내게 했다. 단순하지만 ID를 얻으려면 매번 그 서버에 물어야 하고, 서버 수를 바꾸기 어렵다. 범위를 미리 1000개씩 받아 와 메모리에서 나눠 주면 왕복을 줄일 수 있다.
- UUID v4: 128비트 중 122비트가 무작위다. 어디서나 묻지 않고 만들 수 있고, 50% 확률로 한 번 겹치려면 약 2.7 × 1018개를 만들어야 한다. 대신 길고, 순서가 없어 B-트리 인덱스의 아무 곳에나 끼어들어 쓰기 성능을 떨어뜨린다.
- UUID v7: 2024년 RFC 9562가 RFC 4122를 대체하며 더한 형식이다. 앞 48비트가 유닉스 밀리초 시각이고 나머지가 버전·변형과 무작위 비트라, UUID의 편리함을 유지하면서 시간순으로 정렬된다. RFC도 시각이 든 UUID가 필요하면 v1·v6 대신 v7을 쓰라고 권한다.
- Snowflake: Twitter가 2010년에 공개한 Snowflake는 64비트를 부호 1, 밀리초 시각 41, 데이터센터 5, 작업자 5, 순번 12비트로 나눴다. 작업자 하나가 밀리초마다 4096개까지 만들고, 41비트 시각은 2010년 11월 기준 시각부터 약 69년을 버틴다. 작업자 번호를 겹치지 않게 나눠 주는 일과, 시계가 뒤로 가면 시각이 따라잡을 때까지 발급을 멈추는 처리가 필요하다.
로드 밸런서: 알고리즘과 L4·L7
로드 밸런서는 요청마다 어느 서버에 줄지 고른다. 흔한 방법은 몇 가지뿐이다. NGINX의 Using nginx as HTTP load balancer 문서가 기본 방법들을 짧게 보여 준다.
- 라운드 로빈: 차례대로 돌아가며 준다. 서버 성능이 다르면 가중치를 준다. 요청마다 걸리는 시간이 비슷할 때 잘 맞는다.
- 최소 연결: 지금 처리 중인 요청이 가장 적은 서버에 준다. 요청마다 걸리는 시간이 들쭉날쭉할 때 낫다.
- 해시: 클라이언트 IP나 URL의 해시로 서버를 고른다. 같은 키가 늘 같은 서버로 가서 그 서버의 로컬 캐시를 살릴 수 있다. 서버가 바뀔 때 흔들림을 줄이려 일관된 해싱을 쓴다.
- 무작위 둘 중 나은 쪽: 서버 두 대를 무작위로 골라 덜 바쁜 쪽에 준다. 모든 서버의 상태를 다 보지 않고도 부하가 꽤 고르게 퍼진다.
어느 층의 정보를 보느냐에 따라 L4와 L7 로드 밸런서로 나뉜다(층은 네트워크 노트). L4는 IP 주소와 포트만 보고 TCP 연결이나 UDP 흐름을 통째로 한 서버에 붙인다. 내용을 보지 않으니 빠르고 어떤 프로토콜이든 넘긴다. L7은 TLS를 풀고 HTTP 요청을 읽어, 경로에 따라 다른 서버 무리로 보내거나 쿠키로 사용자를 고정하거나 실패한 요청을 다시 보낼 수 있다. AWS에서 L7인 Application Load Balancer는 대상 그룹 문서에 적힌 대로 라운드 로빈(기본), 처리 중인 요청이 가장 적은 곳, 가중 무작위 중에서 고르고, L4인 Network Load Balancer는 프로토콜·주소·포트로 만든 흐름 해시로 연결마다 대상을 고정한다. 큰 서비스는 둘을 겹쳐 쓴다. Google은 Maglev 논문(NSDI 2016)에서 일반 서버에서 도는 L4 로드 밸런서가 일관된 해싱으로 연결을 백엔드에 고르게, 그리고 로드 밸런서가 바뀌어도 같은 곳으로 보내는 방법을 설명한다.
캐시 쓰기 방식
캐시를 읽는 법은 규모 확장 노트의 캐시 우선 읽기로 충분하다. 고르기 어려운 것은 쓰기다. 값을 고칠 때 캐시와 DB를 어떤 순서로, 언제 고치느냐에 따라 세 가지로 나뉜다. AWS ElastiCache 문서의 Caching strategies가 앞의 두 방식(지연 로딩, write-through)과 TTL을 비교한다.
- 캐시 우선(cache-aside): 앱이 DB를 고치고 캐시 항목을 지운다. 다음 읽기가 새 값을 채운다. 가장 흔하다. 다만 지운 직후 다른 요청이 옛 값을 읽어 다시 채우는 경쟁이 있을 수 있어 TTL을 함께 둔다.
- write-through: 쓸 때 캐시와 DB를 함께 고치고 둘 다 끝나야 응답한다. 읽을 때 늘 최신 값이 있지만 쓰기가 느려지고, 한 번도 안 읽을 값까지 캐시에 쌓인다.
- write-back(write-behind): 캐시에만 쓰고 바로 응답한 뒤, 나중에 모아서 DB에 쓴다. 쓰기가 가장 빠르고 같은 키를 여러 번 고치면 DB에는 한 번만 가지만, DB에 닿기 전에 캐시가 죽으면 그 쓰기는 사라진다. CPU 캐시와 디스크 쓰기 캐시가 쓰는 방식이고, 서비스에서는 잃어도 되는 값(조회수 같은)에 쓴다.
캐시가 무너지는 순간
캐시는 평소 DB 부하의 대부분을 막아 주기 때문에, 캐시가 빗나가는 순간 DB가 감당할 수 없는 양이 한꺼번에 쏟아진다. 인기 항목 하나가 만료되는 순간 수천 개의 요청이 동시에 놓쳐 모두 같은 질의를 DB에 보내는 것을 캐시 쇄도(cache stampede) 또는 thundering herd라 부른다.
- 한 요청만 다시 계산: 놓친 요청 중 하나만 잠금을 얻어 DB에서 읽고, 나머지는 잠깐 기다렸다 다시 캐시를 보거나 옛 값을 받는다. Facebook의 Scaling Memcache at Facebook(NSDI 2013)은 이를 리스(lease)로 구현했다. 캐시가 놓친 클라이언트에게 리스 토큰을 주되 키마다 일정 시간에 한 번만 주고, 토큰을 가진 클라이언트만 값을 채운다. 리스는 지운 뒤 늦게 도착한 옛 값이 캐시를 덮어쓰는 문제도 함께 막는다.
- 미리 갱신: 만료 직전에 확률적으로 몇몇 요청이 먼저 다시 계산하게 하면 만료가 한꺼번에 오지 않는다. Optimal Probabilistic Cache Stampede Prevention(VLDB 2015)은 다시 계산에 걸리는 시간이 길수록, 만료가 가까울수록 일찍 갱신할 확률을 높이는 방법을 제시한다.
- TTL에 흔들림 주기: 같은 시각에 넣은 항목들이 같은 시각에 만료되지 않도록 TTL에 작은 무작위 값을 더한다.
- 캐시 노드가 죽을 때: 노드 하나가 맡던 키가 모두 한꺼번에 빗나간다. 캐시를 여러 노드에 일관된 해싱으로 나눠 피해를 한 노드 몫으로 줄이고, DB 앞에 동시 질의 수 제한을 두어 DB가 무너지는 대신 일부 요청이 느려지게 한다. AWS의 Caching challenges and strategies는 캐시가 비어 있을 때의 시작(cold start)과, 캐시 덕에 줄여 둔 DB 용량이 오히려 위험이 되는 문제까지 짚는다.
- 없는 값도 캐시: 존재하지 않는 키를 계속 물으면 매번 DB까지 간다. "없음"도 짧은 TTL로 캐시해 둔다(부정 캐시).
API 게이트웨이와 BFF
서비스가 여럿으로 나뉘면 클라이언트가 그 주소를 모두 알아야 하고, 인증·TLS·처리율 제한 같은 일을 서비스마다 다시 만들게 된다. API 게이트웨이는 그 앞에 서는 하나의 입구다. Microsoft의 설계 안내는 게이트웨이의 일을 셋으로 나눈다.
- 라우팅: L7 리버스 프록시로 요청 경로를 보고 맞는 서비스로 보낸다. 클라이언트는 입구 하나만 알면 되고, 뒤의 서비스를 나누거나 합쳐도 클라이언트가 바뀌지 않는다. 옛 시스템을 조금씩 옮기는 스트랭글러 피그도 이 지점을 쓴다(진화적 아키텍처 노트).
- 집계: 화면 하나에 서비스 여러 개의 데이터가 필요하면, 클라이언트가 여러 번 부르는 대신 게이트웨이가 한 요청을 받아 뒤에서 나눠 부르고 결과를 합쳐 돌려준다. 느린 모바일 망에서 왕복이 준다.
- 떠넘기기(offloading): TLS 종료, 인증, IP 허용 목록, 처리율 제한, 로그, 응답 캐시, 압축처럼 모든 서비스에 공통인 일을 한곳에 모은다. 위 처리율 제한 절의 "어디에 두나"가 이것이다.
게이트웨이 하나가 웹·모바일·외부 파트너를 모두 맞추려 하면, 클라이언트마다 다른 요구가 한 코드에 쌓여 모든 팀이 그 팀을 기다린다. BFF(backends for frontends)는 사용자 경험 하나마다 전용 백엔드를 두는 패턴이다. Sam Newman이 2015년 글에서 SoundCloud와 REA의 사례로 정리했고, BFF는 그 화면을 만드는 팀이 함께 맡는 것이 핵심이다. 모바일 BFF는 작은 화면에 맞게 필드를 줄이고, 웹 BFF는 다른 묶음으로 데이터를 모은다. Microsoft의 BFF 패턴 문서는 클라이언트가 필요한 필드를 직접 고르는 GraphQL이 따로 BFF 층을 두는 대안이 되기도 한다고 적는다. REST·GraphQL·gRPC의 차이는 데이터 모델과 인코딩 노트에 있다.
대가도 있다. BFF를 화면마다 두면 비슷한 코드가 여러 벌 생기므로, Microsoft 문서는 중복과 화면별 맞춤 사이를 저울질하라고 적는다. 업무 로직은 BFF가 아니라 뒤의 서비스가 맡는다. 게이트웨이의 라우팅 규칙·인증서·허용 목록은 서비스를 더할 때마다 바뀌므로 인프라 코드로 관리하라는 것도 같은 안내의 권고다.
시간 제한, 재시도, 서킷 브레이커
다른 서비스를 부르는 모든 호출에는 시간 제한이 있어야 한다. 없으면 상대가 느려질 때 이쪽의 스레드와 연결이 하나씩 묶여, 느림이 호출한 쪽으로 번진다. Amazon은 받아들일 만한 잘못된 시간 초과 비율(예: 0.1%)을 먼저 정하고, 상대 서비스 지연의 그 백분위(p99.9)를 시간 제한으로 잡는 데서 출발한다고 설명한다(Timeouts, retries, and backoff with jitter).
- 재시도는 이기적이다: 같은 글의 표현이다. 일시적 실패에는 재시도가 효과적이지만, 상대가 과부하라서 실패한 것이라면 재시도는 부하를 더 얹는다. 층마다 따로 재시도하면 곱으로 커진다. 다섯 층이 저마다 3번씩 시도하면 맨 아래 DB는 243배의 요청을 받는다. 그래서 재시도 횟수를 작게 묶어 실패를 일찍 드러내고, 지수 백오프와 무작위 흔들림(지터)을 섞고, 토큰 버킷으로 재시도 자체의 양을 묶는다. 재시도할 API는 멱등해야 한다(돈이 오가는 사례 노트의 멱등 키).
- 서킷 브레이커: Michael Nygard가 『Release It!』에서 널리 알린 패턴이다(Martin Fowler). 실패할 것이 뻔한 호출을 아예 하지 않게 막는다. 재시도가 "언젠가 성공하리라"는 기대라면 서킷 브레이커는 그 반대다(Microsoft의 패턴 문서). 둘을 함께 쓸 때는 재시도가 브레이커가 열렸다는 신호를 보고 멈춰야 한다.
열림일 때 실패를 돌려주는 대신 캐시한 옛 값이나 기본값으로 답하는 것도 흔하다(우아한 성능 저하). 서킷 브레이커가 "고장 난 상대를 끊는" 장치라면, 벌크헤드는 "고장이 번지는 범위를 나누는" 장치다. 배의 칸막이처럼 의존 서비스마다 연결 풀을 따로 두면, 한 의존이 망가져 자기 풀을 다 써도 다른 서비스를 부르는 기능은 계속 돈다(Bulkhead 패턴). 이런 운영 관심사를 서비스 옆 프록시에 맡기는 서비스 메시는 분해와 분산 데이터 노트에 있다.
- RFC 6585: Additional HTTP Status Codes — IETF
- RateLimit header fields for HTTP (Internet-Draft) — IETF httpapi WG
- Rate limits for the REST API — GitHub Docs
- Scaling your API with rate limiters — Stripe
- Module ngx_http_limit_req_module — NGINX
- How we built rate limiting capable of scaling to millions of domains — Cloudflare
- Dynamo: Amazon's Highly Available Key-value Store — DeCandia et al. (SOSP 2007)
- Ticket Servers: Distributed Unique Primary Keys on the Cheap — Flickr
- RFC 9562: Universally Unique IDentifiers (UUIDs) — IETF
- Snowflake (2010 source) — Twitter
- Using nginx as HTTP load balancer — NGINX
- Target groups for your Application Load Balancers — AWS
- Maglev: A Fast and Reliable Software Network Load Balancer — Google (NSDI 2016)
- Caching strategies — Amazon ElastiCache
- Scaling Memcache at Facebook — Nishtala et al. (NSDI 2013)
- Optimal Probabilistic Cache Stampede Prevention — Vattani et al. (VLDB 2015)
- Caching challenges and strategies — AWS Builders' Library
- Use API gateways in microservices — Microsoft Learn
- Backends for Frontends pattern — Microsoft Learn
- Pattern: Backends For Frontends — Sam Newman
- Circuit Breaker pattern — Microsoft Learn
- CircuitBreaker — Martin Fowler
- Bulkhead pattern — Microsoft Learn