Chat GPT가 출시되기 전 Hugging Face, kaggle 등의 데이터셋을 공유하는 사이트를 통해, 괜찮은 모델들을 찾아보는 단계를 진행하고 있었습니다. 직접적으로 데이터를 학습시켜 새로운 모델을 개발하는 것은 어려웠지만, 이미 잘 만들어진 학습 데이터를 활용한다면 양질의 포트폴리오나 사이드 프로젝트를 할 수 있다는 생각이 종종 들었습니다.
하지만 Chat GPT가 출시되면서 많은 경악을 느꼈습니다. 기존에 잘 만들어진 학습 데이터를 이용하더라도 파이썬 등과 같이 새로운 학습 기간이 필요했습니다. 하지만 Chat GPT 홈페이지에서 API를 제공해주는 내용을 찾아, 이 API를 이용하면 괜찮은 프로젝트를 쉽게 구현할 수 있다는 생각을 하게 되었습니다.
그리고 비슷한 생각을 가진 프론트엔드 개발자분을 만나, 구체적인 프로젝트 아이디어에 대해 토론하며 이력서를 기반으로 면접을 도와주는 서비스에 대한 이야기가 나왔습니다. 이 서비스는 괜찮은 아이디어라고 생각하여 프로젝트를 시작하게 되었습니다.
프로젝트를 진행하면서 아래 내용들을 중점적으로 고려하였습니다.
- 현재 관심사에 관한 기술을 활용
- 지속 가능한 개발을 진행
- 실제 사용자의 피드백을 수집하고, 사용자의 요구사항과 의견을 고려하여 개발
- 사용자 중심의 개발을 진행하여 만족스러운 사용자 경험을 제공
단순 개발에서 끝나는 게 아니라, 지속해서 운영을 해보는 게 목적이었습니다.
왜 ask resume 인가
카카오톡에서 askUp이라는 챗봇을 보고 영감을 받았습니다. 이력서에 대해 물어봤더니 “ask resume”이 괜찮겠다는 생각이 들어서 이름을 결정하게 되었습니다.
개발자에 네이밍 센스 부족도 어느정도 있는거 같습니다.
비용
프로젝트에서 사용하는 open ai에 API를 사용할 때 비용이 발생하게 됩니다. gpt-3.5-turbo 모델은 1,000 token 당 $0.002로 비교적 저렴하다고 생각하였습니다.
설계
처음에는 이력서 PDF 파일을 받아서 텍스트로 변환한 다음, GPT에게 모든 내용을 전달하여 예상 질문 내용을 추출하는 방식으로 기획 및 설계를 진행했습니다.
문제
토큰 문제
초기 생각한 설계대로 개발을 진행하였고, 간단한 이력서를 생성하여 테스트 결과, 약 30초~1분이 소요되었고 결과 생성까지 순조롭게 이루어졌습니다. 그러나, 간단 이력서를 통해 테스트를 진행하다가 내용이 많은 이력서를 테스트해 보았고, 테스트 도중 에러가 발생하게 됩니다.
gpt-3.5-turbo 모델의 경우 4096 토큰, 약 3000개의 단어를 사용할 수 있습니다. 그러나 이력서가 길어지면서 이력서 내용 + 프롬프트를 함께 전달하니, 토큰 초과 문제가 발생하게 됩니다.
이후, 프로세스를 다시 변경하게 됩니다.
기존에 방식은 사용자가 PDF 파일 업로드를 통해, 바로 요청을 할 수 있었지만, 토큰 문제가 발생하여 내용 전달에 제한을 두는 방식으로 변경하게 되었습니다. 사용자는 PDF 파일을 올리게 되면 TEXT로 변환하게 되고, 이 내용이 어떤 내용인지, 자기소개, 경력, 기술 스택, 자격증 및 수상 정보 등으로 구분하여 form을 제출하게 되면 제출한 내용으로 면접 질문을 생성하게 됩니다.
그러나 여기서 또 문제가 생기게 됩니다.
속도 제한 (Rate Limit)
기존에 프로세스로 개발하게 됐을 때는 1번에 PDF 제출로, Open Ai API를 1회 호출하는데, 변경된 방식으로는 1회 form 제출에 자기소개, 경력, 기술 스택, 자격증 및 수상 정보가 있다면 총 Open Ai API를 4회 호출하게 됩니다.
기존에 30초~1분 소요되던 시간이 최대 4분까지 지연되게 되는 문제가 발생하였습니다.
그래서 이 문제를 멀티스레드를 이용해, 동시에 요청하여 문제를 해결을 해보기로 합니다.
ExecutorService 를 이용하여, 멀티스레드로 동시에 API를 요청하여 결과를 반환받고, 데이터를 합쳐 사용자에게 데이터를 response 해주도록 프로세스를 변경하니 다시 처리시간이 30초로 줄게 되었습니다.
그러나… GPT서버 부하와 관련하여 정책이 추가가 되면서 다시한번 문제가 발생하게 됩니다.
com.theokanning.openai.OpenAiHttpException: Rate limit reached for default-gpt-3.5-turbo in organization org — — on requests per min. Limit: 3 / min. Please try again in 20s. Contact us through our help center at help.openai.com if you continue to have issues. Please add a payment method to your account to increase your rate limit. Visit https://platform.openai.com/account/billing to add a payment method.
과도한 요청으로 인해 속도 제한으로 인해 요청이 제한되었습니다. 스레드 풀을 서서히 줄이면서 테스트를 진행했지만, 스레드가 1개가 아닌 경우에는 지속적인 에러가 발생했습니다. 이에 멀티스레드 코드를 구현하였으나, 스레드 풀을 1개로 조정하는 것으로 결정하였습니다.
따라서 현재 기능을 사용하되, MVP2에서는 MVP1에서 로딩화면을 지켜보는 부분을 해결하기 위해, 백그라운드에서 결과를 생성하여 메일로 전송하는 방법 등을 고민하고 있습니다.
이 글을 작성하고 난 뒤, 이 문제가 Rate Limit 이슈라는 걸 알게 되었습니다.
이슈
ask-resume 프로젝트를 진행하면서 되게 많은 이슈를 접하는데, 그중에서 기억에 남는 이슈들에 관해 이야기를 해보려고 합니다.
aws 서버 사망
프로젝트에서 CI/CD를 GitHub Actions와 Docker를 사용하여 진행하려고 했는데, AWS에 Docker를 설치하는 과정에서 ”Segmentation fault (core dumped)”라는 메시지와 함께 아무런 명령어가 반응하지 않았습니다. 서버를 재부팅을 시도했지만 서버가 다시 켜지지 않는 일이 발생했습니다. (머쓱)
이모지 문제
이력서 PDF를 TEXT로 변환하고 해당 내용을 리스폰할 때 문제가 발생하였습니다.
JSON 형태로 반환할 때 이모지가 들어있는 내용으로 인해 에러가 발생하는 것으로 확인하였고, 디버깅 결과, XSS 필터링으로 인해, 문제가 발생하는 것으로 파악했습니다.
XSS 필터에 예외를 추가하는 방법도 있지만, 이모지 데이터를 굳이 출력할 필요가 없어 이모지를 전체적으로 제거하도록 로직을 수정하였습니다.
Git Actions 억까
Git Actions를 사용하여 배포 스크립트를 작성하던 중, secret key를 위해, Actions secrets and variables에 값을 추가하고 있었습니다.
Failed to add secret. Secret names can only contain alphanumeric characters ([a-z], [A-Z], [0–9]) or underscores ().Spaces are not allowed. Must start with a letter ([a-z], [A-Z]) or underscores ().
key 값 띄어쓰기를 “-”를 사용해 표현하려고 했으나, 허용되지 않아 약 30분 동안 다시 수작업으로 변경해야 했습니다.
기술 스택
Text Block을 사용하기 위해 Java 17을 선택하게 되었습니다. 이번 프로젝트에서는 처음으로 17버전을 사용하면서 기존의 lombok에서 @setter, @getter 대신 record 타입을 사용하여 DTO 클래스를 구현했습니다. 처음에는 이게 편리하다고 느꼈지만, 객체 자체가 불변 타입으로 생성되어 특정 값 하나만 변경하는 것이 불편했습니다. Kotlin에서는 copy 기능을 통해 짧은 코드로 변경이 가능하지만, Java record에서는 이 기능이 지원되지 않습니다.
그 외에는 특별히 색다른 기술 스택을 사용하지는 않았습니다.
신경썻던 부분
프로젝트를 시작할 때, 배포와 운영을 목적으로 했기 때문에 비용적인 부분을 많이 고려해야 했습니다. 먼저 OpenAI API를 사용하면서도 비용이 발생하였고, 개발 서버와 운영 서버를 분리하여 각각 배포하고, 개발 데이터베이스와 운영 데이터베이스, 도메인만 구입해도 월 7만 원에서 8만 원 이상의 비용이 발생했습니다.
비용을 최대한 아끼면서도 개발하는 방법을 고민하게 되었습니다. 다국어 서비스를 위해 번역 API를 사용하려고 구글 번역 API를 알아보았지만, GPT에 프롬프트를 조절하여 번역 기능을 대체하고, 서버는 로드 밸런싱, 데이터베이스는 마스터와 슬레이브로 나누어 Replication 처리 등에는 트래픽이 발생하지 않는 시점에 대해 오버 엔지니어링이라 판단하여 사용하지 않기로 결정했습니다. 캐싱을 위해 Redis를 도입도 고민했지만, 당장에 애플리케이션이 1개에 서버에서만 운영될 예정이라 캐싱은 Spring Cache를 이용하여 처리했습니다.
좋았던 점
프로젝트를 진행하면서 중도 포기, 이탈이 발생하면서 완성도 못 하고 프로젝트가 터지는 경우를 종종 보곤 했습니다. 그러나 2023년 3월 25일부터 프로젝트를 기획하고 한 달이 조금 넘는 시간 동안 열심히 개발하였고 2023년 5월 5일, 초기 기획한 MVP 기능까지 개발을 종료하고 배포할 수 있었습니다.
그리고 개발을 진행하면서 못해본 경험도 이번 프로젝트를 통해 많은 경험을 해볼 수 있었습니다.
프론트 개발자와 협업
이전에는 프론트엔드와 백엔드 모두 혼자서 개발을 진행하여 문서를 통한 의사소통 경험이 없었습니다. 이번 프로젝트에서는 백엔드만 개발하여 Swagger를 사용하여 API 문서를 프론트엔드 개발자에게 제공하고 설명을 진행하면서 문서 작성 요령에 대한 경험을 쌓을 수 있었습니다.
a~z까지의 배포 경험
처음 서버 환경 설정부터 도메인 구매, DNS 설정, SSL, CICD 등 다양한 내용을 진행하면서 시행 착오를 겪을 수 있었습니다. 서버 환경 설정에서는 어떤 서버를 사용할지, 서버 사양은 어떤 걸 선택해야 할지, 어떤 운영 체제를 설치할지 등에 대해 고민해야 했고, 도메인 구매에서는 도메인 이름을 어떻게 정할지, 어디에서 구매하는 게 비용적으로 저렴한지 고민해야 했습니다. SSL을 적용하는 과정에서 가상환경에서 인증서를 적용하는 방법, 인증서를 발급받는 방법 등을 알아야 했습니다. 이 모든 과정에서 시행 착오가 있었지만, 그 과정에서 많은 것을 배웠고, 배포에 대한 부족한 지식을 채울 수 있는 기회가 되었습니다.
앞으로
앞으로 Ask Resume은 많은 변화가 예상됩니다. 현재 Java17 기반의 레이어드 아키텍처로 생성된 프로젝트는 모두 Kotlin과 헥사고날 아키텍처 기반으로 변경될 예정입니다. 또한, 결과를 생성하기 위해서 기다려야 하는 부분을 백그라운드에서 생성하여 결과를 메일로 전달하는 등의 변경으로 더 신뢰성 높은 데이터를 생성할 수 있도록 개발할 예정입니다.
마지막으로, 긴 내용 읽어주셔서 감사합니다!
프로젝트 구경하기
Ask Resume 사용해보기!
Backend Repository 구경하기!
https://github.com/132262B/ask-resume-backend
Forntend Repository 구경하기!
https://github.com/dev-redo/ask-resume-front
Ask Resume v1 개발 회고록
https://dev-redo.vercel.app/ask-resume-v1-개발-회고록
Turborepo 및 Pnpm을 사용하여 Monorepo 구축
https://dev-redo.vercel.app/turborepo-및-pnpm을-사용하여-monorepo-구축