
Sprinb Batch를 말로만 들어보다가 사용하게 되며 사용 이유에 대해 정리한다.
Spring Batch 자체에 대한 내용은 공식 문서를 읽거나 이미 수많은 정리가 되어있기에 그 글들을 읽으면 될 것 같다.
현재 나의 신분은 대학교 3학년 2학기 재학생이다.
회사에 다니고 있지도 않고 대용량 데이터 처리를 할 일 또한 없다.
트래픽을 마주할 일도 없고 많은 양의 데이터를 처리할 일이 없어서 Spring Batch를 사이드 프로젝트에 투입하는 것은 오버 엔지니어링이라고 생각했다.
그러다가 Spring Batch를 사용해볼까? 라는 생각이 이번 기회에 들게 되었다.
지금까지 내가 처리해왔던 OAuth 동작 방식은 흔하디 흔한 방법이었다.
Jwt를 발급해서 브라우저의 쿠키에 심고 그를 통해 로그인을 처리하고 상태 유지를 처리해왔다.
하지만 현재 진행하고 있는 프로젝트에서는 이런 방식으로 처리하지 않을 뿐더러 사용자의 Google 액세스 토큰이 필요했다. 그리고 인증은 Firebase도 사용하기 때문에 Server단에서 jwt를 발급할 일이 없었다.
그래서 결국 우리가 관리해야할 토큰은 Google 액세스 토큰이었는데 이 토큰은 좀 예외였다.
기존에 내가 만들어서 로그인 여부를 판단하던 jwt 토큰의 경우 복호화를 해도 해당 사용자의 간단한 정보만을 통해서 인증을 처리했다.
하지만 구글 액세스 토큰을 누구나 볼 수 있는 환경에 기록하고 사용하는 것은 말이 안됐다.
그 사람의 액세스 토큰을 그대로 긁어서 해당 사용자의 구글 권한을 모두 지휘할 수 있기 때문이다.
고로 우리는 이 구글 액세스 토큰을 사용자들에게 공개해서는 안되는 상황에 직면했다.
어떠한 방법이 있을까?
3명이서 어떻게 처리를 할 지에 대한 고민을 했었는데 한 명이 이 아이디어를 제시했다.
사실 처음에는 굉장히 무식할 수 있고 부하가 많이 갈 수 있겠다는 생각이 들었다.
구글 액세스 토큰의 만료기한은 1시간이다.
보통 브라우저에서 일반적으로 처리하는 방법에 대해 생각해보자.
쿠키에 토큰을 담았을 시 만료기한(ttl)을 설정한다.
그리고 만료기한이 가까워졌을 때 클라이언트단에서 재갱신 요청을 보내면 Server에서 새 액세스 토큰을 발급해준다.
하지만 우리는 브라우저에 쿠키를 심을 수 없으므로 토큰을 DB에서 관리해야하며 acessToken과 해당 토큰이 fetch된 시간도 등록해야한다.
액세스 토큰과 발급 시간을 등록했다.
기존의 경우 클라이언트에서 요청을 날렸지만 이건 서버에서 알아서 알아내야한다.
그래서 우리는 Spring Scheduler를 사용하기로 했고 해당 스케줄러가 돌아갈 때 액세스 토큰의 fetch 시간을 확인한다.
물론 모든 유저에 대해서 확인해야한다. 모든 유저에 대해서 확인을 하고
지정한 시간내에 추출된 유저의 액세스 토큰을 모두 갈아줘야한다.
이렇기 때문에 사용자가 100명만 오더라도 상당히 좋지 않을 것이라고 생각했다.
또한 나는 많은 사용자의 서비스를 운영해본 경험이 아직 없기 때문에 이렇게 처리해도 되나? 의문이 들었다.
하지만 가장 믿음직한 리더가 이렇게 처리해도 된다는 말을 해줘서 그대로 시행했다.
사실 이 아이디어 빼고 다른 아이디어는 생각이 나지도 않았다.
일단 이 스케줄러와 재갱신 로직을 작성하면서 성능 개선에 대한 의구심이 계속 들었다.
그래서 ISSUE에 미리 남겨두기도 하였다.
성능 개선이 필요할 경우 비동기 or Spring Batch 등을 사용할 것이라고
그렇게 첫 성능 개선은 비동기 처리를 넣어보았다.
비동기를 적용해보려 했던 이유는 프로덕트가 점차 완성되어감에 따라 확장성을 더 고려하게 되었다. 그리고 이 스케줄러에서 종종 발생하는 오류는 치명적이었다.
이 스케줄러를 통해 액세스 토큰이 발급되지 못하면 우리의 핵심 서비스가 동작하지 못한다.
비동기를 넣었던 이유는 구글 측에서 새 액세스 토큰을 발급해오는 시간이 있을 것이다.
이를 기다리고 다음 account의 액세스 토큰을 갱신하고 이렇게 기다리는 시간을 줄이고 싶었다.
그래서 외부 API에서 처리하는 작업은 비동기적으로 투입하게 되었다.
소제를 좀 강하게 적었다.


지금은 시간이 지나서 좀 줄었지만 정말 많이 발생한 에러였다.
잘 되다가도 종종 안되는 경우가 생겼다.
이때 재시도 로직을 추가해야겠다는 생각이 들었다.
확실하게 우리가 이 이슈에 대해서 파악하지는 못했다.
google api 측과의 네트워크 오류라는데 이것은 내가 어떻게 처리할 수 있는 방법이 없었다.
그래서 재시도 로직을 추가하려했다.
현재 우리는 Google Cloud Run을 사용하고 있고 이는 Serverless이다.
Serverless의 특성 상 이벤트(API 호출)가 없을 경우 Server는 가동되지 않는다.
그래서 스케줄러가 돌아갈 때 Server가 가동되지 않다가 돌아가게 되어 네트워크를 잘 못잡나? 하는 의심이 계속 들었다.
재시도 로직을 넣자니 재시도뿐으로 될 게 아니라는 생각이 들었다.
또한 이때즈음 우리의 프로덕트는 사이드 프로젝트가 아니라 정말로 제대로 운영해볼 하나의 서비스를 목표로 달리게 되었기 때문에 확장성을 더 고려하게 되었다.
이제 남은 개선지는 Spring Batch라는 생각이 들었다.
확장성을 정말 고려하게 되었으며, 재시도까지 처리할 수 있다.
faultTolerant() 옵션을 통해 오류 발생 시 재시도(retry())가 가능하고, 오류가 발생해도 작업을 중단하지 않고 설정된 재시도 횟수 내에서 복구를 시도한다. retryLimit(3)과 같은 설정을 통해 3번까지 재시도 후에도 실패할 경우 오류로 기록되며, 이를 통해 재시도를 처리할 수 있다.chunk(10))만큼 데이터를 읽고 처리한다. 이는 대량의 데이터를 효율적으로 나누어 처리함으로써 성능을 최적화하고 메모리 사용량을 줄일 수 있다.최종적으로 Spring Batch를 적용하였고, 바로 어제까지 계속해서 나타나던 에러가 모두 해결되어 지금은 정상적으로 아주 잘 동작하고 있다.
하나 걸리는 점은 Spring Batch를 사용하며 DB에 6개의 테이블이 생겼고 스케줄러가 돌아갈 때 Batch 작업이 돌기 때문에 많은 쿼리가 날아간다는 점이 있다.
그래서 실제 프로덕션 환경에서는 Batch 서버를 따로 관리한다고 한다.
현재 개발서버에서는 4명만 개발하고 있기 때문에 이대로 두고 후에 사용자가 늘어감에 따라 Server를 따로 파야겠다는 생각을 하고 있다.