urworkhelper 서비스 개발 회고
OpenAI GPT-3.5 기반 취업 코칭 서비스 urworkhelper의 기획·개발·운영 회고.

프롤로그
8월은 다른 걸 쳐다볼 여력도 두지 않을 정도로 ‘urworkhelper’ 서비스 개발에만 집중한 한 달이었다. 이 서비스는 AI 기반의 취업 코칭 서비스로, 자기소개서 첨삭, 수행한 프로젝트에 기반한 면접 예상 질문, 성향에 기반한 직업 추천 등의 기능을 제공하고, OpenAI의 gpt-3.5-turbo 모델을 이용해서 개발했다.
C 회사, W 회사, S 회사 등에서 제공 중인, 취업을 도와주는 서비스에서 힌트를 얻었다. 이전에 OpenAI의 text-davinci-003 모델을 이용해 AI 컬러 추천 서비스를 개발한 경험을 접목해서 서비스 구조를 빠르게 머릿속에 그릴 수 있었다. 그리고 당연하지만, 이 서비스의 개발이 단순 유희의 목적만은 아니었기 때문에 도전적인 과제들이 여럿 있었다.
도전과제들
첫 번째는 협업에 대한 도전이다. 이번에는 개인 프로젝트보다는 백엔드 개발자를 준비하는 친구와 협업을 통해 프로젝트를 진행해 보기로 했다. 배움의 척도를 선상에 두고 수치로 비교할 수 없고, 확신도 없었다. 그래도 도전 없이는 현재와 비슷한 수준에 머무르게 될 것이라 여겼고, 협업에 대한 경험이 필요했다. 친구가 벅참을 느낄 때면 내가 조금 더 시간을 투자해 도우며, 전체적인 서비스 개발을 리드할 자신도 있었다. 또한, 제대로 된 개발 프로젝트를 경험해 보지 못한 친구에게도 많은 배움을 줄 수 있으리라 생각했다.
두 번째는 서버 애플리케이션 개발에 대한 도전이다. 최근에 Next.js에서 API를 개발한 경험이 있고, 시간이 조금 흘렀지만 Express.js와 MongoDB를 이용해 서버를 개발한 경험도 있다. 하지만 시간이 흘렀고, 현실에서는 SQL RDBMS를 이용한 서버 애플리케이션 개발이 흔하지만, 자신이 없었다. 심지어 같이 프로젝트를 진행하기로 한 친구도 Python 기반의 지식을 쌓았기 때문에, Javascript, Node.js, Express.js에 대해 비교적 배움이 있는 내가 빠르고 간결하게 알려주고 이끌 수 있어야 했다. 이번에도 확신은 없었지만, 그렇다고 해낼 수 없는 수준처럼 보이진 않았다.
세 번째는 프런트엔드 애플리케이션과 백엔드 애플리케이션의 분리 개발에 대한 도전이다. 이전의 컬러 추천 서비스, 중고 거래 서비스 모두 Next.js API, Vercel을 이용해 하나의 프로젝트에서 개발했다. 하지만, 당장 분업화와 전문화만 생각해 봐도 분리된 상태로 개발하는 경우가 흔하고, 이에 대한 경험이 필요했다. 분리 개발 경험과 확신은 없었지만, 필요한 지식을 빠르게 찾고 흡수할 자신은 있었다.
이외에도 크고 작은 기술적 도전이 여럿 있었다. Next.js의 Server-side Rendering 환경에서의 Redux를 이용한 상태 관리와 Styled-components를 이용한 다크 모드 테마 시스템 구축, 일관적인 UI 시스템 구축을 위한 Storybook 도입 등이 그것이다— 사실 Server-side Rendering을 포함하는, Universal Rendering을 지향한 노력의 결말은 조금 아쉽게 되었다. 또한, 개인이 아닌 협업으로써 프로젝트를 진행하기 위해서 필요한 것들, 협업하기 위한 브랜치 전략, Git과 Github를 이용한 버전 관리, Jira를 이용한 프로젝트 이슈 관리와 협업, 데이터베이스 스키마 정의와 MySQL, AWS를 이용한 클라우드 환경에서의 배포 등의 도전 사항도 있었다.

몇 가지 에피소드
예정된 도전 과제들. 예상보다 쉽게 이룬 것도 있었다. 이를테면 친구를 자바스크립트 생태계로 인도하는 것은 어렵지 않았다. 알려주는 과정에서 나 또한 지식을 다시금 정립할 수 있었고, 친구 또한 빠르게 따라와 주었다.
그런가 하면 예상하지 못했던 문제도 있었다. 원래는 KoGPT 모델을 이용해 서비스를 개발하려 했으나, Node 환경의 예제는 찾을 수 없어서 어느 정도의 시간을 투자해야 했다. 프롬프트 테스트 결과도 계획했던 바를 벗어나, OpenAI의 gpt-3.5-turbo 모델로 급히 선회해야 했고, 이전 프로젝트에서 사용한 경험이 있었지만, API 인터페이스가 변경되어 새로 익혀야 하는 부분이 생기기도 했다. 그래도 KoGPT를 사용한 경험과 Node 환경에서의 구현 예제는 필요할 누군가를 위해 기록으로 남겨두었다.

그런데 OpenAI의 API는 사용량에 따라 요금이 부과된다. 꽤 저렴한 편이지만, 어쨌거나 이 요금이 부과된다는 점은 서비스가 커지면 내가 감당해야 하는 부분으로 돌아오리라 생각했다.
그래서 이에 대한 솔루션으로 포인트와 출석 체크 시스템을 떠올렸다. 유저 모델을 만들고, 로그인하며 보내는 POST 요청에서 현재의 일시와 가장 최근의 접속일을 비교한다. 비교 결과, 그날의 최초 로그인 시도라면 일정량의 서비스 이용 가능 포인트를 지급하고, 서비스 이용 시 일정량의 포인트를 차감하며, 유저가 자신의 포인트 지급 및 차감 기록을 확인할 수 있도록 로그 모델을 만들어 보여주는 것이다. 비즈니스적인 문제를 기술적으로 풀어낸 것 같아 꽤 보람찼던 부분이다.

토스트 UI를 통해 애플리케이션의 상태를 알려주는 것도 새로운 도전이었다. 이를테면 개발과 테스트를 진행하며 간혹 OpenAI API 요청에 대한 응답이 느리거나 오류가 날 때도 있었다.
오류가 발생하거나 잘못된 요청의 경우, 서버에서 에러 처리를 실행한 응답을 돌려주며 토스트 UI를 이용해 사용자에게 전달했다. 또한, OpenAI API의 응답이 지연될 경우를 대비해 서비스가 정상적으로 동작하고 있다는 것을 알 수 있도록 프런트엔드 애플리케이션에서 로딩 애니메이션과 처리 중이라는 메시지를 토스트 UI를 통해 전달했다.

물론, 희망찬 도전만 있던 건 아니었다. 정말 답을 찾지 못해 며칠간 식음을 전폐할 정도로 몰두해서 이루어 낸 목표도 있었다. AWS에 서버 애플리케이션을 배포하는 과정이 그것이었다. 배포 준비 과정까지 포함한다면 일주일을 넘게 투자했고, 서버 인스턴스 구성부터 아파치 웹 서버를 이용한 리버스 프록시 설정, HTTPS 설정 등을 위시한 인프라스트럭처 구성에서는 정말 벽을 마주하는 기분이었다. 따로 분리한 데이터베이스 인스턴스의 연결이 블록되기도 하고, Debian 리눅스의 서버 인스턴스에서 MySQL 클라이언트의 설치 과정 중 충돌이 일어나기도 했다. 덕분에 MariaDB를 MySQL 대신 사용할 수 있다는 사실도 처음 알았다.
그렇지만 여기까지 나를 믿고 따라와 준 친구를 위해서라도, 알려주는 이 하나 없더라도 나는 빠르게 배우고, 검증하고, 시도해야 했다. 배포한 서비스가 제대로 돌아간 걸 확인한 새벽은 얼마나 벅차올랐는지 모른다. 그렇게 겪었던 날 것의 고생들은 몇 배로 값진 경험과 지식으로 남았고, 이제는 전부 ‘그땐 그랬지.’라는 해프닝일 뿐이다.
결

프로젝트를 리드하는 것은 많은 경험과 책임감이 필요했다. 개발뿐만 아니라 주요 기획과 디자인도 수행했기 때문에 더 버겁다고 느꼈을지도 모른다. 하지만 프로젝트의 진척도 조정, 이슈 관리, 필요한 기술의 선정, 필요할 때 팀원을 도울 수 있는 경험과 지식까지, 개발 외에도 신경 써야 할 일이 많았다. 개발만 하는 것이 아니라, 개발 실력은 기본이고 거시적인 관점에서도 프로젝트를 성공으로 이끌 수 있어야 한다고 생각하게 되었다.
한 달을 통째로 투자한 프로젝트 치곤, 개인적으로 서비스 규모도 그리 크지 않고, 데이터베이스 스키마도 복잡하지 않다고 생각한다. 그렇지만 그에 비해 얻은 경험과 지식은 이루 말할 수 없을 정도로 값지고, 이번 프로젝트가 아니었다면 배우지 못했을 것들이었다. 그리고 항상 옳은 선택과 결정만을 하며 프로젝트를 이끌어 온 것이 아니기 때문에, 잘 따라와 준 친구에게 고맙기도 하다.
에필로그

내가 나름 자랐다고 느꼈던 시기, 7월에 면접에서 떨어지며 큰 좌절감을 느꼈고, 7월 말에 떠난 일탈 또한, 나 혼자가 아니기 때문에 떠난, 그런 기분인 채로 떠나게 되었었다. 짧은 일탈에서 돌아온 이후에도 갈피를 쉽게 잡지 못했고, 어느 정도의 과감한 결단과 함께 시작한 프로젝트, 아마 도전이 없었다면 어땠을까?
앞서 계획했던 도전 목표들은 사실, 보상이 걸렸거나 보상을 바랐던 목표들은 아니다. 되려 지적 유희나 볼 수 없던 시야에 대한 갈증에 가깝다고 할 수 있다. 과연 도전이 없었다면 어땠을까?
Amazon Web Services의 Lightsail VPS 내의 Debian Linux 운영체제에서, Apache 서버의 가상 호스트와 리버스 프록시 설정을 통한 Node 서버를 연결했던 경험,
AWS Certificate Manager과 Let’s Encrypt를 통해 SSL 인증서를 발급받고 연결해본 경험,
AWS S3, CloudFront로 정적 배포를 이루고, Route 53에서 도메인을 구입해서 연결해본 경험,
Node — Express.js 서버의 개발 환경을 구축하고, MySQL 서버를 연동하며, Sequelize ORM을 사용해본 경험,
Next.js 프레임워크에서 Redux, Styled-components를 사용해본 경험,
Jest로 하여금 이전보다 더욱 다양한 단위 테스트를 작성하고, Cypress를 이용해서 작성한 E2E 테스트 경험,
다양한 Git 브랜치 전략으로, stash, rebase 같은 평소에 써보지 않았던 Git 명령어를 사용해서 코드를 통합하고, Jira를 통해 이슈를 공유하며, ESLint, Prettier 같은 린트 도구로 코딩 컨벤션을 통일해본 경험,
‘OpenAI API의 과금 구조에 따른 제한적 서비스 공급’이라는 비즈니스적인 문제를 풀기 위해 포인트와 출석 체크 시스템을 도입하고, 이를 사용자에게 보여줄 수 있는 로그 시스템을 정의해 구현한 경험.
MVC 아키텍처의 구조와 프로젝트에서 구상한 애플리케이션의 모습을 겹쳐보며, 각각의 모델, 뷰, 컨트롤러에 적절하게 역할을 배분하고, 거시적으로 애플리케이션을 바라본 경험.
이런 경험들을 할 수 있었을까? 굳은 시야는 녹았고, 새로운 지식과 값진 경험을 쌓았고, 다시 앞으로 나아갈 동기를 얻었다. 누가 주입한 것도, 해야만하는 숙제도, 일도 아니었다. 보상이 없다던 퀘스트의 끝에, 새로운 시야라는 클리어 보상이 있을 줄 몰랐다.
원문: Medium