Skip to content

프로젝트 핵심 경험‐장승훈(BE)

Hoons97 edited this page Dec 1, 2024 · 6 revisions

💡 본 문서는 노션에 최적화되어 보실 수 있습니다. 링크

안녕하세요. Q-Lab 백엔드 개발자, 장승훈입니다.

개인적으로 정말 짧게 느껴졌던, 그래서 더욱 아쉬움이 남는 프로젝트였습니다.

5 주라는 짧은 기간 동안 프로젝트를 진행하면서 처음에 목표로 하였던 가치와, 경험을 통해 얻은 가치를 정리해보고자 합니다.

🎯 핵심 목표

다양한 클라우드 서비스를 활용하고, 인프라를 설계해보자.

부스트 캠프를 시작하기 전, AWS 에 대해서 간단히 공부를 하였습니다. AWS의 다양하고 방대한 서비스들을 맛보면서 인프라에 매력을 느끼게 되었습니다. 인프라라는 것을 막연하게 어렵고 복잡한 것이라 생각하고 있었는데, 그 어려운 것들이 버튼 한 두 번에 세팅되는 것이 특히 매력적으로 다가왔습니다.

클라우드를 공부하면서 학부 과정 동안 학습했던 것들을 전체적으로 활용하는 느낌을 받았고, ‘이걸 어떻게 만들었을까?’ 하는 궁금증으로 이어졌습니다. 이러한 궁금증이 클라우드라는 주제를 선택하는데 큰 영향을 미치게 되었어요.

팀원들과 다양하게 인터랙션하며 협업의 경험을 쌓자.

저는 프로젝트 경험이 그렇게 많지 않습니다. 프로젝트 경험이라곤 학교에서 종합설계 시간에 했던 프로젝트가 전부입니다. 당시에는 스스로 아는 것도 많지 않았고, 팀원들도 의욕적이지 않아 소통도 많지 않았고 각자 맡은 일만 했어요.

부스트 캠프에서 의욕 넘치는 분들과 함께 베이직, 챌린지 과정을 거치며 협업이라는 것이 즐겁다고 느끼게 되었습니다. 정말 하고자 하는 사람들과 함께 프로젝트를 할 수 있는 것이 다시는 없을 소중한 경험이라는 것을 알게 되었어요. 그렇다면 이 기회를 최대한 활용해서 커뮤니케이션 경험을 얻고자 합니다.

🌟 프로젝트를 통해 얻은 것

협업 관점에서

깃허브를 활용해서 팀원들과 하나의 프로젝트를 관리하는 과정 그 자체가 새로운 경험이었어요. 특히나 기획내용을 바탕으로 스토리를 나누고, 각 스토리 별로 태스크를 나누어 서로 분담하고, 태스크를 하나씩 처리해가며 하나의 프로젝트가 완성되어가는 과정이 인상깊었습니다.

각각 일을 맡아서 진행하고 하나의 프로젝트에 적용하는 만큼 사소한 것 하나하나 약속하는게 얼마나 중요한 일인지 깨닫게 되었습니다. 팀원이 정리해서 문서화한 내용을 읽어보고 공유하는게 정말 중요한 것 같아요. 특히나 팀원이 올린 PR 코드를 읽고 리뷰하는 경험이 익숙하지 않아 애를 먹었던 것 같아요. 여태 코딩할 때에는 를 기준으로 생각했었지만, 팀원들과 함께하며 우리 를 기준으로 생각해 볼 수 있었던 게 낯설면서도 소중한 경험이었습니다.

소통 관점에서

프로젝트 시작부터 소통에 신경쓰고, 중요시하자고 스스로 다짐했었습니다. 그러나 프로젝트가 진행되면서 점점 힘들어짐에 따라 여유와 이해가 부족해지는 것이 느껴졌어요. 그럴수록 우리 보다는 중심적으로 생각하게 되었습니다. 스스로 원했던 자신의 모습은 ‘팀원들이 믿고 의지할 수 있는 사람’ 이었는데, 맡은 일 해결하기에도 급급했던 모습만 남은 것 같아요.

하지만 정말 운 좋게도 이해해주고 배려해주는 팀원들을 만났고, 커뮤니케이션에 있어서 팀원에게 많이 기댈 수 있었어요. 이번 프로젝트 경험을 발판 삼아 기대는 사람이 아니라, 기댈 수 있는 사람이 되고자 합니다.

성장 관점에서

인프라를 설계하면서 처음에 기대했던 것은 정말 화려하고 복잡한 구조였습니다. 동적으로 관리되고, 다양한 클라우드 서비스와 연동하는 그런 아키텍처를 상상했습니다.

막상 구현에 들어가고 나서 깨달은 것이 2가지 있습니다. 하나는 ‘실현 가능한 목표’ 를 세우는 것과, 하나는 ‘프로젝트 규모에 적합한 아키텍처’ 를 설계하는 것이 중요하다 입니다. 당장 6주, 실질적으로 4주라는 짧은 개발기간에 제 상상을 모두 적용하기란 불가능 했어요. 당장 CloudDB 를 활용하다가 비용문제로 하나의 인스턴스에 3개의 DB를 올리도록 바꾸었습니다. 이 과정에서 기초가 얼마나 중요한 지와, 필요없이 과도한 아키텍처는 오버 엔지니어링에 불과하다는 사실을 깨달았어요. 부족한 기초에 대해 자각하였고 비어있던 부분들을 채워넣을 수 있었던 프로젝트였습니다.

📅 주차 별 핵심 경험

💡프로젝트 진행 전반에 거쳐, 인프라부터 프론트까지 다양한 경험을 하였습니다.

주차 경험
1 주차 프로젝트 기획 , 그라운드 룰, 깃허브 전략, 문서화 관리
2 주차 — 예비군 —
3 주차 인프라 설계/구현, CICD 구축, 배포/테스트 환경 구축
4 주차 백엔드 기능 구현
5 주차 프론트 기능 구현

1 주차 - 프로젝트 계획

💡프로젝트를 진행하며 서로 지켜야 할 약속에 대해 이야기를 나누었습니다.

프로젝트 기획 - 개요

로컬 환경에 DB 를 세팅하고, 100만건 이상의 랜덤 데이터를 삽입하는데 있어서 번거로움을 느꼇습니다. 여기서 아이디어를 얻어 사용자마다 Mysql DB 환경을 제공받고 쿼리를 실행할 수 있는 클라우드 서비스를 떠올리게 되었습니다.

깃 허브 전략 - 깃허브 컨벤션

깃과 깃허브를 활용함에 있어서 정할 내용들을 이야기하였습니다. 브랜치 관리 방법, 커밋 컨벤션, PR 형식과 단위 등을 공유하였습니다.

문서 관리 - Q-Lab

모든 문서는 노션 페이지에서 관리하고, 확인할 수 있도록 하였습니다.

2 주차 - 예비군

💡 예비군 이슈로 활동에 많이 참여하지 못했습니다 ㅠ.ㅠ 주로 인프라 관련된 고민을 하였고, 3 주차 내용에 함께 정리해 놓았습니다.

download

3 주차 - 인프라

💡 프로젝트 기획을 바탕으로, 인프라개발환경에 대한 고민을 하였습니다.

386078111-dc1e1a29-b9fa-47ad-8c58-df591ed922b7

인프라 설계

서버 인스턴스는 2개를 활용하였고, 목적에 따라 그 인스턴스의 성격을 분류하였습니다. 성격에 따라 서버를 분리함으로써 추후에 AutoScaling, LoadBalancer 등을 적용할 때 큰 리팩토링 없이 적용시킬 수 있도록 하였습니다.

  • 서비스 인스턴스
    외부 네트워크와 상호작용 해야 하는 성격의 서버들을 올리고 관리하였습니다. 배포를 담당해야 하는 젠킨스는 서버에 직접 설치하고 활용하였고, 웹 서버와 백엔드 서버의 경우는 도커를 활용하여 배포해주었습니다.

  • 스토리지 인스턴스
    데이터를 저장해야 하는 성격의 서버들을 올려주었습니다. 세션을 관리할 Redis , 서비스에서 활용할 Mysql , 쿼리 실행에 활용할 Mysql 3개의 서버를 올리고 관리하였습니다. Privite Subnet 에 위치시켜 외부 네트워크에서의 직접적인 접근을 제한하고, VPC 내부에서만 인터랙션을 허용 하였습니다.

구현 과정
배포환경 DB 세팅
nginx 설정 및 프론트 배포
백엔드 mySQL, redis 세팅

CICD 구축

젠킨스를 활용, 깃허브와 연동해서 CICD 를 구축해 주었습니다. 젠킨스가 많이 활용된다고 알고 있어 이번 기회에 경험해보고자 하는 이유가 컸습니다. 파이프 라인은 크게 3가지 경우로 나뉘어 동작합니다.

  • Main 브랜치에 대한 Push 이벤트
    Main 브랜치를 기준으로 CD를 구축하였습니다. Main 브랜치에 대해 Push 이벤트가 발생하면 도커 컴포즈를 빌드하고 서버를 배포합니다.

  • Dev 브랜치에 대한 PR 이벤트
    모든 작업 내용을 Dev 브랜치로 머지됩니다. 따라서 Dev 브랜치에 대해 PR 요청이 생성되면 타깃 브랜치와 Dev 브랜치를 머지하고, CI 테스트를 수행합니다. CI 테스트는 4가지를 수행하였습니다. 각 테스트는 도커환경에서 실행해주었습니다.
    FE - npm test , vite build
    BE - npm test , npm buld

  • 그 이외 브랜치에 대한 이벤트
    이외의 브랜치에 대해서는 스킵하도록 하였고, PR 인 경우에는 간단한 코멘트를 남겨주었습니다.

연동 과정
Jenkins 세팅
Jenkis 활용 CI 구축

배포 환경과 테스트 환경에 대한 고민

  • 배포 환경
    배포 환경에서 비밀번호 등의 민감한 정보를 관리할 방법이 주요 고민거리였습니다. Jenkins 를 활용해서 CD 를 수행해주고 있었기에 젠킨스가 수행하는 빌드환경에서 환경변수를 어떻게 넣어줄 수 있을까를 고민하였습니다. 이를 해결하기 위해 Jenkins Credential 기능을 활용하였고, 빌드 파이프라인 환경에서 환경변수를 주입해 받는 방법을 선택하였습니다.

    Jenkins에서 환경변수 관리 방법

  • 테스트 환경
    로컬에서 개발할 때, 기능 테스트를 어떻게 할 수 있을지 고민하였습니다.

    • 백엔드의 경우
      기능 테스트를 위해 필요한 요소는 DB서버입니다. 따라서 서비스에 필요한 3개의 DB를 도커로 올리고, 테스트 DB 에 연결해 개발할 수 있도록 .env 파일을 관리하였습니다.

    • 프론트의 경우
      실제 배포환경과 최대한 비슷하게 연동해 볼 수 있도록, 배포환경에 필요한 백엔드 구성요소를 올릴 수 있도록 도커 컴포즈를 작성했습니다. 그 후 npm run start:container 명령어로 서버를 로컬에 띄울 수 있도록 하였습니다.

4 주차 - 백엔드 기능 구현

💡 대용량 데이터 처리에 대한 고민과, 세션ID 를 숨길 방법을 고민하였습니다.

대용량 랜덤 데이터의 추가

프로젝트 메인 피처 중 하나인, 다량의 랜덤데이터 추가 기능을 담당하여 구현하였습니다.

388032321-d0920c85-2d3c-4b2d-944a-1483dac21416

  • Domain 설정
    랜덤 데이터를 추가할 때, 의미없는 값보다는 사용자가 활용할 수 있는 값을 넣어주는 것이 활용하기 좋다고 생각하였습니다. 랜덤 값의 Domain 을 설정하고, 사용자가 선택한 Domain 에 해당하는 랜덤 값이 추가되도록 하였습니다.

  • 데이터 추가 방법
    생성된 데이터를 어떻게 추가할 지 고민하였습니다. 처음에는 프로시저를 생성하고, 반복문을 활용해 INSERT 문으로 데이터를 추가하고자 하였습니다. 기획 공유 시간에서 반복문을 통한 데이터 추가는 성능이 매우 좋지 않다는 조언을 받았고, 쿼리 DB 에 불필요하게 많은 부하를 발생시킬 수 있다는 생각이 들었습니다.

    데이터 파일을 생성한 뒤 DB 에 추가하는 방법을 찾아보았습니다. 그러던 도중 다음과 같은 자료를 발견하였습니다.

    ‘bulk Insert’ VS ‘Load Data’ images_morningstar_post_220f7308-2ad6-47ea-be79-ec777017d8ee_bulk_insert_performance

    Insert 명령문의 경우 데이터 삽입 시 마다 Validation 을 수행하기 때문에 대량의 데이터를 추가하기에는 좋은 방법이 아님을 확인하였습니다. 최종적으로는 요청에 따라 .csv 파일을 생성하고, LOAD DATA 명령으로 데이터를 삽입하는 방안을 선택하였습니다.

  • 파일 생성 최적화
    다량의 데이터를 생성함에 있어서 메모리에 전부 들고 있는 것은 좋은 방법이 아닙니다. 따라서 csv 파일을 생성 시 요청 개수가 10000개를 넘는다면 10000개씩의 배치크기로 csv 파일을 생성하고, DB 에 파일을 스트림으로 넘겨주도록 처리하였습니다. 최종적으로 약 10만건의 데이터를 생성하는데 3~4 초 정도의 수행시간을 확인하였습니다.

세션 ID 를 숨겨보자

로그인 기능이 없는 만큼 세션 ID 를 다룰 일이 많습니다. 다만 세션 ID 는 Request 에 종속적인 정보이므로, 서비스에서 이를 자동으로 주입받을 방법을 고민하였습니다.

  • 세션 ID 를 담당하는 클래스 구현
    클래스에서 @Req 객체를 주입받고, 세션ID 를 담당하도록 해주었습니다. 서비스에서 이를 호출하여 세션 ID 에 접근하도록 하였고, 이 클래스를 Transient 스코프로 설정하였습니다.

  • 스코프 전파 문제 발생
    Transient 스코프로 설정하였음에도, @Req 객체를 주입받아 해당 클래스는 Request 스코프로 동작하였고, 스코프 전파가 일어나 의존성이 존재하는 모든 컨트롤러와 서비스가 Request 스코프로 동작하여 인스턴스가 복제되는 문제가 발생하였습니다. 멘토님께 조언을 구한 결과, Request 스코프를 활용하는 것은 상당히 드문 일이고 특수한 경우가 아니면 사용하지 않는것이 좋다는 답변을 받았습니다.

  • session ID 를 매개변수로 받도록 변경
    결과적으로 sessionID 는 컨트롤러에서 주입받고, 매개변수를 통해 서비스로 넘겨주도록 리팩토링하여 해결하였습니다.

5 주차 - 프론트 기능 구현

💡 프론트 구조 파악과, 다양한 라이브러리 학습에 집중하였습니다.

못 다한 기능 구현

프론트를 한 분께서 맡고 계셨습니다. 계획을 못 따라가는 이슈들이 있었고, 이를 도와 기능을 구현하는데 힘썼습니다. 이미 프론트에는 많은 레거시 코드들이 있었고, 그 구조를 파악하고 일관성 있게 유지하도록 노력하였습니다. react-query , tailwind 등 이해가 부족한 라이브러리에 대한 학습에 힘썼습니다.

  • AceEditor 적용 & CSS 문제
    사용자가 쿼리문을 입력할 일이 많기에 쿼리를 하이라이팅하고 자동완성을 적용하기 위해 AceEditor 를 활용하였습니다. 에디터 스타일을 커스텀 하려 하였지만 에디터 내부 스타일이 강하게 적용되어 테일윈드의 스타일이 적용되지 않는 문제가 있었습니다. 에디터 내부 css Class 이름을 파악한 뒤, !important 선언을 활용해 원하는 스타일을 적용해 주었습니다.

프론트 에러핸들링

API 반환 상태코드에 따라 에러메시지를 toast 로 띄워주어야 했습니다. 프론트에서는 API 요청에 useMutation 을 활용하고 있는 상황이었습니다. API 마다 상태코드에 따른 에러핸들링을 한곳에서 관리하기 위해 글로벌 API 에러핸들러를 훅으로 정의하고, mutation 함수에 달아주었습니다.

  • 에러 핸들러를 모든 API Mutation에 달아야 하는 문제

Clone this wiki locally