쿠팡 파트너스 API 연동 삽질기: HMAC 서명과 캐싱으로 검색 한도 극복하기
안녕하세요, 코딩아빠입니다. 오늘 제가 풀어낼 이야기는 외부 API를 연동하면서 겪었던 실제 경험을 정리한 노트입니다. 이번에는 쿠팡 파트너스 API를 사용하며 겪었던 HMAC 서명 구현의 난관과 엄격한 검색 한도에 대한 고민을 어떻게 해결했는지 담담히 기록해 보려고 합니다. 처음에는 상품 데이터를 안정적으로 수집하기 위해 API를 활용하려 했지만, 복잡한 인증 방식과 예상치 못한 호출 제약에 부딪혀 한동안 고생을 좀 했습니다. 젊은 친구들이야 이런 문제를 뚝딱 해결하겠지만, 저는 좀 더 시간을 들여 차근차근 접근했습니다.
대상: 쿠팡 파트너스 API 연동을 계획하는 개발자, 서명 기반 인증 API 구현에 어려움을 겪는 개발자, 외부 API 호출 시 캐싱 전략에 관심 있는 개발자
난이도: 중급
이번에 정리한 내용
- 쿠팡 파트너스 API의 HMAC-SHA256 서명 생성 과정에서 유의할 점
- API 호출 한도에 효과적으로 대응하는 캐싱 전략 구축 방법
- API 응답 코드 429(Too Many Requests)를 처리하는 견고한 로직 구현
- 파이썬에서
requests와Redis를 활용한 외부 API 연동 사례
예상치 못한 난관: HMAC 서명 생성의 까다로운 규칙들
처음 쿠팡 파트너스 API 문서를 보면서 HMAC-SHA256 서명을 만들어야 한다는 것을 알았습니다. 단순히 키와 데이터를 조합하는 것이 아니라, 타임스탬프, HTTP 메서드, 요청 URI, 쿼리 파라미터, 심지어 바디 해시까지 포함하여 복잡한 문자열을 특정 규칙에 따라 정규화하고 인코딩해야 하더군요. 이 과정에서 가장 많이 시간을 허비했던 부분은 바로 요청 URI의 정규화와 쿼리 파라미터의 정렬 순서였습니다. 문서에 분명히 설명되어 있었음에도 불구하고, 제가 가진 일반적인 REST API 호출 지식만으로는 미처 파악하지 못했던 미묘한 차이들이 있었던 것이지요.
특히 URI 정규화 부분에서 쿼리스트링을 포함한 전체 경로를 정확히 일치시키는 것이 중요했습니다. 예를 들어, /?param=value와 /path?param=value는 완전히 다른 URI로 취급된다는 점을 간과하면 서명이 계속 맞지 않는 오류를 겪게 됩니다. 쿼리 파라미터 역시 알파벳 순으로 정렬한 뒤 URL 인코딩해야 하는데, 이 순서를 지키지 않으면 서명 값 자체가 달라지니 API 서버에서는 '서명이 유효하지 않다'는 응답을 계속 보내왔습니다. 처음에는 제 API 키나 시크릿 키가 잘못된 줄 알고 몇 번이나 재발급받아 보기도 했지요.
이런 시행착오 끝에 결국 문서에 명시된 모든 규칙을 빠짐없이 적용하여 서명 생성 로직을 완성할 수 있었습니다. 아래는 서명 생성 로직의 핵심 부분입니다. 메시지 문자열을 구성하는 방식이 중요합니다.
def generate_hmac_signature(method, uri, secret_key, access_key, params=None, body_hash=''):
# ... (생략된 서명 생성 로직)
msg = f'{method}\n{uri}\n{parsed_query}\n{body_hash}'
h = hmac.new(secret_key.encode('utf-8'), msg.encode('utf-8'), hashlib.sha256)
return base64.b64encode(h.digest()).decode('utf-8')
위 코드에서 msg 변수를 만드는 부분이 바로 핵심입니다. HTTP 메서드, 정규화된 URI, 정렬된 쿼리 파라미터, 그리고 바디 해시를 \n으로 구분하여 하나의 문자열로 만드는 것이죠. 이 문자열을 가지고 HMAC-SHA256 해싱을 수행한 뒤 Base64 인코딩을 거치면 최종 서명 값이 나오게 됩니다. 이렇게 서명 생성 로직을 완벽하게 구현하고 나니 비로소 200 OK 응답을 받을 수 있었습니다. 테스트용 API 키로 반복해서 호출하며 서명이 제대로 되는지 꼼꼼히 확인했네요.
잦은 429 응답, 검색 한도 초과에 대한 해법으로 캐싱 도입
HMAC 서명 문제를 해결하고 나니 또 다른 난관이 기다리고 있었습니다. 바로 쿠팡 파트너스 API의 엄격한 검색 엔드포인트 호출 한도였죠. 시간당 특정 횟수 이상 호출하면 429 Too Many Requests 응답을 받게 되었는데, 이는 서비스 안정성에 직접적인 위협이 될 수 있었습니다. 처음에는 단순히 재시도 로직을 넣을까도 생각했지만, 근본적인 해결책이 아니라는 판단이 들었습니다. 무턱대고 재시도하는 것은 API 서버에 더 많은 부하를 주는 셈이니까요.
제가 선택한 방법은 24시간 유효한 캐시 시스템을 구축하는 것이었습니다. Redis를 활용하여 동일한 검색 요청에 대해서는 API를 다시 호출하는 대신 캐시된 데이터를 반환하도록 했습니다. 캐시 키는 API 엔드포인트와 요청 파라미터를 조합하여 고유하게 만들었지요. 이렇게 하면 자주 요청되는 검색 쿼리에 대해서는 한 번의 API 호출만으로도 여러 번 데이터를 제공할 수 있게 됩니다. 이는 호출 한도를 지키면서도 사용자에게 빠른 응답을 제공하는 효과적인 방법이라고 생각했습니다.
아래는 제가 구현한 캐시 시스템의 핵심 로직입니다. fetch_func는 실제 API 호출 로직을 담고 있습니다.
def get_or_set_cache(key, fetch_func, expiry_seconds=86400):
cached_data = cache.get(key)
if cached_data:
return json.loads(cached_data)
data = fetch_func()
cache.set(key, json.dumps(data), ex=expiry_seconds)
return data
이 get_or_set_cache 함수는 먼저 cache.get(key)로 캐시된 데이터가 있는지 확인합니다. 만약 데이터가 있다면 즉시 반환하고, 없다면 fetch_func를 호출하여 새로운 데이터를 가져온 뒤 캐시에 저장하고 반환하는 방식입니다. expiry_seconds를 86400초(24시간)로 설정하여 하루 동안은 같은 검색 결과를 재활용하도록 했습니다. 이렇게 캐시를 도입하고 나니, 고의로 호출 한도를 초과하는 상황을 재현했을 때도 캐시에서 데이터를 가져와 안정적으로 응답하는 것을 확인할 수 있었네요.
429 응답 감지 및 추가 호출 중단으로 서비스 보호하기
캐싱 시스템으로 호출 한도 문제를 상당 부분 해결했지만, 만에 하나 캐시가 작동하지 않거나 새로운 검색 쿼리가 폭주하여 API 호출 한도를 초과하는 상황이 발생할 수도 있습니다. 이런 경우를 대비해 429 응답을 감지하면 즉시 추가 호출을 중단하고 다음 실행까지 대기하도록 하는 방어 로직을 마련했습니다. 이는 단순한 에러 처리를 넘어, API 서버에 대한 예의이자 저희 서비스의 안정성을 지키는 중요한 장치라고 생각합니다.
API 응답을 받은 후 상태 코드를 확인하여 429라면 특정 예외를 발생시키고, 이 예외를 상위 로직에서 처리하도록 설계했습니다. 이렇게 함으로써 불필요한 API 호출을 즉시 막고, 일정 시간 동안 API 호출을 자제하게 되는 것이죠. 아래는 429 응답을 감지하는 핵심 코드입니다.
if response.status_code == 429:
logger.warning('API 호출 한도 초과: 429 응답을 받았습니다. 다음 실행까지 대기합니다.')
raise RateLimitExceededException('Rate limit exceeded')
이 로직을 통해 서비스는 429 응답을 받았을 때 즉시 호출을 멈추고 경고 로그를 남기게 됩니다. 이렇게 되면 개발팀에서는 문제가 발생했음을 빠르게 인지하고 대응할 수 있습니다. 고의로 호출 한도를 초과하는 상황을 재현하여 이 로직이 제대로 작동하는지 검증했습니다. 429 응답이 오자마자 즉시 호출이 중단되고 예외가 발생하는 것을 확인했습니다. 이런 자동화된 보호 장치 덕분에 마음 편히 서비스를 운영할 수 있게 되더군요.
마치며
쿠팡 파트너스 API를 연동하면서 겪었던 HMAC 서명 구현과 호출 한도 극복 과정은 저에게 많은 것을 가르쳐 주었습니다. 특히 외부 API 문서를 꼼꼼히 읽고 그 규칙을 정확히 따르는 것이 얼마나 중요한지 다시 한번 깨닫게 되었습니다. 단순히 구현하는 것을 넘어, 발생할 수 있는 문제 상황까지 미리 예측하고 캐싱이나 호출 제어 로직으로 방어하는 것은 안정적인 서비스 운영에 필수적인 부분이라고 생각합니다. 이 글이 비슷한 문제를 겪고 계신 다른 개발자분들에게 작은 도움이 되기를 바랍니다.

Top comments (0)