작업 분배, 일정 산출이 이전 스프린트에 비해 적절히 이뤄진 스프린트여서 만족스럽다. 데모데이 전날에 2시에 시연하기로 했는데, 10시에 시연을 할 수 있었다 ㅋㅋㅋㅋㅋㅋ 8시간 디버깅^^ 그 전날에는 mocking해서 발생한 문제라고 생각했는데 실서버를 사용해도 같은 문제가 발생했다. 그 다음날에 시연해야하는데, 제대로 동작하지 않아 저녁도 대충먹고 문제 해결에 집중했는데 결국 해결해냈다…데모 데이 때 문제가 발생했다면 절대 시간 안에 해결하지 못했을 것이다 미리 시연해서 진짜 다행이였고, 데모날에도 여유롭게 준비하고 성공적으로 시연하였다~!
2주 스프린트 간 어떤 고민들을 했고, 어떤 과정을 거쳐 프로젝트를 진행하고 있는지 공유하려고 한다.
3차 스프린트의 목표는 멀티 플레이
다. 2차 스프린트에서는 혼자 게임을 진행하고 통계를 확인하는 부분까지 만들었다면 이제는 다른 사용자와 같이 할 수 있는 게 목표다. 모임에서 함께 밸런스 게임을 진행하면서 대화 주제를 제공하는 우리 서비스의 핵심 가치다.
멀티 플레이하면 계속 연결되어 있어야하는 거 아닌가? 어떻게 방이라는 걸 만들지? 라는 생각이 들었다. 그래서 웹소켓을 무조건 사용해야 하는줄 알았다. 실시간 통신이라고 하면 단순히 웹소켓이 떠올랐다. 하지만 실시간 데이터 처리를 위해선 여러 방법이 있었다. 해당 키워드로 검색해보면 폴링, 롱폴링, 웹소켓, SSE 등 다양한 키워드가 존재한다. 이 중에서 우리가 선택한 방식은 폴링
이다.
우리 서비스에서 실시간 데이터 처리가 필요한 부분을 정의해보면 다음과 같다.
- 게임이 시작되었는가?
- 다른 사용자가 해당 방에 들어왔는가?
- 모든 유저들이 선택을 완료했는가?
- 다음 라운드로 이동했는가?
- 최종 결과 확인 후 대기화면으로 돌아갔는가?
위 요구 사항을 만족하기 위해선 폴링, 롱폴링, 웹소켓 3가지를 사용할 수 있었다. 하지만 실시간성이 매우 중요한 서비스가 아니라는 점
과 개발 리소스가 크다는 점
을 고려하여 웹소켓은 제외하였다.
처음에는 서버에 요청이 많으면 부하가 커질 것으로 예상되어 롱폴링 방식을 고려하였다. 하지만 롱폴링을 사용할만큼 서버의 부하가 큰지도 모르고, 구현해본 팀원들도 없었다. 따라서, 주기적으로 요청을 보내는 폴링 방식을 사용했다가 문제가 발생하면 롱폴링을 고려하는 방향으로 결정하게 되었다.
너무 짧은 주기는 서버에 영향을 주고, 너무 길면 사용자가 서비스를 이용하는 데에 영향을 줄 것이다. 그래서 1초를 주기로 폴링 요청을 보내고 문제가 생기면 변경하기로 하였는데, 느낌으로는 1초가 길수도 있다고 생각이 들었다. 하지만 개발을 하면서, UT를 하면서 화면이 전환되는 데에 불편함을 느낀 사람이 하나도 없었다. 그래서 일단은 1초 주기를 유지할 예정이다.
로컬 환경에서 되지만 배포 환경에서 안되는 경우가 많았었다. 그런데 문제 해결하고 로컬에서 빌드한 후 s3 올려서 cloudfront 무효화시키는 과정을 반복하니까 수고가 많이 들었다. 그래서 이러한 직접 신경써야하는 비용을 줄이고자 배포 자동화
를 구축하기로 결정하였다.
배포 자동화 흐름을 요약하면 다음과 같다.
0. CodePipeline 으로 source, build, deploy, invalidate 단계를 생성한다.
1.source
: 지정한 브랜치에 push되면 CodePipeline 이 변화를 감지하여 소스 코드를 가져온다.
2.build
: CodeBuild 를 이용해 지정한 브랜치의 최신 소스 코드를 빌드한다.
3.deploy
: CodePipeline 을 통해 빌드한 output을 s3에 저장한다.
4.invalidate
: Lambda Function 을 활용하여 cloudfront의 캐싱을 무효화하고 최신 빌드 결과물을 User에게 제공한다.
이해를 한 상태에서 보면 단순하다. CodePipeline으로 설정한 source, build, deploy, invalidate 단계를 거치면 사용자에게 최신 버전의 서비스를 제공한다. 해당 방식 내에서도 세부적으로는 한 브랜치 내에서 frontend 디렉토리에서 변경사항이 발생할 때만 빌드가 이뤄지도록 분기처리
, dev환경에서는 storybook까지 확인할 수 있도록 cloudfront function 설정
을 적용하였다. 해당 방식을 통해 인프라를 구축한 뒤로는 배포가 성공하였는지 확인만 하면 되고, 세부사항은 신경쓰지 않아도 되어 인프라 관리 비용을 줄일 수 있었다. 구체적인 개발 내용은 별도로 포스팅하였다.
데모데이 때 엄청난 문제를 일으킨 쿼리 캐싱이다. staleTime의 기본값이 0이므로 캐싱이 문제가 없다고 생각했는데, 쿼리는 staleTime이 지나고 gcTime에 있더라도 기존의 데이터를 갖고 있었다. 그래서 1라운드에선 잘되고 2라운드에선 문제가 발생하는 것이였다!!!! 폴링으로 받는 서버 데이터가 true일 경우 페이지를 라우팅하는 방식인데, true 값을 기억하는 것이였다.
우리는 해당 문제를 isFetching으로 라우팅에 분기처리를 하는 방식으로 해결하였고, gcTime에도 값을 기억하고 있다는 것을 알게 되어 추후에는 gcTime을 모두 0 으로 처리하였다.
캐싱의 장점을 모두 버려가면서 문제를 해결했지만, 이렇게 하면 쿼리를 쓰는 의미가 없어지는 것 아닌가? 싶긴 했다. 하지만 좀더 생각해보니 서버 상태 분리
라는 큰 역할을 하고 있고, 이는 곧 관심사의 분리
와 유지 보수 관점에서의 큰 장점으로 이어지기 때문에 쿼리 사용은 유지하기로 하였다.
코드 리뷰를 하면서 우려했던 문제 상황이 발생하여 이를 해결하기 위해 코드리뷰 회고를 진행하였다.
코드 리뷰를 하는 것 자체에는 모두 만족을 느끼는데 리뷰 후 머지가 늦게 되면서 기능 개발에 병목이 생겼다. 해당 문제를 해결하기 위해 PR을 우선적으로 확인하는 것을 규칙으로 가져갔다. 해당 규칙을 정할 수 있었던 이유는 모두가 코드 리뷰의 가치를 이해하고 인정하기 때문이였다. 만약 한명이라도 그냥 approve하자는 방향으로 갔다면 해당 방법으로 해결할 수 없었을 것이다. 팀원 모두가 코드 리뷰를 좋아해서 회고를 통해 문제를 해결할 수 있었다.
문제 상황 : 코드 리뷰는 좋지만 머지가 너무 늦게 되어
병목
이 생긴다.
결론 : PR을 우선적으로 확인하는 방법으로 TRY 해보자 !
해결 방안 : PR을 우선적으로 확인하기 / 작업 전 루틴으로 가져가기 → 효과 : 소통 비용을 줄일 수 있다
- 우선순위가 동일한 경우 해당 루틴 수행 / 개인작업 우선순위가 높을 경우 개인 작업 먼저!
- 1순위. 현재 올라온 코드 리뷰 코멘트 작성
- 2순위. 리뷰 확인 및 반영 코멘트 작성
- 3순위. 개인 작업 (리뷰 반영이나 기능 구현)
- 코멘트가 달릴 때마다 알림이 오는 것이 불편 → 파일 체인지에서 한번에 바꾼 후 리뷰 적용하기
- 리뷰를 확인했는지 알기 어려움 → 확인했을 때 바로 코멘트 달기
- 반영되었는지 알기 어려움 → 반영사항마다 커밋 메세지 작성
- 의견이 달라 코멘트가 새로 달린지 확인하기 어려움 → 파일 체인지에서 작성하여 보기 편하도록
- 근거 기반 코멘트 작성 - why? + 근거를 외부에서 찾기
- ex) 나는 {이런} 근거로 코드를 작성했는데, {그런} 근거로 반영하면 좋을 것 같아요!
또한 리뷰 템플릿의 수정이 필요했다. L1, L2는 준 적이 없고, L4, L5 는 점점 신경쓰지 않는 것 같아서 이를 간략화시켰다. 제안, 질문, 칭찬 3가지로 나눠 사소한 의견 또한 제안으로 가져가고 적극적으로 의견이 반영될 수 있도록 설정하였다.
💡 [ 리뷰 템플릿 수정 ]
- L3 → 제안
- ex) 이렇게 바꿔보는 건 어떨까요? 이렇게 해보는 것도 좋은 것 같아요!
- L4 → 질문
- ex) 이건 어떤 기능인가요? 이 속성을 넣은 이유는 무엇인가요?
- L5 → 칭찬
- ex) 레전드 훅 분리 미쳤다 ㄷ ㄷ ㄷ ㄷ ㄷ ㄷ ㄷ