[Project] 하이스코어 프로젝트 1차 목표 달성 및 후기
포스트
취소

[Project] 하이스코어 프로젝트 1차 목표 달성 및 후기

하이스코어 리팩토링 프로젝트를 진행하면서 경험한 부분을 기록하기 위해서 작성했습니다.

개요

고등학생때 교내 대회 출품용으로 ‘내신계산기’라는 서비스를 개발했었습니다. 대입을 위해서 내신 점수를 계산해보고 싶은 상황이 생기면 매번 계산기를 켜고 계산하는 고등학생들의 귀찮음을 해결해주자 라는 목적으로 개발된 서비스였습니다. 교내 대회에서 최우수상을 수상하고 실제로 출시해도 많은 사람들이 사용할 것 같아서 바로 출시했습니다.

ln1

결과는 성공이었습니다. 당시 학생들 사이에서 입소문을 타서 꽤 많은 학생들이 서비스를 이용했고, 시험기간에는 MAU 1700을 달성하기도 했습니다.

대학생 1학년 생활하면서 사용자 피드백 반영, 버그 수정, Flutter로 리팩토링 후 IOS 출시까지 지속적으로 유지보수를 진행했었습니다. 그러다가 너무 바빠져서 내신계산기 개발은 점점 뒤로 미뤄졌고, 서비스는 방치되게 됩니다.
그렇게 2025년까지 4년정도 방치되다가, 정부 정책으로 5등급제가 나왔다고 해서 반영할 겸 UI와 기능을 개선해 업데이트하기로 마음먹게 됩니다.

기획

리팩토링을 진행하기 위해 우선 머릿속에만 있던 생각을 구체적으로 정리하기 시작했습니다.

팀원 모으기

가장 먼저 진행한건 팀원들을 모으는 일 이었습니다. 이전까지 내신계산기는 프론트만 개발하면 돼서 거의 혼자 개발을 진행했는데, 이번에는 서버를 따로 만들어 사용자 로그인 기능과 성적 데이터 서버 저장 기능을 추가하기 위해 백엔드 개발을 진행해야 했습니다. 지난 4년간 여러번 팀플을 진행해봤고, 팀장으로 이끄는 경험도 여럿 생겨서 협업에 자신이 있었습니다. 그래서 같이 일을 분담할 팀원들을 모았고, 디자이너 1명, 백엔드 개발자 1명을 추가해서 팀을 구성하게 되었습니다.

기능 정리

팀원들과 모여서 진행한 가장 첫 회의는 제 머릿속에만 있던 기능을 구체화 하는 회의였습니다.

ln1 ln1

우선 현재 구현되어있는 서비스의 문제점, 5등급제 계산 방법 등 기능 정리에 필요한 사항들을 파악하고 기록했습니다. 이 과정에서 팀원들도 여러 아이디어를 냈고, 추가적으로 생각해볼 부분을 기록했습니다.
그 다음, 파악한 정보를 바탕으로 구현하려는 목적에 대해 정리했습니다.

  • 계정 기능을 만들어 성적 데이터를 연동. 다른 기기에서 동일한 계정으로 로그인 하면 복구가 가능하도록 구현.
  • 수시/정시 성적 입력 기능
  • 수시 성적 계산 기능. 이때 학교의 반영 비율, 반영 과목을 마음대로 조정해서 볼 수 있는 프리셋 기능도 구현.
  • 정시 성적 간편하게 보여주는 기능
  • 수시 5등급제 반영
  • 더 직관적인 디자인

이렇게 정리된 목적을 바탕으로 구현이 필요한 기능들을 생각했습니다.

ln1

그러나 단순히 기능들을 나열하니 직관적이지 않아서, 기능들을 모두 기능 명세서 형식으로 정리했습니다.

api 명세서 작성

기능 명세서를 바탕으로 필요한 api를 파악하고, api의 정의를 명확하게 하여 추후 협업 개발에 혼선이 없고자 하는 목적으로 api 명세서를 작성했습니다.
api 명세서 작성은 일부 인증 api 제외한 대부분의 api는 백엔드 담당 팀원이 진행했습니다.

ln1

저는 api 명세서의 형식과, 제가 구현 예정인 인증/인가 처리 관련 api만 작성했습니다.

디자인

ln1

디자인은 디자인 담당 팀원이 진행했고, 저는 피드백을 요청하면 지속적으로 피드백을 주고받으며 수정하는 형식으로 진행했습니다. 기능 명세서의 기능이 모두 있는지, 조건에 맞는 디자인인지를 지속적으로 검토하며 수정을 반복했고, 최적의 디자인을 찾으려고 다함께 노력했습니다.

ER다이어그램 작성?

마지막으로 데이터를 저장하기 위한 DB를 구성하기 위해 ER다이어그램을 그리려고 했습니다. 그런데 생각해보니 RDB 형식으로 데이터를 정의하는것 보다 noSql 기반의 MongoDB를 이용하는게 더 좋을것 같았습니다. 그 이유는 다음과 같았습니다.

첫 번째로 수시/정시 성적은 항상 특정 사용자에게 종속되는 데이터인데, RDB에 저장하는것 보다 Json처럼 계층적으로 구성하는게 더 좋겠다 라는 생각이었습니다. RDB로 데이터를 모델링 할때마다 드는 생각이었는데, 특정 사용자에게 종속되는 데이터를 같은 엔티티라는 이유로 하나의 테이블에 떄려박고 유저가 정보를 요청하면 그 부분만 가져오는게 개인적으로 맘에 들지 않았습니다. 그래서 이참에 MongoDB로 진행해보자 해서 MongoDB를 선택하는 첫 번째 이유가 되었습니다.

두 번째로 서비스를 지속적으로 업데이트 하는 과정에서 분명히 기존 테이블의 구조를 바꿔야 하는 순간이 올꺼라고 생각했습니다. 저희는 가장 필요하다고 생각되는 기능들을 우선적으로 구현하고 순차적인 업데이트로 기능을 하나씩 추가할 생각으로 개발을 진행하기로 했기때문에 지금 설계된 테이블도 나중에 가서는 바꿔야 할 순간이 반드시 올꺼라고 판단했습니다. 그래서 데이터 저장 형식의 수정이 자유로은 MongoDB를 선택하는 두 번재 이유가 되었습니다.

ER다이어그램을 그리려다가 문득 든 생각으로 MongoDB를 사용하기로 결정했고, 결과적으로 ER다이어그램은 작성하지 않았습니다.

개발

기능 명세서, 디자인, api 명세서까지 개발에 필요한 사전 준비가 모두 완료되었습니다. 이제 기획을 바탕으로 실제 개발을 시작했습니다.

개발 진행 방법 및 규칙

우선 본격적인 개발 전 팀원들과 어떤 방식으로 개발을 진행할지 먼저 회의했습니다.

개발 일정 관리

저는 이전의 여러 협업 경험들을 바탕으로 애자일 스크럼 빙밥론을 적용하여 진행하자고 제안했습니다. 처음부터 워터풀 하게 개발 목표를 모두 정해두고 진행하면 중간에 흐지부지되는 경험을 많이 했었고, 이번에는 그런 일이 없도록 최대한 중요한 기능 단위로 개발하고 순차적으로 업데이트 하는 방식으로 진행하고자 했습니다. 팀원도 모두 동의해줘서 스크럼 방법론을 적용해 개발 일정을 관리하기로 결정했습니다.

ln1

ln1

스프린트 주기는 1주일로 잡고, 매 주 주말에 스프린트 계획 회의와 스프린트 리뷰를 진행하기로 정했습니다. 스프린트 및 백로그 관리는 Jira를 이용하기로 정했고, 데일리 스크럼은 디스코드를 이용해 비대면으로 간단히 진행하기로 했습니다.
그러나 방학때까진 잘 진행되다가 개강한 후에는 각자 학교 시험, 대회 참여 등 외부 일정때문에 정기적으로 모이는게 힘들어졌고, 결국 데일리 스크럼만 진행하며 필요할때만 비정기적으로 회의를 진행하는것으로 바꾸게 되었습니다.

Git-Branch 전략

여러 사람들과 협업을 진행하는 프로젝트이기 때문에, git에 코드를 올릴때도 branch마다 규칙을 정해 효율적으로 관리할 수 있게 했습니다.

ln1

저희는 Git-flow 브랜치 전략을 가져와 규칙을 약간 수정했습니다. 백엔드 레포지토리 브랜치 규칙은 다음과 같습니다.

  • master: 릴리즈 서버에 배포되는 브랜치. beta에서 문제가 없다고 판단되면 필요한 시기에 여기로 병합합니다.
  • beta: 테스트 서버에 배포되는 브랜치. dev에서 완성한 기능들을 테스트 서버에 반영할때 여기로 병합합니다.
  • dev: 완성된 기능들을 병합하는 브랜치. feat/* 브랜치의 분기와 병합을 여기서 진행합니다.
  • feat/*: 실제 개발을 진행하는 브랜치. Jira의 백로그 ID를 붙여 어떤 기능에 대한 개발인지 표현합니다.

프론트 레포지토리 브랜치 규칙은 beta를 제외하면 동일합니다. 프론트는 dev가 beta와 dev의 역할을 동시에 수행합니다. 프론트 레포에서는 dev 브랜치는 완성된 기능들을 결합하는 동시에 내부 테스트로 올릴 코드들을 관리하는 브랜치입니다.

Commit Message Convention

커밋 메시지 역시 규칙을 정해 이 커밋이 어떤 수정을 진행한 커밋인지 파악하기 쉽게 했습니다. 기본적인 구조는 다음과 같습니다.

1
[Jira Backlog ID] [type]: [commit message]

Jira Backlog ID는 어떤 백로그에 해당하는 작업인지 판별하기 위해 정했습니다.
Type은 이 작업이 어떤 종류의 작업인지 알기 쉽게 하기 위해 정했습니다. 구체적인 Type은 다음과 같습니다.

  • feat: 새로운 기능 추가
  • fix: 버그 수정
  • chore: 빌드 업무 수정, 패키지 매니저 수정 등 직접적인 프로덕션 코드의 변경이 없는 작업
  • build: 앱 패키징 등 빌드 작업
  • rename: 파일, 클래스 등 이름을 변경한 작업

다른 블로그를 보니 이 외에도 많은 type이 있었는데, 저희는 이정도만 필요할 것 같아서 이렇게 5가지만 활용하고 있습니다.

아키택쳐 구성

본격적인 백엔드 개발 전, 테스트 서버 배포를 위한 아키택쳐를 먼저 구성했습니다.
서버는 Oracle Cloud Instance 두 대를 각각 테스트용, 릴리즈용으로 활용했습니다. 전체적인 아키텍쳐는 다음과 같습니다.

ln1

이번에 서버를 구성할때는 ELK Stack 로그 관리, MongoDB Cluster 등 지금까지 해보고 싶었던거 전부 다 적용해 봤습니다.

ELK Stack 구축

이전에 프로젝트를 진행할때, 항상 로그를 제대로 남기지 않아서 문제가 생겨도 원인을 파악하는데 오랜 시간이 걸렸습니다. 이전까지는 Docker에 배포한 백엔드 컨테이너에서 로그 부분만 외부 volumne에 연결해서 일일이 수작업으로 로그를 추적하는 과정이 필요했습니다.
로그를 볼때마다 노가다를 해야하는 상황이 또 발생하는게 싫어서 이번 프로젝트에는 ELK Stack을 구축했습니다. 테스트 서버에 ElasticSearch, Kibana, Logstash를 올리고, 테스트와 릴리즈 서버에 각각 Filebeat를 올려 로그 파일이 변경되면 ElasticSearch에 저장되도록 했습니다. 원래는 FastAPI Logger에서 직접 Logstash로 전달하게 만들려고 했는데, Docker라서 그런지 제대로 전달이 되지 않아서 Filebeat로 구축하게 되었습니다.

MongoDB Cluster 구축

이번 프로젝트는 실제 서비스를 릴리즈 할 예정이었기 때문에 DB가 조금 더 안정적으로 동작하면 좋겠다고 생각했습니다. 그래서 두 개의 인스턴스가 있는 김에 MongoDB를 클러스터로 구축해보자 하는 생각이 들어 진행하게 되었습니다.
릴리즈, 테스트 인스턴스 각각에 Primary와 Secondary를 두고, 테스트 인스턴스에 Arbiter를 두어 한 인스턴스가 죽으면 자동으로 Primary를 바꾸게 구축했습니다.

그런데 글을 정리하면서 생각났는데, 지금 서버 구성상 릴리즈 서버에는 api 요청을 처리하는 FastAPI도 같이 있기때문에 별로 의미 없었던 구성 같습니다. 아직은 트래픽이 적어서 감당이 가능하긴 한데, 나중을 위해서 오토 스케일링이나 다른 방법을 좀 더 생각해 봐야겠습니다.

Jenkins 구축

Jenkins는 이전 프로젝트에서도 유용하게 활용했습니다. 이번 프로젝트 역시 자동 배포를 위해 구축했습니다.
백엔드 레포에 beta 브랜치에 머지하면 자동으로 Jenkins가 빌드해서 테스트 서버에 반영되도록 했습니다. 릴리즈 서비는 테스트 서버와 스크립트를 동일하게 구성을 했지만, 자동으로 실행되게는 만들지 않았습니다. 혹여나 잘못 push했을때 사고가 발생할것을 방지하기 위해서 입니다.

백엔드 개발

백엔드는 FastAPI로 개발을 진행했습니다.

대부분의 CRUD API는 백엔드 담당 팀원이 진행했고, 저는 사용자 계정 및 인증인가처리 부분만 구현했습니다.
특히 인증 부분에 많은 신경을 써서 개발했습니다. 실제로 배포하는 서비스인데, 만에하나 토큰이 유출되고 보안 사고가 터지면 뒷감당이 안될것 같아서 다양한 방법을 생각했고, 결과적으로 DPop이라는 인증 방식을 찾아 직접 구현하여 적용하게 되었습니다. DPop에 대한 자세한 기술적인 내용은 [DPop] Demonstration of Proof-of-Possession에서 확인할 수 있습니다.

DPop 구현이 이번에 구현한 기능 중 가장 어렵고 힘든 작업이었던거 같습니다. 구현 자체는 IETF에 등록된 문서와 GPT를 이용해 어찌어찌 하긴 했는데, 한국어가 된 자료가 전혀 없어서 정보를 얻고 구현하는데 총 2주가 걸릴 정도로 오래 걸리고 어려웠습니다. 그래도 결과적으로 사용자의 기기가 직접적으로 털리지 않는 이상 웬만하면 안전한 인증 방식을 구현했다는 생각에 뿌듯하긴 합니다.

프론트 개발

프론트는 Flutter로 개발을 진행했습니다. 대부분의 취업 공고에서 네이티브 개발자를 찾길래 네이티브로 각각 개발해볼까 생각은 해봤지만, 혼자 개발을 해야하는 상황에서 각 OS마다 네이티브로 개발하는건 너무 비효율적인거 같아서 Flutter로 진행하게 되었습니다.

Google Analytics

이번 앱 개발에서 중점적으로 생각한건 사용자 데이터 추적 및 활용이었습니다. 이전 프로젝트에서 앱을 개발할땐 항상 기능 구현에 중점을 두고 기능만 구현 후 배포했는데, 이번에는 GA4와 같은 Data Analytics를 적용해 사용자들의 앱 사용 흐름과 내가 만든 기능을 잘 사용하는지, 의도한 흐름대로 사용하는지 등 사용자의 데이터를 보고 판단할 수 있게 만들고 싶었습니다.
Firebase Analytics를 이용헤 Google Analytics를 연동했습니다. sign_up(회원가입), add_susi(수시 데이터 추가) 등 중요하다고 생각되는 액션들을 수행할때마다 이밴트가 기록되게 만들었습니다.

그리고 Analytics는 출시한지 얼마 되지 않아 큰 도움이 되었습니다.

활용 사례 1

출시 후 사용자들이 기능을 잘 사용하는지 대시보드로 보고있는데, 이상하게 앱을 처음 여는 first_open 이벤트는 수북히 쌓이는데 회원가입 이벤트인 sign_up은 숫자가 현저히 적어 보였습니다.

ln1

그래서 GA4에서 유입경로 탐색 분석을 해보니 first_open 후 같은 세션에서 바로 회원가입까지 진행하는 사람의 비율이 약 22%라는 사실을 알게 되었습니다.

앱을 업데이트하면 가장 먼저 회원가입을 해야 정상적인 서비스 이용이 가능할텐데, 앱을 연 사용자들은 앱만 열어보고 다시 나가는건가? 싶었습니다. 그래서 왜 그럴까 생각해보니 업데이트하면서 앱 이름도 내신계산기에서 하이스코어로 바꿨고, 앱 로고까지 바꿔서 사용자 입장에서는 내가 언제 이런걸 깔았을까 하면서 앱을 켰을것이라고 생각이 들었습니다. 거기다가 서비스가 업데이트되면서 회원가입을 요구하는데, 이에대한 설명도 충분하지 않았었습니다. 그러니 사용자 입장에서는 처음 보는 앱이니까 뒤로가기를 누르는게 어찌보면 당연한겁니다.

ln1

그래서 이 사실을 깨닫자 마자 앱을 첫 실행하면 업데이트 안내와 회원가입을 유도하는 다이얼로그를 띄우도록 한 뒤 긴급심사로 업데이트를 바로 올렸습니다. 아직 사용자들이 모두 업데이트가 되진 않아서 회원가입 비율이 어느정도 늘었는지 정확히 판단은 어려우나, 어찌됐건 GA4로 인해 놓쳤던 부분을 다시한번 생각하게 되었습니다.

활용사례 2

그리고 GA4를 예상치 못한 곳에서 활용하게 되었습니다. 앱을 출시한김에 잃어버린 사용자들을 찾고자 Google Ads로 광고를 걸어보려고 했습니다. 그런데 Google Ads에서 광고가 효과적으로 목표에 도달했는지 판단하는 지표를 GA4를 이용해서 판단하는 기능을 지원한다는 사실을 알았습니다. 그래서 저는 회원가입 이벤트인 sign_up을 전환 이벤트로 지정했고, 회원가입까지 진행할 사람에게 광고를 보여주라고 더욱 구체적인 타겟팅이 가능했습니다.

후기

ln1

앱 리팩토링의 첫 목표인 수시 기능 및 회원 기능 업데이트가 성공적으로 완료되었습니다. 최근 몇달동안 진행한 프로젝트들은 모두 끝까지 마무리하지 못했는데, 오랜만에 끝까지 진행해 목표를 달성한 프로젝트라 더 기분이 좋은것 같습니다.

이번 프로젝트는 제가 주도하는 프로젝트라서 그런지 그동안 해보고 싶었던 것들을 다 해본것 같습니다. 그 과정에서 분명 어려움도 있었지만 결과적으로 어쩄든 원하는대로 구현했고, 모두 도움이 되는것 같아 만족스럽습니다.

이제 다음 목표인 정시 데이터 관련 기능들을 구현하고 업데이트를 진행해야 합니다. 고등학생들은 수시뿐만 아니라 정시로도 대학교를 가기 때문에 꼭 추가해야하는 핵심 기능이라고 생각합니다. 이처럼 앞으로도 사용자들이 필요로 하는 기능이 뭔지 생각해보고, 구현해서 사용자들이 만족하는 서비스가 되기를 노력해야겠습니다.


앱 다운받기

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

[ML] LoRA (Low-Rank Adaptation of Large Language Models)

-