✍️졸업 프로젝트 회고
드디어! 1년 동안 진행했던 졸업 프로젝트가 끝났다. 후련하기도 하면서 복잡 미묘한 생각이 든다. 왠지 나중에는 이때를 그리워할 것 같으면서도 지금 당장은 끝나서 개운한 느낌이 든다. 우리 팀은 프론트/백/AI 등 서로 역할이 명확하게 나누어져 있는 팀이었는데, 졸업 프로젝트에서 AI가 거의 필수적으로 넣어야 하는 요소가 된 지금, 다시 돌이켜보니 이렇게 각자 전문 분야를 잘하는 팀원들과 같이 일할 수 있었다는 것이 너무 좋은 경험이었다. 프론트를 맡은 cora는 FE로 여러 프로젝트 경험 및 인턴 경험이 있었고, AI를 맡은 chaen 언니는 2년 동안 인공지능 랩실을 다닌 경험이 있었다. 백을 맡은 나는 그냥 감자였던 것 같다ㅎㅎ
다른 아기자기한 일상 및 디테일한 회고는 벨로그에 기록해놨다. 벨로그 쓸 때는 정말 번거로웠는데 지나고 보니 이렇게 기록해놔서 너무 다행이라는 생각이 든다.
❓그래서, 뭐 만들었는데?
우리가 1년 동안 만든 서비스, NESS는 Gen AI 기반 스마트 스케줄링 웹 서비스이다. "일정"이라는 요소에는 시간, 위치, 사람, 일정 내용 등 굉장히 많은 요소가 존재하는데, 이를 AI를 통해서 쉽게 생성하고, 삭제하고, 수정하는 것을 목표로 한다. 우리 서비스는 "메모장과 같은 편리함"이 캐치프레이즈였다. 다른 인터페이스 없이 채팅에 "내일 강남에서 저녁 식사 약속 추가"라고 입력하는 것만으로도 캘린더에 일정이 등록되게 만들어서 기존의 UI/UX를 혁신적으로 대체하고자 했다.
채팅을 통한 일정 관리 뿐만 아니라, 생성형 AI의 가장 큰 능력, 즉 "생성"을 최대한 활용해서 맞춤형 일정 관리 비서를 만들어내는 것이 또 다른 주요 목표였다. NESS에서는 페르소나를 바꾸면서 서로 다른 일정 관리 스타일을 경험할 수 있고, 사용자의 일정을 요약하거나 리포트로 작성해서 메일로 받아볼 수 있었다. 채팅으로는 편리한 비서를, 리포트와 페르소나로는 맞춤형 비서를 만드는 것이 우리의 주요 목표였다.
👩💻기술적 발전
NESS의 전체 기술 아키텍쳐는 다음과 같다. 이 구조를 만들기 위해서 정말 많은 시행착오가 있었다. 아키텍쳐를 거의 5~6번 정도 다시 그렸던 것 같다.
이 중에서도 정말 중요한 핵심 개념은 RAG(Retrieval-Augmented Generation)였다. 사용자가 채팅에서 일정 분석을 원하거나 리포트 생성 등을 해야 할 때, 사용자 일정을 AI가 알고 있어야 데이터 기반 분석이 가능하다. 이를 가능하게 만들기 위해서 Vector DB에 일정을 저장하고, 사용자 요청과 유사한 일정 데이터를 Vector DB에서 꺼내서 AI에게 prompt와 함께 제공한다. 일종의 few-shot learning을 실제 사용자 데이터를 가지고 가능하게 한다고 보면 될 것 같다. 정말 어려운 부분이었는데, chaen 언니가 말도 안되는 능력을 발휘해서 나와 cora가 마구 내놓는 아이디어를 현실로 가능하게 만들어줬다. (믿기지 않음)
장기 프로젝트를 진행하면서 정말 상상치도 못한 에러도 많이 마주치고, 그 전에는 생각해보지 못한 문제도 해결했어야 했다. 지금도 생각나는 건 중간 발표 때 실시간 데모를 보여주는데 500에러가 떴을 때의 그 정적...ㅋㅋㅋㅋㅋㅋㅋ DB단에서 너무 예측하지 못한 에러가 터져서 어떻게 대응할 수도 없었다. 중간 발표를 시작하기 전부터 내가 너무 불안해해서 chaen 언니가 답지 않게 너무 긴장한다고, 괜찮냐고 했었는데 결국 이런 문제가 터졌다. 불안한 예감은 틀리지 않지...
그때는 정말 우울했는데 지금 지나고 보니 그냥 귀여운 에피소드가 되었다.
이런 에러 대처 능력 말고도 기술적으로 배운 것이 정말 많다. 일단 우리 서비스는 캘린더 기능과 채팅 기능이 필수적인 서비스인데, 이 둘 다 생각외로 정말 까다로운 점이 많았다. 캘린더의 경우에는 시간에 민감한 데이터를 백엔드가 신경써서 보내줘야 한다는 점, 월별 일정 > 일별 일정 > 개별 일정으로 이어지는 depth가 깊어서 프론트엔드적으로 상태 관리가 까다롭다는 점이 힘들었다. 채팅에서는 우리가 너무 많은 AI 기능을 채팅에 통합시키는 바람에 AI도 그 기능을 모두 만들어야 하고, FE도 기능에 맞춘 UI를 만들어야 한다는 점이 정말...까다로웠다.
백엔드의 관점에서 까다로웠던 부분은 다음과 같았다:
- 스케쥴의 timezone 맞추기: 이 서비스를 개발하면서 처음 알게 된 것인데, 백엔드는 1) DB 자체가 가지고 있는 타임존과, 2) 서버가 가지고 있는 타임존, 3) 백엔드 어플리케이션이 가지고 있는 타임존, 이 3가지 방식으로 타임존을 변경 가능하다. 그래서 SQL 쿼리를 잘못 짜면 KST 기준이 아니라 UTC 기준으로 스케쥴을 가져오게 되어서 에러가 나는 문제가 반복적으로 생겼다. 또 Vector DB와도 시간 sync가 맞지 않아서 계속 데이터베이스를 리셋했던 기억이 난다. 이 부분은 chaen 언니에게 무한한 감사를 드리고 싶다.
- 채팅에서 Server Push 기능: 이것도 처음 알게된 사실인데, 보통의 클라이언트-서버 구조가 API 요청-응답의 쌍으로 이루어져 있다면, 실시간 채팅과 같은 기능에서는 클라이언트가 채팅을 보낼 때는 API 요청을 보내는 게 맞지만 채팅 답변을 받을 때는 서버에서 push해서 보내줘야 한다. 웹소켓(WebSocket) 같은 기술을 써서 이 기능을 구현해야 하는데, 시간과 성능 관계상 이 내용을 구현하지는 못하고 우리는 우회적으로 "클라이언트는 API 요청하며 사용자 채팅을 전송-서버는 이를 받아 DB에 저장하고 AI의 응답까지 포함한 모든 채팅 내용을 다시 반환"하는 방식으로 구현했다. 근데 cora의 말에 따르면 웹소켓을 쓰지 않고 그냥 이렇게 채팅 기능을 구현하는 회사들도 있다고 한다. 이 부분은 나중에 유튜브 실시간 댓글과 같은 서비스에서 어떻게 구현하는지 좀 더 기술적으로 배워보고 싶다.
그 외에도 넣은 기능들은 다음과 같다:
- 스프링 시큐리티로 OAuth Google 로그인하기: redirect-uri 개념이 익숙하지 못해 프론트인 cora를 너무 많이 괴롭혔다. 관대하게 나의 수많은 변경 사항을 모두 반영해준 그녀에게 무한한 감사를...그리고 외부 사용자들이 자유롭게 사용 가능하게 만들기 위해서 dev 모드가 아니라 production까지 배포를 했어야 했는데, 구글은 프로덕션 배포를 하려면 개인정보 방침도 만들었어야 했다. 정말 너무 괴로웠던 기억이 난다.
- S3로 presigned url 구축하기: 이것도 내가 미숙해서 cora를 혼란스럽게 만들었던 주요한 원인이었다. 그리고 백엔드가 API 명세서를 자세하게 잘 써야 프론트가 혼란을 겪지 않을 수 있다는 것을 다시 한 번 깨달았다.
- access token 및 refresh token 구현: 엑세스 토큰은 기존에도 사용해봤었지만, 리프레시 토큰을 구현한 것은 이번이 처음이었다. 개발하다가 자꾸 토큰이 만료되는 일이 생겨서 쿠키를 지속적으로 지워줬어야 했는데, 실제 유저가 이 서비스에 들어온다고 생각했을 때 쿠키를 지워야 에러가 해결된다는 점을 모를 것 같아서 꼭 구현했어야 했다. 도커로 redis 띄우고, springboot와 redis 도커를 docker network로 연결하는 일련의 과정이 정말 처음이었고 매우 흥미로웠다.
- FeignClient 사용해서 외부 API 연결: AI 백엔드에서 제공하는 API를 내가 연결해줘야 했기 때문에 FeignClient를 매우 적극적으로 사용했다. 이렇게 서비스에서 FeignClient를 많이 사용한 건 이번이 처음이었던 것 같다.
- ACM, Route53, ALB 사용해서 HTTPS 제공 및 서브 도메인 라우팅: HTTPS는 기존에도 해봤었지만, 프론트/백/AI 모두를 위한 서브 도메인 라우팅을 하는 부분은 이번이 처음이었다. 이것도 정말 시행착오를 많이 거쳤다.
- 이메일 전송 스케쥴링 및 비동기 처리: 이 서비스에서 가장 재미있는 기능 중 하나이다. ChatGPT + DALL-E를 통해서 생성형 AI가 하루를 정리하는 리포트를 이메일로 보내주는데, 일단 이메일 전송 기능을 처음 구현해봐서 신기했다. 그리고 이걸 스케쥴링 처리해서 일정한 시간마다 보낼 수 있게 만드는 것도 재미있었고, 이메일 하나하나가 생성되는데 시간이 너무 오래 걸려서 이걸 비동기 처리해서 한번에 보낼 수 있게 만드는 과정도 흥미로웠다.
- 400/500에러 Discord 알림으로 보내기: 매번 에러가 날 때마다 서버에 들어가서 도커의 로그를 확인하는 과정이 정말 너무 번거로워서 구축했던 기능이다. 이것도 정말 너무 재미있었는데, 일단 문제 인식 > 문제 해결 과정이 매우 뚜렷했고, 나와 프론트 모두 너무 편리하다고 느꼈다. Exception이 발생하면 핸들러가 잡아서 디스코드 메세지로 에러 발생 시각, stack trace, 에러 발생 서버 등을 보내주기 때문에 클라이언트 문제/서버 문제를 한눈에 구별 가능하다.
- Future로 API 호출 시간 줄이기: 우리 서비스는 생성형 AI를 정말 많이 쓰고, 그 중에서도 메인 페이지는 여러 종류의 생성형 AI 기능을 쓰기 때문에 로딩되는 데 시간이 정말 많이 걸렸다. 이때 future를 써서 비동기 작업으로 처리하게 만들어 메인 페이지의 로딩 시간을 절반 이하로 단축할 수 있었다. 이것도 매우 뿌듯한 경험 중 하나인 것 같다.
전반적으로 학부생 수준의 프로젝트에서 할 수 있는 흥미로운 기능을 다 때려넣은 프로젝트였다. 팀원들 전부 거의 마지막 학기라서 할 수 있는거 다 해보자! 하고 미친 듯이 개발했었다. 결론적으로 우리는 총 42개의 API와 각자 1000개 정도의 commit을 얻게 되었다.
😊기술 외적으로 배운 점
1년 동안 이 프로젝트를 다른 사람들에게 보여주면서 정말 많은 피드백을 받았다. 그 중에서는 우리가 자체 LLM을 안 만들고(!) OpenAI에 의존도가 너무 높아서 별로라는 분들도 있었고, 캘린더에 Gen AI를 결합시키는 것의 가능성을 매우 좋게 봐주는 분들도 있었다. 사람마다 의견이 다 너무 다양해서 프로젝트 초반에는 피드백 하나하나, 의견 하나하나 꼼꼼하게 점검하려고 애썼는데, 프로젝트 후반에 가서는 힘을 좀 빼고 개선할 부분은 개선하고 그렇지 않은 부분은 살짝 넘어가는 능력(?)을 얻은 것 같다.
또한 팀장으로서 한 팀을 1년 동안 이끌면서 어느 정도 PM의 역할을 대리 체험하게 되었다. Cora는 나보고 "영원히 나의 PM을 해줘"라고 했지만ㅋㅋㅋㅋㅋ 솔직히 미숙한 부분이 너무 많이 있었던 것 같다. 전체적으로 강약 조절을 조금 잘 못한 것 같다. 힘을 빼야 할 곳과 힘을 주어야 할 부분을 구별하고, 속도를 내는 구간과 약간 쉬어가는 구간을 번갈아서 주고, 팀원들을 동기부여를 할 때와 조금 릴렉스 할 때를 잘 구별하지 못한 것 같다. "항상 최선의 노력!!!!" 방식으로만 팀원들을 갈군 것 같아서 좀 미안하다. 내 부족한 부분은 슬쩍 봐주고 좋은 부분은 칭찬해준 팀원들에게 무한한 감사를...
👀프로젝트 성과
NESS 프로젝트를 정말 많은 곳에 내봤다. 네이버 D2SF 스타트업, SW 창업 경진대회, 디지털 경진대회 등이 있었는데, 돌이켜보면 괜찮은 성과를 받은 것 같다. D2SF는 떨어졌지만 덕분에 IR 자료가 무엇이고 뭐가 필요한지를 아는 능력을 (그리고 빠르게 figma로 PPT 찍어내는 능력을) 얻게 되었다. SW 창업 경진대회는 장려상을 탔고, 디지털 경진대회는 아쉽게 본선 진출은 못했지만 학교에서 산학장학금으로 인당 100만원씩 받을 수 있었다. 나에게도 팀원들에게도 좋은 커리어가 된 것 같아서 다행이다. 그리고 cora랑 chaen 언니는 모두 NESS 프로젝트 경험으로 인턴에 합격했다! 1년 동안의 노력이 좋은 방식으로 모두에게 돌아간 것 같아서 기분이 너무 좋다.
👋바이바이
2023년 7월부터 2024년 7월까지, 정말 딱 1년을 진행했던 프로젝트를 끝내고 AWS 계정에서 하나하나 리소스를 삭제했다. 서버를 중지하는 데 왜 이렇게 묘한 기분이 들까? 거의 자식을 떠나보내는 것 같다. 이제 그만 안녕 나의 AWS 청구서...
'🎉연말결산 및 잡담' 카테고리의 다른 글
Cathy의 2023 Wrapped 연말결산✨🎉 (0) | 2024.01.01 |
---|