[네이버부스트캠프 웹・모바일 10기] 멤버십 20주차 회고
- 네이버부스트캠프
- 회고
들어가며
이번 주에는 개발의 기반이 어느 정도 잡히면서 전반적인 작업 속도가 빨라졌다. 팀과 함께 이번 주 목표를 분명히 정했고, 팀 회고에서 실제로 98% 정도 달성했다는 데 의견을 모았다. 나머지 2%는 자잘한 오류들 때문에 제외했다. 주요 기능은 큰 문제 없이 구현을 마쳤고, 이제는 다듬는 작업과 남은 마무리만 남은 상태다.
PR과 슬랙으로 소통하는 주기도 이전보다 짧게 가져가, 진행 상황과 이슈가 빠르게 공유되고 서로의 작업 맥락도 쉽게 파악할 수 있었다. 무엇보다 팀 내에서 실제로 기능을 테스트해보는 과정이 큰 도움이 됐다. 테스트를 진행하며 부족했던 부분을 구체적으로 발견하고, 이를 리스트업해 다시 개발에 반영하는 개발 → 테스트 → 문제 리스트업 → 개선 사이클을 이번 주 내내 돌렸다. 짧은 주기로 피드백하고 개선하는 방식이 작업 능률을 눈에 띄게 끌어올렸다.
지난주 피드백을 반영하다
지난주 팀 회고에서 목표를 더 분명히 세우자는 이야기가 나와, 이번 주에는 데모와 오픈 스테이지 시연을 하나의 목표로 잡았다. 실제 사용자 흐름을 따라가 보니 중요한 기능과 작업 순서가 자연스럽게 정리됐다. 역할을 나누기도 수월해져 망설이는 시간이 줄었고 개발 속도도 올라갔다.
사용자 테스트를 해보자는 이야기도 나왔다. 그전까지는 한 번도 해본 적이 없어 서버가 어느 정도 트래픽을 버티는지 감이 없었다. 그래서 팀 내부에서 먼저 서비스를 써보며 이슈와 병목 지점을 미리 점검했고, 덕분에 외부 참여자가 합류한 시연에서도 서버가 안정적으로 버텼다. 다만 참여자 화면 목록이 어디 있는지 한눈에 드러나지 않는다는 의견도 나왔다. 개발자는 무심코 넘기지만 처음 쓰는 사람에게서만 들을 수 있는 피드백이라, 사용자 테스트를 꾸준히 이어가기로 했다.
개인 회고에서 세운 목표는 화이트보드를 더 적극적으로 쓰는 것이었다. 흐름을 먼저 눈에 보이게 잡아두면 중간에 구조를 뒤엎는 일이 줄 거라 봤다. 이 서비스는 강의실에 입장하는 순간 소켓과 mediasoup가 초기화되고 여러 이벤트가 동시에 서버로 오간다. 어떤 시점에 어떤 상태를 저장해야 하는지가 얽혀 있어, 생각이 막힐 때마다 화이트보드에 단계별로 풀어 적었다.

단계마다 실행할 로직과 이벤트 뒤에 필요한 상태 변경을 코드를 짜기 전에 정리해두니, 구현한 뒤 되돌아가 고치는 일이 확실히 줄었다. 앞으로도 복잡한 기능 앞에서는 화이트보드에 먼저 그려보고 시작하는 습관을 굳혀가려 한다.
mediasoup 로직을 커널처럼 분리하다
화상 연결을 담당하는 mediasoup 로직을 서버와 연결하다 보니, 연결 통로인 transport를 만들고 미디어를 송출(produce)하는 과정이 여러 곳에서 반복되고 있었다. 화면 공유만 해도 상단의 중지 버튼, 하단의 토글 버튼, 브라우저 자체의 중지 버튼까지 세 군데에 같은 중지 로직이 들어 있었다.

처음에는 이 로직을 훅 하나로 추상화하려 했다. 그런데 카메라와 마이크도 어떤 미디어를 보내고 어떤 상태를 갱신할지만 다를 뿐, 서버에 미디어를 보내고 끊는 과정은 같았다. 기능마다 훅을 따로 만들면 겉으로는 중복이 줄어도, 거의 같은 연결/송출 로직이 여러 훅에 흩어질 뿐이었다.

그래서 transport 생성과 producer, consumer 처리처럼 mediasoup에 직접 닿는 로직을 공통 계층으로 떼어내고, 그 위에 카메라, 마이크, 화면 공유를 얹었다. 지난번 OS를 직접 구현하며 배운 커널 구조에서 떠올린 방식이다. 사용자 공간이 커널 내부를 몰라도 시스템 콜로 기능을 요청하듯, 컴포넌트는 고수준 API만 호출하게 했다. 아래부터 mediasoup 훅을 모은 Low-Level 계층, 이를 조합하는 인프라 계층, 비즈니스 흐름을 제어하는 중앙 제어 계층을 차례로 두었고, 최상단의 UI 컴포넌트는 이 컨텍스트와 고수준 명령만 주고받는다.

구조를 바꾸고 나니 문제가 생겼을 때 어디부터 볼지가 분명해졌다. 연결이나 송출이 문제면 공통 계층을, 화면 표시나 상태 변경 시점이 문제면 상위 API와 컴포넌트를 보면 돼서 원인을 찾는 시간이 줄었다. 컴포넌트는 mediasoup 내부 동작을 몰라도 되고, mediasoup 훅은 UI 세부사항을 신경 쓰지 않아도 됐다.
이번 경험으로 특정 도메인 기능을 구현한다고 해서 그 도메인 안에서만 답을 찾을 필요는 없다는 걸 느꼈다. 운영체제처럼 전혀 다른 분야의 개념도 문제를 나누고 추상화하는 데는 충분히 끌어다 쓸 수 있었다. 이 구조가 소프트웨어 아키텍처나 디자인 패턴에서 어떤 이름으로 정리돼 있는지 더 찾아보려 한다.
카메라는 끄고 마이크는 켜둔 이유
카메라와 마이크를 끄는 기능을 만들면서 하드웨어 자원을 어떻게 다룰지 두 번 방향을 바꿨다. 처음에는 끄는 즉시 track.stop()을 호출해 장치 권한을 반환했다. 배터리를 아끼고 프라이버시도 지킬 수 있었지만, 다시 켤 때 장치를 초기화하느라 화면이 잠깐 검게 깜빡였다.
그래서 장치 권한은 유지하고 서버로 보내는 송출만 끊어봤다. 다시 켤 때 초기화가 없어 반응이 빨라지고 깜빡임도 사라졌는데, 이번에는 카메라를 껐는데도 노트북의 카메라 LED가 계속 켜져 있었다. 분명 껐는데 왜 켜져 있냐는 불안을 줄 수 있어, 서비스 신뢰와 바로 이어지는 문제였다.
결국 미디어마다 전략을 나눴다. 카메라는 하드웨어를 완전히 해제했다. 재연결이 조금 늦더라도 LED가 꺼지는 물리적인 안심이 더 중요하다고 봤다. 마이크는 연결을 유지한 채 송출만 제어했다. 대화 중에는 잠깐의 지연도 상대를 되묻게 만들어 흐름을 끊기 때문이다.
Zoom도 비슷하게 동작한다. 카메라를 켜면 노트북 LED와 브라우저 상단 아이콘이 함께 켜지고, 끄면 둘 다 바로 꺼진다. 반면 음소거 상태에서는 마이크 아이콘이 켜진 채로 남고, 말을 하면 "마이크가 음소거되어 있습니다"라는 안내가 뜬다. 마이크 연결은 유지하고 송출만 막고 있다는 뜻이다.

리소스 절감만 생각했다면 카메라와 마이크를 모두 완전히 해제했을 것이다. 하지만 오디오처럼 자원을 더 쓰더라도 즉각적인 반응이 필요한 곳이 있고, 비디오처럼 빠른 반응보다 안심이 먼저인 곳도 있다. 시스템 자원 효율성만 생각해서는 안 된다는 걸 이번에 배웠다.
로컬에서는 됐는데 서버에서는 화면이 안 보였다
기능 구현이 어느 정도 되자 팀원 모두가 개발 서버에 접속해 실제 서비스 흐름대로 테스트를 해봤다. 그런데 시작하자마자 로컬에서는 잘 보이던 다른 사람들의 화면이 서버에서는 전혀 보이지 않았다. 로컬에서 여러 번 확인한 기능이라 담당자로서 머리가 하얘졌다. 강의실 생성과 입장 API는 정상이었으니 WebRTC 신호 교환이나 mediasoup 연결 단계가 문제라고 보고 환경 변수, 포트, 방화벽을 차례로 확인했지만 뚜렷한 원인은 나오지 않았다. 그러다 다른 팀원이 Docker 설정에서 문제 지점을 찾아냈다.
문제의 핵심은 WebRTC 특성과 Docker 네트워크 방식의 차이였다. WebRTC는 미디어를 보낼 때 여러 포트를 동적으로 쓰는데, Docker의 기본 bridge 모드는 미리 지정한 포트만 외부에 열어준다. 필요한 포트를 전부 미리 열어두기도 현실적이지 않아, Docker를 host 모드로 바꿔 컨테이너가 호스트 네트워크를 그대로 쓰게 했다. 그 뒤로는 비디오가 정상적으로 송출됐다. 개발 초기부터 배포 환경과 비슷한 조건에서 자주 테스트했다면 훨씬 일찍, 적은 노력으로 잡았을 문제다. 로컬 테스트만으로는 실제 상황의 모든 문제를 발견할 수 없다는 걸 뼈저리게 느꼈다.
뛰어난 팀원을 만난 덕도 컸다. 혼자였다면 포트 설정쯤에서 생각이 멈춰, 가설 없이 전체 코드를 다시 훑으며 시간을 보냈을 것이다. 그 팀원은 자신이 구현한 부분은 확신을 갖고 과감히 제외하며 탐색 범위를 논리적으로 좁혀 나갔다. 다음에 오류가 나면 나도 가능한 원인을 가설로 세우고 로그와 설정에 하나씩 대조해보려 한다. 결과가 예상과 다르면 틀렸다는 데서 멈추지 않고, 어떤 전제를 잘못 뒀는지까지 돌아보려 한다.
정리하며
이제 정말 2주밖에 남지 않았다. 처음 생각한 수준까지 구현하려면 갈 길이 멀고 AI를 붙여야 하는 구간도 있어 여유가 없다. 그래서 이 프로젝트로 달성하고 싶은 가치와 데드라인까지 현실적으로 가져갈 범위를 기준으로 우선순위를 다시 세우려 한다. 코드 한 줄 한 줄의 완벽함보다 사용자에게 동작하는 서비스를 먼저 내놓는 게 우선이라 보고, 남은 2주는 기능 완성에 집중한 뒤 리팩토링과 구조 개선은 그 다음으로 미룬다.
시간이 지날수록 나도 팀원들도 조금씩 지쳐가는 게 느껴진다. 기획 단계에서는 오프라인으로 모이는 게 의사결정을 빠르게 해줬지만, 개발 단계에서는 이동 시간과 에너지가 들고 듀얼 모니터 같은 환경도 제한돼 오히려 능률이 떨어질 수 있다. 그래서 이슈가 생기면 슬랙 허들이나 줌으로 바로 음성을 켜고 짧게 논의하는 방식을 적극적으로 써보려 한다. 다음 주부터는 사실상 막바지다. 좋은 팀원들과 도전적인 프로젝트를 함께하는 만큼, 끝까지 밀어붙여 완주하고 싶다.