Intro
- 저는 본인확인 콜백을 다루면서 두 번이나 세션이 비는 사고를 맞았어요.
- 브라우저는 GET으로, 일부 모바일 브라우저는 POST로 돌아오고, 로드밸런서는 세션을 가끔 잊어버리더군요.
- 그래서 세션만 믿지 말고 메모리 캐시를 함께 쓰는 하이브리드 전략을 직접 설계했습니다.
핵심 아이디어 요약
- 토큰 기준으로 세션과 프로세스 메모리에 동시에 데이터를 저장합니다.
- 콜백이 들어오면 메모리 캐시를 먼저 확인하고, 없으면 세션을 폴백합니다.
- 1시간짜리 만료 타이머를 두어 인증 정보가 무한정 남지 않도록 합니다.
준비와 선택
- 로드밸런서(ALB) 뒤에서 세션 스티키가 잘 걸리지 않는다는 걸 인정하고, 세션만으로는 불안하다는 결론을 냈습니다.
- Redis까지 들고 오기엔 과한 상황이라, 최소한의 안전장치로 Node.js 프로세스 메모리를 활용하기로 했습니다.
- 토큰 만료를 자동으로 돌리기 위해
setInterval 기반 청소 루틴을 추가했습니다.
구현 여정
- Step 1: 토큰 저장 구조 정의
tokenStorage라는 Map을 만들고, 복호화를 위해 필요한 key, iv, hmac_key, req_no를 묶어 저장합니다.
- Step 2: 만료 타이머 설정
1시간(3,600초)을 상수로 두고, 매분 실행되는 청소 루틴을 붙였습니다. 덕분에 메모리 누수 걱정을 덜었죠.
const TOKEN_EXPIRE_TIME = 60 * 60 * 1000;
setInterval(() => {
const now = Date.now();
for (const [token_version_id, data] of tokenStorage.entries()) {
if (now - data.created_at > TOKEN_EXPIRE_TIME) {
tokenStorage.delete(token_version_id);
console.log(`만료된 토큰 삭제: ${token_version_id}`);
}
}
}, 60000);
- Step 3: 발급 시 이중 저장
토큰을 발급할 때 세션과 tokenStorage에 동시에 넣습니다. 이중화 덕분에 콜백이 다른 노드로 튀더라도 최소한 한 곳에서 데이터를 건질 수 있습니다.
- Step 4: 콜백 처리에서 폴백 순서 설계
/checkplus_success에서는 먼저 메모리 캐시를 확인하고, 없을 때만 세션으로 내려갑니다. 로그를 남겨 두 경로 중 어떤 것을 탔는지 추적했습니다.
const tokenData = tokenStorage.get(token_version_id);
let key: string, iv: string, hmac_key: string, req_no: string;
if (tokenData) {
({ key, iv, hmac_key, req_no } = tokenData);
} else {
key = req.session.key || "";
iv = req.session.iv || "";
hmac_key = req.session.hmac || "";
req_no = req.session.req_no || "";
}
- Step 5: 디버깅 로그와 정리
무결성 검증에 실패하면 자세한 로그를 남기고, 성공하면 세션 값을 정리했습니다. 세션 누수로 인한 이상 동작을 막는 마지막 장치였습니다.
- 캐시 청소 주기가 너무 짧은 건 아닌지 확인하려고, 저는 GPT에게 시뮬레이션 로직을 물어봤고 10분 주기로도 충분하지만 1시간이 사용자 경험상 든든하다는 판단을 다시 확인했습니다.
결과와 회고
- 세션만 쓸 때 겪었던 "토큰을 찾을 수 없습니다" 오류가 현저히 줄어들었습니다.
- 요청마다 저장소 상황을 로그로 남겨두니, 운용 중 문제를 재현하기도 쉬워졌습니다.
- 다만 여러 프로세스나 서버로 스케일 아웃하면 Map만으로는 부족하니, 다음에는 Redis나 DynamoDB 같은 외부 스토리지를 연동할 예정입니다.
- 여러분은 콜백 인증 데이터를 어디에 저장하시나요? 로컬 세션을 계속 쓰는지, 아니면 별도 캐시를 두고 계신지 궁금합니다.
Reference