가슴속 3천원 사이드 프로젝트 운영기
안녕하세요. 가슴속 3천원 백엔드 개발자 강승호입니다.
가슴속 3천원은 본업과 별도로 개인 공부 목적으로 진행하고 있는 사이드 프로젝트입니다.
가슴속 3천원 같은 초기/소규모 서비스를 지속적으로 운영하기 위해서 해온 것들을 공유하고, 초기부터 서비스를 운영하면서 발생했던 문제점들을 해결해 온 내용들을 몇가지 공유드려보고자 합니다.
1. 가슴속 3천원 소개
가슴속 3천원은 주변의 붕어빵 등 길거리 음식/푸드트럭 파는 곳을 알려주는 서비스로, 겨울철만 되면, 생각나는 “붕어빵”을 파는 곳이 어딘지 알고 싶어서 만든 앱 서비스입니다.
(앱 다운로드는 아래 링크에서 하실 수 있습니다~)
가슴속 3천원은 팀원 전체가 본업과 별도로 진행하는 사이드 프로젝트라 프로덕트 속도가 아주 빠르지는 않지만, 몇 년 간 꾸준하게 진행하면서 AppStore 1등 달성 및 2023년에는 100만 가입자 수 & 20만 MAU를 달성하였습니다.
2. 가슴속 3천원 팀원 구성 & 협업 방식
2-1. 팀원 구성
초기에는 Android, ios, 백엔드, 디자이너 각 파트별 1명으로 서비스를 만들어가다, 서비스 확장을 위해 좀 더 많은 팀원 분들을 모셔서, 현재는 Android, ios, 백엔드, 디자이너, 마케터 각 파트 별 2명으로 총 10명의 팀원들이 함께 서비스를 만들어가고 있습니다.
2-2. 협업 방식
- 가슴속 3천원 팀은 Slack, Notion, Jira 등 협업툴을 사용하면서 협업하고 있습니다.
- 매년 오프라인 모임을 통해서, 서비스의 방향성을 논의하고 매주 온라인 미팅을 통해 방향성 및 진행 사항 등을 논의 및 공유하고, 이슈나 논의 등은 슬랙으로 자유롭게 논의를 진행하고 있습니다.
- 협업툴은 각 과제 및 버그 등의 이슈 & 릴리즈 관리는 Jira를 통해서, 커뮤니케이션 및 통계, 알림 등은 슬랙으로, 문서 등은 노션을 통해 관리하고 있습니다.
3. 가슴속 3천원 서비스 운영 방식 (백엔드)
3-1. 가슴속 3천원 아키텍처
가슴속 3천원에서는 현재 ECS를 기반으로 애플리케이션 (API 및 워커 서버 등)을 운영하고 있고, 데이터 저장소로는 MariaDB(RDS), MongoDB(EC2), Redis (Elastic Cache)를 사용하고 있습니다.
- ECS는 저희 같은 소규모 서비스 & 인력에 적절하다고 생각하였고, 컨테이너 서비스로 빠르게 Scale-out 할 수 있는 것을 장점으로 보고 선택하였습니다.
3-2. CI & 배포 자동화 파이프라인
현재까지 총 170번의 릴리즈를 진행했었는데요 (평균적으로 매주에 1번씩은 배포하는 것 같네요)
- 이런 배포들을 매번 수동으로 해야 한다면… 얼마나 귀찮고, 실수하기 쉬울까요?
- 가슴속 3천원에서는 Github Actions으로 각 환경 (개발/운영계)으로 CI 및 배포 파이프라인을 구축하여, 파이프라인을 구축하여 배포하고 있습니다. (물론 롤백 같은 경우나 기타 등은 수동으로 배포할 때도 있습니다)
각 환경 및 서비스별로 파이프라인을 구축해서, 필요한 배포에 맞는 파이프라인을 사용하고 있습니다.
3-3. 모니터링 및 알림 시스템
서비스를 운영하다 보면, 서비스가 문제없이 안정적으로 운영되고 있는지, 언제 증설이 필요한지, 문제발생 시 개발자가 인지해서 대응할 수 있는지 등이 필요했는데요.
이러한 필요성으로 가슴속 3천 원에서는 서비스/인프라 지표 및 알림, SLI 지표, 로그 및 에러 로그 모니터링 및 알림 등을 구축해서 관리하고 있습니다.
인프라 모니터링 대시보드
위 대시보드를 통해서 인프라 사용량에 대해서 모니터링하고, 임계치에 대한 알림을 걸어서 운영하고 있습니다. (API 서버는 AutoScaling을 통해서 운영하고 있지만, Worker 서버 및 데이터베이스 인프라는 아직까지는 수동으로 증설하고 있습니다)
Tracing, 에러 모니터링 및 알림 시스템
서비스에 대한 모니터링 및 Tracing 그리고 비정상적인 에러에 대한 알림은 각각 ELK/Sentry, Pinpoint, Slack을 통해서 관리하고 있습니다. 이외에 액세스 로그 및 푸시 발송 등의 로그들은 ELK를 통해서 모니터링하고 있습니다.
3-4. 장애 대응
최근에는 크리스마스에 발송한 이벤트 푸시로 인해서, 평시 피크 대비 10배의 트래픽이 한 번에 몰려서 들어왔고, API 등 인프라들에 대해서 미리 증설해두었지만, 예상보다 더 많은 트래픽으로 인해서 MongoDB의 CPU가 100%까지 올라가고, 이후 요청 처리가 밀리게 되는 장애가 발생하게 되었습니다.
- 당시 상황…
위와 같은 장애로, 단기 대응으로 MongoDB의 READ성의 요청들에 대한 부하를 분산시키기 위해서, Secondary Node들을 추가하여 대응하였고, 이외에 재발 방지를 위해서
- 전반적인 서버들에 대한 가용량 점검을 진행하고,
- 상황이 성능에 문제가 있어서 발생한 문제는 아니고, 단순히 가용량 이상의 많은 트래픽으로 인한 문제로써, 푸시 발송 후 인입되는 사용자들에 대한 트래픽은, 평시 피크보다 좀 더 많이 잡을 계획입니다.
- 그리고 대량의 푸시를 발송하는 경우 푸시 발송 속도를 제어할 수 있도록 개발해서, 푸시를 나눠서 발송할 수 있도록 설정할 수 있도록 개선하였습니다.
4. 서비스가 점점 커지면서 발생하는 문제점
가슴속 3천원 서비스를 초기 구축부터 시작해서, 2~3년간 운영하면서 어느덧 커밋은 3000개가 넘어가는 등 프로젝트는 점점 커지고 있는데요. (사실 다른 프로덕트들에 비하면 이정도는 귀엽겠지만요..)
이에 따라서 빌드 속도는 점점 느려지고, 테스트 코드는 점점 증가하여서 개발 및 CI 및 배포 시간이 증가하는 문제점 등이 생기기 시작했습니다.
위와 같은 문제점을 경험하였고, 다음과 같은 내용들로 개선하였습니다,
4-1. 모듈화
- 각 애플리케이션에서 에서 최소로 필요한 모듈만을 의존해서 사용하는 구조로 모듈화 하였습니다.
- 각 인프라 모듈을 분리한 이유는 빌드 속도 감소 및 불필요한 의존성 최소화가 목적으로, 예를 들어서 불필요한 커넥션을 맺는 등의 문제가 있고, 빌드 등 전반적에 있어서 좋지 못한 문제가 있어서 개선하기 위함입니다.
4-2. 테스트 속도 개선
가슴속 3천원 서비스를 운영하면서 가장 고민했던 주제 중 하나가 테스트인데요.
서비스를 운영하면서 테스트가 점점 많아질수록 (특히 Spring Context가 필요한 통합 테스트) 전체 테스트 실행 속도가 느려지는 문제가 발생하였습니다. 현재 3300개가 넘는 테스트 (통합 테스트의 경우 약 2000개)까지 추가되었는데요. 많은 테스트로 인해 기존에 전체 테스트를 돌리려면 로컬 기준으로 거의 5분 가까이까지 소요되는 문제점들이 있었습니다.
- 먼저 느린 테스트 속도의 가장 큰 주범은 Spring Context가 뜨는 속도로, Spring Test Context Caching을 재사용할 수 있도록 구조를 바꾸고 최적화하였습니다.
- 테스트 환경을 인메모리 볼륨으로 마운트해서 성능을 개선하고, 각 테스트 간에는 테스트 격리를 통해서 데이터들을 클렌징하는 속도 최적화 등 다양한 최적화 진행.
- 개발 환경 개선을 위해서 테스트를 단위/통합 테스트를 분리해서 실행할 수 있도록 하여, 개발 시 필요한 테스트만을 실행할 수 있도록 개선하였습니다. 이런 문제들을 해결하기 위해, 대표적으로 아래와 같은 것들을 진행하였습니다.
- 그리고 CI 환경에서는 전체 테스트를 돌리는 것이 아닌, 모듈 단위로 테스트를 분산시켜 실행하도록 변경해 속도를 개선하는 등의 많은 테스트 개선 작업을 진행했습니다
이러한 개선을 통해서 전체 테스트 수행 속도가 로컬 환경에서는 1분 정도, CI 환경(테스트 환경 구축 등의 시간이 추가로 소모됩니다)에서는 20분 -> 7분으로 감소시켰습니다.
그치만... 한계점이 있다
하지만 이런 테스트 개선 작업을 하더라도, 프로젝트는 점점 더 커질 것이고, 테스트는 점점 더 많아지게 될 것인데요. 이러한 개선에는 한계가 도달할 것입니다.
이에 따라서 테스트 방법을 현재는 아예 다른 방법으로 구상하고, 저의 다른 사이드 프로젝트에서 해당 방법으로 구성하여 경험해보고 있습니다. (실 적용 전, 먼저 다른 프로젝트에서 운영해보고 있습니다)
해당 내용은 먼저 해보고, 괜찮다면 다른 포스팅으로 공유드릴 예정입니다)
4-3. 서비스 분리
담당하는 기능에 따라서 필요에 따라서 서비스를 하나씩 분리하고 있는데요.
먼저 사용자 앱 외에 푸드트럭 사장님들을 대상으로, 직접 가게를 운영하실 수 있도록, “가슴속 3천원 사장님” 앱도 출시하였는데, 이때 처음 (관리툴 및 배치 등 제외) 단일로 구성된 유저 서비스에서 사장님 서비스까지 2개의 서비스로 분리되었습니다.
- 이후 앱 푸시 & 알림 기능 등이 추가되면서, 알림을 공통적으로 담당하는 알림 서비스가 플랫폼화 되어 추가되었습니다.
- 작년 말에 앱 내에 커뮤니티 기능을 초기 오픈하였는데요. 올해 커뮤니티를 좀 더 활성화하는 방향으로, 커뮤니티 기능들을 독립적으로 개발하고, 운영할 수 있도록 커뮤니티 서비스를 분리하고 있습니다. (현재 신규 기능들을 개발하고 있으며, 차후 커뮤니티 기존 기능들을 하나씩 이관할 계획입니다)
- 단일 서비스에서 시작해서, 서비스가 하나씩 추가되고 분리되면서 각 서비스 간의 연동 방식도 고민해야 하고, 분산 트레이싱 및 외부 API 실패 시 전략 (Circuit Breaker, Timeout, Retry 등) 서비스 분리에서 발생하는 문제들을 해결해 가면서 프로젝트를 키워가고 있습니다.
마무리
사실... 사이드 프로젝트로 운영되다 보니 프로덕트 개발 속도가 빠르지 않고, 각자의 속도에 맞춰서 천천히 하는 편이라서,
남는 시간에는 이것 저것 시도해보는 쓸데 없는 짓도 많이 해보고 소규모 서비스에서 오버 엔지니어링도 많이 하고 있습니다 ㅎㅎ
그래도 이런 쓸데 없는 시간이 사이드 프로젝트의 묘미이지 않을까 싶네요.