LabHub
배우기 러닝패스 코스

Integration and Deployment

The Nightly Batch Loses a Few Calls to 401 Every Run

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

짧은 수명의 접근 토큰을 들고 남의 API 를 계속 부르는 클라이언트를 만든다. 만료 전에 미리 갱신하고, 시계 오차 여유를 두고, 401 을 한 번만 받고 갱신 후 재시도하며, 워커가 여럿이어도 갱신이 한 번만 일어나게 한다.

왜 중요한가

"야간 배치가 매번 두세 건씩 401 로 실패합니다" 의 원인은 거의 언제나 같다. 토큰을 한 번 받아 두고 401 이 오면 그때 새로 받는 구조는 만료 순간마다 반드시 한 번은 실패한다. 토큰이 짧게 사는 것은 결함이 아니라 설계다. 수명이 짧아야 새어 나갔을 때 쓸 수 있는 기간이 짧다. 그래서 고쳐야 하는 쪽은 서버가 아니라 우리 클라이언트다. 그리고 고칠 때 세 가지를 함께 봐야 한다. 시계는 정확히 같지 않으므로 여유가 필요하고, 401 이 토큰 문제가 아닐 수도 있으므로 재시도 횟수에 상한이 필요하고, 워커가 여럿이면 만료 순간에 갱신이 한꺼번에 몰리므로 갱신을 한 번으로 묶어야 한다. 채점기는 여러분의 문장을 믿지 않는다. 인증 서버를 채점기가 고른 포트에 직접 띄우고 발급 횟수와 자원 호출 횟수를 서버 쪽에서 세어, 여러분의 클라이언트가 실제로 몇 번 갱신하고 몇 번 다시 걸었는지 대조한다.

단계

  1. /root/token/authsrv.py 를 만들어 포트 8015 에 띄우고, 토큰 하나를 받아 /root/token/token.json 에 저장하세요.
  2. /root/token/decode.py 를 만들어 토큰 안의 헤더와 페이로드를 읽어 /root/token/claims.json 에 적으세요.
  3. /root/token/should_refresh.py 를 만들어 만료·미도래·임박·정상 네 갈래를 시계 오차 여유까지 감안해 가려내게 하세요.
  4. /root/token/client.py 를 만들어 만료 전에 미리 갱신하며 여러 번 호출해도 401 이 한 건도 나지 않게 하세요.
  5. client.py 가 401 을 받으면 갱신 후 딱 한 번만 다시 걸게 하세요.
  6. client.py 가 워커 여럿이 동시에 시작해도 토큰 발급이 한 번만 일어나게 하세요.
  7. /root/token/token_policy.json 에 정책 표를 적으세요.
  8. /root/token/token_report.md 에 네 절로 보고하세요.

참고

짧게 사는 토큰 받아 보기

/root/token/authsrv.py 를 만들어 포트 8015 에 띄우고, POST /oauth/token 으로 토큰 하나를 받아 응답 전체를 /root/token/token.json 에 저장하세요.

JWT 는 base64url 로 인코딩한 헤더·페이로드와 HMAC 서명을 점으로 이어 붙인 것입니다. 파이썬 표준 라이브러리의 hmac·hashlib·base64 만으로 만들 수 있습니다. 발급 수와 자원 호출 수를 서버가 세어 두면 나중에 클라이언트가 실제로 몇 번 갱신했는지 확인할 수 있습니다.

토큰 안을 열어 보기

/root/token/decode.py 를 만들어 --in 으로 받은 토큰 응답에서 JWT 를 꺼내 헤더와 페이로드를 읽고, --out 으로 /root/token/claims.json{"header": ..., "payload": ..., "ttl_s": exp - iat} 를 적게 하세요.

JWT 의 앞 두 조각은 암호가 아니라 base64url 로 인코딩된 평문입니다. base64url 은 패딩(=)을 떼고 다니므로 디코드 전에 길이를 4의 배수로 맞춰 줘야 합니다. 여기서 하는 일은 읽기지 검증이 아니라는 점을 기억하세요 — 이 값을 근거로 권한을 판단하면 안 됩니다.

언제 갈아야 하는가

/root/token/should_refresh.py 를 만들어 --exp·--now·--skew-s·--nbf 로 판정하게 하세요. 판정 순서는 nbf, 만료, 임박이고 reason 은 not_yet_valid·expired·near_expiry·ok 입니다. 임박한 토큰은 아직 쓸 수 있으므로 valid 는 true 입니다.

시계는 정확히 같지 않습니다. nbf 는 우리 쪽에 여유를 주어 보아야 방금 받은 토큰이 '아직 유효하지 않음' 으로 거절되지 않습니다. 임박 판정은 남은 시간 <= 여유 한 줄이면 됩니다. remaining_s 는 만료 뒤에는 음수가 되어야 합니다.

만료 전에 미리 갈기

/root/token/client.py 를 만들어 --calls 번 자원을 부르되, 매번 토큰이 임박했는지 확인하고 임박했으면 먼저 갱신하게 하세요. 토큰 수명보다 긴 시간 동안 호출해도 unauthorized 가 0 이어야 합니다.

앞 단계의 판정기를 모듈로 불러 쓰면 규칙이 한 군데에만 있게 됩니다. 토큰은 파일에 담아 두고, 호출 직전에 '이걸 써도 되는가' 를 물으세요. 수명 3초짜리 토큰으로 3초 넘게 호출하면 갱신이 실제로 일어났는지 서버의 발급 수로 확인할 수 있습니다.

401 은 한 번만 받는다

client.py 가 401 을 받으면 갱신하고 딱 한 번만 다시 걸게 하세요. 갱신해도 계속 401 이면 그 호출은 실패로 끝내야 합니다. --start-expired 로 만료된 토큰을 들고 시작할 수 있어야 합니다.

권한이 모자란 상황에 401 을 주는 서버가 많습니다. 그런 서버에서 재시도 횟수에 상한이 없으면 갱신과 401 이 끝없이 반복되며 인증 서버를 때립니다. 자원 호출이 호출당 정확히 두 번만 나가는지 서버의 호출 수로 확인하세요.

만료 순간에 스무 개가 한꺼번에 갱신한다

client.py 가 --workers 로 동시에 여러 호출을 하더라도 토큰 발급이 한 번만 일어나게 하세요. 캐시 파일과 잠금을 쓰고, 잠금을 잡은 뒤 캐시를 한 번 더 확인해야 합니다.

잠금만 걸면 갱신이 줄 서서 일어날 뿐 횟수는 그대로입니다. 기다리는 사이에 남이 이미 갈아 두었을 수 있으므로, 잠금을 잡은 뒤에 캐시를 다시 읽어 쓸 만한 토큰이 있으면 그것을 쓰세요. 파이썬에서는 fcntl 의 파일 잠금을 씁니다.

정책 표로 못박기

/root/token/token_policy.jsontoken_ttl_s·refresh_skew_s·max_retry_on_401·cache_path·stampede_guard·clock_sync·notes 를 적으세요. refresh_skew_s 는 1 이상이고 수명보다 작아야 하며, max_retry_on_401 은 1 이하입니다.

값의 근거를 notes 에 한 줄로 남기세요. 여유를 수명의 몇 퍼센트로 잡았는지, 왜 재시도를 1로 묶었는지가 들어가면 됩니다. 이 표는 나중에 파트너가 수명을 바꿨을 때 우리가 무엇을 같이 바꿔야 하는지 알려 주는 문서이기도 합니다.

토큰 점검 보고서

/root/token/token_report.md## 토큰이 어떻게 생겼나 ## 언제 갈아야 하나 ## 401 을 받으면 무엇을 하나 ## 워커가 여럿일 때 네 절로 적으세요. claims.json 과 token_policy.json 의 값이 본문에 들어가야 합니다.

읽는 사람은 야간 배치가 왜 401 로 죽는지 묻는 사람입니다. '토큰이 만료돼서' 로 끝내지 말고, 만료는 정상이고 문제는 우리가 만료를 다루는 방식이었다는 점을 숫자로 보여 주세요.