자호스팅 인스턴스에 대한 API 요청 시 “429 Too Many Requests” 메시지가 표시되고 있습니다.
DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE를 600으로 증가시켰음에도 불구하고
app.yml의 env 섹션에 이 값을 설정하고 ./launcher rebuild를 실행한 후, 재빌드된 컨테이너에서 변수가 설정되었는지 확인했습니다.
이는 제가 시도하고 있는 분당 요청 수보다 훨씬 많습니다.
제한이 없는 관리자 API 키 사용
DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE를 변경해도 작동하지 않는 이유에 대해 명확한 답변 없이 이전에 논의된 적이 있는 것 같습니다:
관리자 키/사용자를 통한 API 요청이 스로틀링(throttling) 대상이 되지 않도록 보장하는 방법은 무엇입니까?
Bas
(Bas van Leeuwen)
8월 8, 2023, 8:39오전
2
aas님, 안녕하세요.
조금 더 맥락을 알려 주실 수 있을까요?
API 요청을 얼마나 많이 보내고 계신가요? 초당, 분당, 시간당, 하루 기준
관리자용 API 키를 사용하고 계신지 확인하셨나요?
이 모든 요청이 동일한 IP 주소에서 발생하고 있나요? 리버스 프록시 때문일 수도 있습니다.
nginx 또는 다른 소프트웨어가 해당 오류를 발생시키고 있는 것은 아닐까요?
aas
10월 3, 2023, 10:52오후
3
Bas님, 안녕하세요.
늦게 답변드려 죄송합니다!
최근 Discourse 통합 기능을 출시했기 때문에, 속도 제한(rate limiting) 관련 문제가 발생하지 않도록 다시 확인 중입니다.
Bas van Leeuwen:
Admin API 키를 사용하고 계신지 확인해 보셨나요?
어떤 방식으로든 제한이 걸리지 않았는지 확인하기 위해 새 키로 테스트를 수행했습니다. 명확히 하자면, 'Admin API 키’라고 정확히 어떤 것을 말씀하시는 건가요?
다음과 같은 설정으로 키를 생성했습니다:
"API 키에 제한이 없으며 모든 엔드포인트에 접근할 수 있습니다."라고 표시되어 있습니다.
Bas van Leeuwen:
이 모든 요청이 동일한 IP 주소에서 온 건가요? 리버스 프록시 때문일 수도 있나요?
로컬 Python 셸에서 API 요청을 보내며 테스트하고 있으므로, 모든 요청은 동일한 IP 주소에서 나옵니다. 또한 서버에서 스크립트를 실행할 때도 속도 제한에 부딪혔습니다. 그 경우에도 모든 요청은 동일한 IP 주소에서 발생했습니다.
다음 코드로 속도 제한이 적용되는 것을 확인했습니다:
async def get_topic_post_stream(topic_id):
url = f"{DISCOURSE_URL}/t/{topic_id}"
async with httpx.AsyncClient(headers=HEADERS) as client:
topic = await client.get(url)
return topic.status_code
async def get_topic_post_streams(topic_ids):
tasks = [functools.partial(get_topic_post_stream, topic_id) for topic_id in topic_ids]
topics = await aiometer.run_all(
tasks,
# max_per_second=1,
)
return topics
# 테스트를 위해 topic_ids의 15개 항목만 가져옵니다.
topics = asyncio.run(get_topic_post_streams(topic_ids[:15]))
max_per_second 매개변수가 주석 처리되어 있어 요청 횟수에 제한이 걸리지 않는 상태임을 참고해 주세요.
이 작업은 2.05초 만에 완료되며, 15개 요청 중 2개가 429 상태 코드를 반환합니다.
max_per_second=1로 설정하여 실행하면 모든 작업이 성공적으로 완료됩니다.
추가로 제공해야 할 정보가 있다면 알려주세요. 감사합니다!
aas
10월 10, 2023, 10:41오후
4
@Bas , 아래는 이 문제를 개발자 도구 콘솔에서 더 쉽게 재현할 수 있도록 Python 코드의 JavaScript 버전입니다:
const DISCOURSE_URL = '';
const HEADERS = {
'Api-Key': '',
'Api-Username': '',
'Content-Type': 'application/json'
};
const topicIds = Array.from({ length: 100 }, (_, i) => i + 1);
async function getTopicPostStream(topicId) {
const url = `${DISCOURSE_URL}/t/${topicId}`;
const response = await fetch(url, { headers: HEADERS });
return response.status;
}
async function getTopicPostStreams(topicIds) {
const results = await Promise.all(topicIds.map(topicId => getTopicPostStream(topicId)));
return results;
}
// 요청에 레이트 리미팅을 적용하지 않으면 두 개의 429 오류가 발생함을 확인하세요.
(async () => {
const topics = await getTopicPostStreams(topicIds.slice(0, 15));
console.log(topics);
})();
async function getTopicPostStreamsRateLimited(topicIds) {
const results = [];
for (const topicId of topicIds) {
const result = await getTopicPostStream(topicId);
results.push(result);
await new Promise(resolve => setTimeout(resolve, 1000)); // 1초 지연
}
return results;
}
// 초당 1개의 요청을 보내면 모든 응답이 200을 반환합니다.
(async () => {
const topics = await getTopicPostStreamsRateLimited(topicIds.slice(0, 15));
console.log(topics);
})();
Bas
(Bas van Leeuwen)
10월 11, 2023, 12:19오후
5
추측컨대, 이것이 가장 가능성 높은 문제입니다. API 자체에서 속도 제한을 받는 것이 아니라, IP 주소 기준으로 제한을 받고 있는 것입니다.
아래 링크에서 IP별 설정을 확인해 보실 수 있습니다: Available settings for global rate limits and throttling - #27
이것이 실제로 문제를 해결했는지 알려주세요
aas
10월 12, 2023, 2:01오후
6
고맙습니다, @Bas !
그 게시물에 언급된 설정과 무관하게, 제가 429 오류를 받는 것은 맞지 않다고 생각합니다. 제가 제공한 예시에서는 15개의 요청을 보냈는데, 이는 모든 기본 API 한도 미만입니다. 이 작업은 관리자 API 키와 사용자명을 사용하여 수행했습니다.
해당 예시는 다음 IP당 기본 한도를 초과하지 않습니다:
관리자가 아닌 한도조차도 초과하지 않습니다:
DISCOURSE_MAX_REQS_PER_IP_MODE를 warn 또는 none으로 변경해도 도움이 되지 않았습니다.
무언가를 놓치고 있는 건가요?
참고로, 저는 app.yml을 편집하고 ./launcher destroy app && ./launcher start app을 실행하여 설정을 변경했습니다.
aas
10월 12, 2023, 3:10오후
7
/var/log/nginx/access.log에서 IP 주소가 정확하다는 것을 확인할 수 있으므로, Discourse가 모든 요청이 동일한 IP에서 온다고 판단하지 않는다고 생각합니다.
관리자 화면에서도 사용자들의 IP 주소를 확인할 수 있습니다.
제가 수정한 설정은 다음과 같습니다:
DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE: 1200
DISCOURSE_MAX_USER_API_REQS_PER_MINUTE: 60
DISCOURSE_MAX_REQS_PER_IP_MODE: none
DISCOURSE_MAX_REQS_PER_IP_PER_10_SECONDS: 100
DISCOURSE_MAX_REQS_PER_IP_PER_MINUTE: 400
수정: 방금 실패한 요청 중 하나의 응답 내용을 확인해 보니 nginx에 대한 언급이 있었습니다:
<html>\r\n<head><title>429 Too Many Requests</title></head>\r\n<body>\r\n<center><h1>429 Too Many Requests</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n
nginx를 언급하는 주제들에 대해 더 조사해 보겠습니다.
aas
10월 12, 2023, 9:28오후
8
nginx 설정에서 관련되는 두 부분은 다음과 같습니다:
limit_req_zone $binary_remote_addr zone=flood:10m rate=12r/s;
limit_req_zone $binary_remote_addr zone=bot:10m rate=200r/m;
limit_req_status 429;
limit_conn_zone $binary_remote_addr zone=connperip:10m;
limit_conn_status 429;
server {
listen 80;
return 301 https://community.ankihub.net$request_uri;
}
그리고
location @discourse {
add_header Strict-Transport-Security 'max-age=31536000'; # remember the certificate for a year and automatically connect to HTTPS for this domain
limit_conn connperip 20;
limit_req zone=flood burst=12 nodelay;
limit_req zone=bot burst=100 nodelay;
proxy_set_header Host $http_host;
proxy_set_header X-Request-Start "t=${msec}";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $thescheme;
proxy_pass http://discourse;
}
}
지금 제 남은 질문은 다음과 같습니다:
Discourse 설정에 맞추기 위해 두 부분 모두를 수정해야 합니까? 아니면 location @discourse의 값만 수정하면 될까요?
이 값들을 수정하고 재빌드(rebuild) 후에도 변경 사항이 유지되도록 하는 올바른 방법은 무엇입니까?
컨테이너 내에서 nginx 설정을 직접 편집한 후 컨테이너를 중지/시작하면 된다고 생각합니다. 하지만 이 값들은 원래 templates/web.ratelimited.template.yml에서 온 것으로 보이며, 재빌드 시 덮어쓰기(overwrite)될 수 있는 것 같습니다?
도움 주셔서 감사합니다!
Bas
(Bas van Leeuwen)
10월 13, 2023, 8:06오전
9
아, 이제 내 전문 분야를 벗어난 것 같네요.
만약 nginx에 의해 레이트 리미팅(요청 제한)을 받고 있다면, 해당 설정을 조정하여 제한을 완화하는 것이 합리적인 선택일 수 있습니다. nginx가 IP 주소를 화이트리스트에 추가할 수 있는지 확실하지는 않네요.
RGJ
(Richard - Communiteq)
10월 13, 2023, 8:58오전
10
다음과 같은 방식으로 설정할 수 있습니다.
map $remote_addr $exclude_from_limit {
default 0;
192.168.1.1 1;
192.168.1.2 1;
}
그리고 제한 설정을 if 블록으로 감싸줍니다.
if ($exclude_from_limit = 0) {
limit_req zone=flood burst=24 nodelay;
limit_req zone=bot burst=400 nodelay;
limit_conn connperip 20;
}
```\n
예, 빌드 시 pups를 사용하여 치환 작업을 수행하여 이를 영구적으로 유지해야 합니다. 이를 어떻게 접근해야 하는지 확인하려면 `web.ssl.template.yml` 파일을 참고하세요.
또는 이 문제를 무시하고, 전략적인 위치에 일부 sleep을 삽입하여 API 클라이언트 스크립트의 실행 속도를 늦추는 방법도 있습니다. <-- 권장 방법
pfaffman
(Jay Pfaffman)
10월 14, 2023, 10:11오후
11
예를 들어, 레이트 리미팅(rate limiting)에 걸렸을 때 rescue 블록 안에서 처리하는 방식이죠. 저는 보통 그렇게 한다고 생각하는데, 실제로도 그렇게 하는 편입니다.
Isambard
(Isambard)
3월 12, 2024, 5:01오후
12
표준 설치 환경에서 API 요청의 최대 지속 가능한 속도는 분당 또는 초당 몇 건인가요?
초당 1건으로 시도했는데 한도에 도달한 것 같습니다. 반면, 7초에 1건으로 요청하면 문제가 없습니다.