https://www.youtube.com/watch?v=Y-kImm8S-58
- 주제 : GitHub 데이터를 활용한 LLM 기반 이력서 자동 생성 서비스
- 대상 : 이력서 작성에 어려움을 느끼는 예비 개발자
- AI 기반 이력서 생성 및 수정: 사용자의 GitHub 데이터를 분석하여 실제 기업에 제출 가능한 이력서를 AI와 함께 손쉽게 제작 및 수정
- 커뮤니티 환경 제공: 사용자 간 이력서를 공유하고, 피드백을 주고받을 수 있는 협력의 장 제공
- GitHub 소셜 로그인을 통해 회원 가입 및 로그인을 할 수 있습니다.
- 마이페이지에서 회원 정보를 관리할 수 있습니다.
- 사용자의 GitHub 데이터를 분석해 AI가 이력서를 자동으로 생성해줍니다.
- 이력서는 3가지의 템플릿 중 1개를 골라 해당 형식으로 이력서를 작성할 수 있습니다.
- 수정하고 싶은 부분을 사용자가 드래그 하고 요청사항을 입력하면 AI가 이력서를 수정해줍니다.
- 직접 수정하고 싶은 부분은 사용자가 직접 수정할 수 있습니다.
- 완성된 이력서는 PDF 파일로 다운로드 받을 수 있습니다.
- 이력서 허브에서 사용자들은 이력서를 공유하고 피드백 받을 수 있습니다.
- 관심 있는 이력서는 좋아요를 누를 수 있고, 좋아요 누른 이력서만 조회할 수 있습니다.
- 직군, 학력, 조회수 순, 좋아요 순 등 다양한 필터로 이력서들을 조회해 볼 수 있습니다.
- 카카오페이 API를 통해 사용자는 정기 결제를 진행해 PRO 플랜으로 사용할 수 있습니다.
- FREE 플랜 사용자는 하루에 3회의 사용권만 사용할 수 있고 몇가지 제약사항이 존재합니다.
- 매일 자정에 백엔드에서는 배치작업을 통해 만료된 결제를 처리하고, FREE 플랜 사용자의 사용권을 초기화합니다.
문제 상황
- 정기 결제 및 사용권 초기화를 배치로 처리하고 있는데, 현재는 배치 로직도 엄청 복잡하지 않고, 데이터 양도 많지 않지만, 나중에 데이터가 많아지게될 경우 성능 저하가 우려됨
1. 순차처리 → 병렬처리
- 기존에 순차적으로 진행하던 작업을 병렬로 처리
- 성능 개선은 확실히 됐지만, 로그에 각각의 update 쿼리가 개별적으로 실행되고 있는 것을 확인
2. JPA → JDBC 전환
- 쿼리가 개별적으로 실행되면서 배치로 일괄처리 하는것의 이점을 전혀 살리지 못하고 있다고 판단
- JPA의 객체 매핑 기능이 매우 편리하긴 하나, 트랜잭션 관리, 변경 감지 등의 추가적인 기능이 필요하지 않고, 오히려 오버헤드가 발생해 성능이 저하되는 것을 확인
- 기존 JpaItemWriter을 JdbcBatchItemWriter로 변경해 설정해둔 Chunk Size를 그대로 반영해 Batch Update가 되도록 수정해 성능을 개선
결과
문제 상황
- Resume 모듈에서 좋아요를 누르거나, 댓글을 작성하는 경우, Notification 모듈로 알림 생성 요청을 보내는데, 동기로 요청을 보내다 보니 요청이 많아질수록 응답속도와 처리량이 저하됨
- 알림 시스템 특성상 다수의 사용자와 대규모 트래픽을 처리해야할 것으로 예상되는데, 기존 동기 방식으로는 감당할 수 없을 것 같아 테스트 진행
1. WebClient(blocking)
평균 응답속도 : 1304ms
99% 선 (최악의 경우) : 4155ms
TPS : 365.4/sec
2. WebClient(non-blocking)
평균 응답속도 : 987ms
99% 선 (최악의 경우) : 4162ms
TPS : 477.8/sec
3. Kafka
평균 응답속도 : 608ms
99% 선 (최악의 경우) : 2245ms
TPS : 761.3/sec
확장성 측면에서는 Kafka가 훨씬 더 좋을 것으로 예상되나, 트래픽이 엄청나게 몰리는 것이 아니고 비용적인 측면을 고려해야 한다면 WebClient 비동기 요청도 괜찮을 것 같음
문제 상황
- GitHub API를 사용해서 조회하는 것인데 응답시간이 상당히 오래 걸리던 상황
- 처음 회원 가입한 회원인 경우, 온보딩 페이지에서 다른 입력값을 입력하고 있을 때 미리 호출해 놓기 때문에 사용자가 응답을 기다리는 시간이 없었음
- 커뮤니티에서 "새 이력서 만들기"를 클릭하면 레포지토리 조회하는데 약 3~4초의 시간을 사용자가 기다려야 하는 상황이 발생
- 그러나, 시간이 오래 소요되는 부분이 GitHub API였기 때문에 해당 로직을 수정하거나 개선할 수 없음
문제 해결
- 레포지토리 목록은 변경 가능성이 매우 적고, 짧은 시간 사이에 이력서에 넣을 정도의 레포지토리가 생성되는 것이 불가능하다고 판단
- 해당 데이터를 캐싱해놓고 쓴다면 이점이 매우 클 것으로 예상
- 1시간정도 해당 데이터를 Redis에 캐싱하므로 첫 요청 말고는 사용자가 기다리는 시간이 거의 없도록 개선
- 캐싱 시도
- 필터 자체를 캐싱하자니 Cache Hit 확률이 너무 낮아 성능 개선이 안됨
- 각 이력서 정보를 캐싱해봤으나, 필터 검색에서 쿼리 생성하는 부분에서 성능이 매우 저하되는 것이라 개선 불가
- 필터 검색 로직 자체를 최적화 시도
- 코드를 다시 보니 regex를 쓰는 부분이 많았는데 해당 방식은 전체 데이터를 스캔하므로 성능이 저하되는 것 확인
- regex를 is로 변경하고, 배열 내 개별 요소에 대해 비교할 때 elemMatch를 사용하는 것으로 변경해 일치 항목을 찾으면 스캔을 중단하도록 개선
- 불필요한 쿼리 조건 제거 및 단순화
- 특정한 조건 없이 해당 이력서를 조회할 때마다 조회수가 올라가는 것은 조회수 정보의 신뢰성을 떨어뜨릴 수 있다고 판단
- Redis에 이력서 ID와 사용자 IP 주소를 합친 문자열을 1시간 동안 저장하도록 설정해 해결
- 처음 이력서를 조회한 경우 조회수가 증가하지만 그 이후 1시간 동안은 동일 이력서를 조회해도 조회수 집계되지 않음
- Redis의 TTL설정을 통해 편리하게 관리 가능
- MongoDB는 기본적으로 쿼리나 집계 파이프라인 수행시 메모리에서만 작업을 처리하는 것을 확인
- 이 과정에서 대량의 문서를 정렬하고 집계하다보니 메모리를 초과해 에러가 발생하던 것
- 특히 페이징 처리시 전체 페이지와 전체 문서 개수 집계 과정에서 메모리가 초과됨
- allowDiskUse(true) 설정을 통해 디스크를 추가로 사용해 연산을 완료할 수 있도록 허용해 해결
- Payment 모듈의 배치작업과 Member 모듈의 배치작업을 순차적으로 진행해야 하는 상황
- Payment 모듈의 작업 완료 이벤트를 Kafka를 통해 받으면 Member 모듈이 작업을 시작
- 그러나 Kafka의 트랜잭션과 Spring Batch의 트랜잭션이 충돌하면서 배치작업이 실패
- Kafka 이벤트 핸들러의 Transactional 설정을 Propagation.NOT_SUPPORTED 로 설정해 기존에 생성된 트랜잭션이 있든 없은 트랜잭션 없이 진행하도록 설정
- Batch Job을 실행시키는 로직을 별도의 메서드로 빼고, Propagation.REQUIRES_NEW 로 설정하여 새로운 트랜잭션에서 실행하도록 설정해 문제를 해결







