일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- react-query 도입후기
- React
- state 관리
- state 사용하기
- react-query v5
- 리액트
- javascript
- TypeScript
- SW캡스톤디자인
- router instance
- 사내 오류 대응
- MSW
- JS변수
- node.js
- no router instance found
- 더블엔씨
- 사내 오류 해결
- Next.js
- react-query&Next.js
- 결제페이지
- nextjs
- 캡스톤디자인 후기
- ClientSide
- 기술 낙서장
- react-query
- 일상 생각
- 사내 이슈
- 기술낙서장
- SSR
- React.JS
- Today
- Total
코딩을 잘하고 싶은 코린이 동토니
스마트 스토어 자동화 프로그램 429에러 처리하기 본문
1. 문제 발생
스마트 스토어 관련 슬랙 채널에 429에러 다수 발생
2. 문제 확인
네이버 커머스 API 확인시 429코드 관련 공지가 올라와있음
(신규 요청 제한 정책) https://github.com/commerce-api-naver/commerce-api/discussions/1538 (구 요청 제한 정책) https://github.com/commerce-api-naver/commerce-api/discussions/6
아래는 네이버 커머스 api 요청량 제한 관련 공식 문서
과도한 트래픽으로부터 서비스를 보호하기 위해 커머스API에 초당 최대 동시 요청 수를 제한하는 요청량 제한(rate limit)이 적용되어 있습니다.
커머스API는 'Token bucket' 알고리즘 기반 요청량 제한을 개별 API와 인증된 애플리케이션 단위로 처리합니다.
요청량 제한을 초과하는 요청에는 다음과 같은 응답이 전송됩니다.
- HTTP 상태 코드: 429(Too Many Requests - RFC 6585)
- 오류 코드: `GW.RATE_LIMIT`
개발사는 API 응답 헤더에서 현재 요청한 API의 요청량 관련 상태를 확인할 수 있습니다. 관련 헤더에 대한 설명은 다음과 같습니다.
- GNCP-GW-RateLimit-Replenish-Rate: 초당 최대 동시 요청 수
- GNCP-GW-RateLimit-Burst-Capacity: 버스트 모드(burst mode)로 처리 가능한 초당 최대 동시 요청 수
- 커머스API는 API별 요청량 제한 값의 두 배를 버스트 요청 값으로 정의하고 있습니다. 즉, 요청량 제한 값을 넘겼을 경우 최대 두 배까지 처리 가능하지만 그만큼 다음 1초에 호출 가능한 요청 건은 줄어듭니다.
- GNCP-GW-RateLimit-Remaining: 남은 동시 요청 수
#### Quota Limit: (단위) 시간당 판매자 리소스 요청 수 제한
커머스API는 초당 최대 동시 요청 수 제한과 다르게 좀 더 세밀한 단위의 요청량 제한 수단을 제공하고 있습니다. 시간당 판매자 리소스 요청 수 제한이 필요한 경우는 다음과 같습니다.
- API데이터솔루션을 구독 후 API를 호출하는 판매자
- 단위 시간: 구독 회차
- 커머스솔루션을 구독 중인 판매자 리소스에 대한 API를 호출하는 개발업체
- 단위 시간: 1초
요청량 제한을 초과하는 요청에는 다음과 같은 응답이 전송됩니다.
- HTTP 상태 코드: 429(Too Many Requests - RFC 6585)
- 오류 코드: `GW.QUOTA_LIMIT`
개발사는 API 응답 헤더에서 현재 요청한 API의 요청량 관련 상태를 확인할 수 있습니다. 관련 헤더에 대한 설명은 다음과 같습니다.
- GNCP-GW-Quota-Period: 단위 시간
- `SECONDS` 초
- `ROUND` 회차 (API데이터솔루션 구독)
- GNCP-GW-Quota-Limit: 시간당 최대 요청 수
- GNCP-GW-Quota-Remaining: 남은 요청 수
하지만 res.headers 확인시 quota 값에 대해선 확인이 불가
- GNCP-GW-RateLimit-Remaining
- GNCP-GW-RateLimit-Burst-Capacity
- GNCP-GW-RateLimit-Replenish-Rate
위 3개의 RateLimit에 대한 res.headers만 확인이 가능했음
요약하자면
이전 버전에 대한 요청량 제한은 2rps/어플리케이션 으로
초당 요청량 제한 2 최대 4 까지 허용이 되었으며 시간당 제한이 걸려있지않았음
신규 버전 요청량 제한의 경우
res.headers를 통해 확인했을때
초당 제한의 경우 4, 최대 8 까지 늘려준것으로 확인
하지만 1시간당 요청량 제한이 새로생긴것으로 추측됨
이부분은 직접 확인 할 방법이 없어 예상만 함
3. 임시 대응 방안
- 첫 임시 대응은 각 요청당 default sleep time 기존 800ms => 1500ms 로 변경 및 429 에러 발생시 즉시 슬랙채널로 알림 발송되도록 변경
임시 대응후에도 동일 에러 발생
로그 확인시 default sleep time이 과도하게 늘어나 오히려 요청들이 밀리면서 한번에 처리되는 요청들이 발생하게됨
해당 로그 확인후 default sleep time 1500ms => 1000ms 로 조정 및 orderDelivery시 추가되어있는 sleepTime은 800ms 로 변경
- jobs queue 구현
스마트 스토어 프로젝트에는 총 6개의 program이 구현되어 있음 (아래 코드 참고)
const ONE_MINUTE = 1000 * 60
const ONE_HOUR = ONE_MINUTE * 60
const serve = async () => {
try {
... set apiClient (1)
// 토큰 재발급및 apiClient 재설정
setInterval(async () => {
await updateToSoldOut()
}, ONE_MINUTE * 5) (2)
setInterval(async () => {
await bulkUpdate()
}, ONE_MINUTE * 19) (3)
setInterval(async () => {
await cancellationAndRefundRequest()
}, ONE_MINUTE * 31) (4)
setInterval(async () => {
await orderDelivery()
}, ONE_MINUTE * 2) (5)
setInterval(async () => {
await cancelDelayedOrder()
}, ONE_HOUR * 23) (6)
} catch (e) {
handleError(e)
}
}
일정 시간마다 해당 program들이 스케쥴되어 반복되어 실행하도록 구현되어있음
각각의 program에 대해 api 내부적으로 queue를 구축하여 특정 program이 돌고있는동안에는 다른 program이 돌지 않도록 방지하여 동시 요청에 대한 제한을 두도록 변경함
하지만 orderDelivery와 setToken의 경우 queue에 넣게되면 발송처리가 지연되며 token이 정상적인 시간대에 reset되지 않아 버그가 발생 할 수 있음
따라서 해당 두 program은 독립적으로 병렬로 돌아도 상관없게끔 구현
아래 코드 참조
type TaskFunction = () => Promise<void>
interface ScheduledTask {
task: TaskFunction
interval: number
}
const scheduledTasks: ScheduledTask[] = [
{ task: updateToSoldOut, interval: 5 },
{ task: bulkUpdate, interval: 19 },
{ task: cancellationAndRefundRequest, interval: 31 },
{ task: cancelDelayedOrder, interval: 23 * 60 },
]
let taskQueue: TaskFunction[] = []
let minutesElapsed = 0
const runTaskQueue = async (): Promise<void> => {
while (taskQueue.length > 0) {
const task = taskQueue.shift()
try {
if (task) {
await task()
}
} catch (error) {
await postToSlack(`🚨 *queue 실행 실패* 🚨\\\\n *error*: ${error}`)
console.error('Error executing task:', error)
}
}
}
const fillTaskQueue = (): void => {
//1분마다 ++
minutesElapsed++
// 진행된 시간이 scheduledTasks의 interval에 맞으면 taskQueue에 task 추가
scheduledTasks.forEach((scheduledTask) => {
if (minutesElapsed % scheduledTask.interval === 0) {
if (taskQueue.length < MAX_QUEUE_LENGTH) {
taskQueue.push(scheduledTask.task)
}
}
})
if (taskQueue.length > 0) {
runTaskQueue()
}
}
const startScheduler = (): void => {
//최초 세팅
taskQueue = scheduledTasks.map((scheduledTask) => scheduledTask.task)
// 30분마다 토큰 재발급
setInterval(async () => {
await setApiClient()
await setNaverClient()
await setCmsClient()
}, ONE_SECOND * 29)
// 2분마다 주문배송
setInterval(orderDelivery, ONE_SECOND * 2)
// 1분마다 큐 채우기 및 채워진 큐가 있으면 해당 큐 task 실행
setInterval(fillTaskQueue, ONE_SECOND)
}
startScheduler()
4. 추후 대응 방안
현재 임시 대응책으로 program당 queue를 구축하여 동작하도록 설계 해두었음
하지만 근본적으로 해결하기 위해선 + 앞으로 네이버 요청량 제한에 대한 정책이 어떻게 될지 미지수이므로 다음과 같은 대응이 추가적으로 필요
- program 당 queue에서 도는것이 아닌 naverClient api 요청에 대하여 queue를 구축하여 요청당 queue가 돌도록 구현 필요
- api-v2및 cms 요청의 경우 요청량 제한에 걸리지 않으므로 해당 요청들은 제외 할 수 있으나 req,res 관리가 묶어서 이루어져야 하므로 구현 난이도가 굉장히 높을것으로 예상 중
'기술낙서장' 카테고리의 다른 글
사내 결제 페이지 개편하기 (1) | 2024.09.03 |
---|---|
사내 스마트 스토어 옵션 가격 책정 오류 수정하기 (1) | 2024.09.03 |
스마트 스토어 프로그램 health Check (1) | 2024.09.02 |
playwright로 네이버 스마트스토어 자동 로그인 구현하기 (1) | 2024.09.02 |