운영 중인 서비스에 출석 기능을 추가하면서, 단순히 출석 로직만 구현하는 것 이상이 필요했다. 이를 위해 데이터베이스 설계를 새로 하고, 대부분의 코드를 리팩토링하는 작업을 진행했다. 또한, 두 달간의 운영 경험을 바탕으로 문제점을 분석하고 기존 코드의 불편함을 개선하기 위해 리팩토링을 결정했다. 새로운 버전을 배포한 후, 약 두 달 동안 운영하면서 초기에는 몇 가지 예상치 못한 문제가 발생했지만, 모두 해결한 뒤 현재는 안정적으로 서비스가 운영되고 있다.
우선 초기 설계를 진행했을 때의 ERD는 다음과 같다. 사실 서비스를 한 기수 정도만 운영할 생각이었지만 생각보다 반응이 좋아서 지속적인 운영을 하는 방향으로 변경했다. 그에 따른 문제점은 기수(generation)에 대한 정보가 ranking 테이블과 user 테이블에 각각 종속되어 있는데, 서비스 확장 측면에서 보면 하나의 테이블로 분리하는 게 적합하다. 또한 attendance 테이블에 대한 수정도 필요했다.
따라서 ERD는 다음과 같이 수정했다. generation 테이블을 생성 후 다른 테이블에서 참조하도록 변경했고 attendance 속성을 수정했다. 또한 동아리 운영하지 않는 날에 대한 자동 처리를 위해서 unavailable_dates 테이블을 추가했다.
generation 정보를 따로 분리했기 때문에 대부분의 API를 수정해야 했다. 프로젝트를 처음 진행할 때는 DRF에 익숙하지 않아서, API를 간단하게 구현할 수 있는 ModelViewSet을 사용했다. 그러나 시간이 지나면서 이 방식이 오히려 코드의 복잡성을 증가시키는 원인이 되었다. 그래서 각 API를 개별적으로 분리하여 APIView로 리팩토링했다. 이 과정을 통해 더 세밀하게 제어할 수 있었고, 코드의 가독성과 유지보수성을 크게 향상시킬 수 있었다.
서비스 운영 중 두 차례 문제가 발생했다. 우선 어드민 페이지에서 null 값에 대한 처리를 하지 않아 어드민 접속이 안된 문제다. 해당 문제는 쿼리 수정으로 비교적 간단하게 해결할 수 있었다. 또 다른 문제는 운영 중 서버가 마비된 것이다. 이전에도 동일한 문제가 발생했고, 해결했다고 생각했는데 또 발생한 것이다. 서버가 마비된 원인을 조사한 결과, 로그를 확인해보니 Redis 인스턴스가 Master에서 Slave로 전환되었고, 이로 인해 쓰기 작업이 불가능해진 상황이었다. 이 상태에서 Celery가 지속적으로 Redis에 연결을 시도하면서 문제가 발생했다. Celery는 메시지 브로커인 Redis에 의존해 작업 대기열을 관리하는데, Redis가 Slave 모드로 전환되면서 쓰기 작업이 차단되었고, Celery는 이를 인식하지 못한 채 계속해서 재연결을 시도한 것이다. 이 과정에서 메모리 자원이 제대로 해제되지 않아 메모리 릭이 발생했고, 결국 서버의 메모리가 고갈되어 시스템 전체가 마비된 것으로 추정한다.
이를 해결하기 위해 maxmemory 설정을 통해 Redis가 사용할 수 있는 최대 메모리 용량을 설정했다. 또한 maxmemory-policy 설정을 allkeys-lru로 지정하여 maxmemory 한도에 도달했을 때 모든 키 중에서 가장 덜 사용된 키를 삭제하여 공간을 확보할 수 있도록 했다. 또한 도커 컴포즈의 Redis 포트 설정을 제거했다. 하지만 해당 문제의 원인은 테스트를 위해 열어 놓은 모든 포트에 대한 요청을 허용하는 인바운드 규칙을 제거하지 않아서였다. 로그를 살펴보니 외부에서의 공격으로 추정되었기 때문에 원인을 찾을 수 있었다. 해당 문제 해결과 관련하여 작성한 글이다.
150여명의 사용자를 대상으로, 약 10개월 동안 하나의 서비스를 기획, 개발, 운영하며 다양한 것을 느끼고 배웠다. 한 가지 아쉬웠던 점은 처음부터 설계에 좀 더 집중했으면 좋았을 것 같고, 코드를 좀 더 객체지향스럽게 작성했다면 유지보수 할만한 포인트가 적지 않았을까 싶다. 또한 DRF 특성상 객체지향을 지키면서 코드를 작성하기 힘들다는 특성으로 인해 이전엔 유지보수에 용이하지 못하게 코드를 작성했다. 물론 Django의 MTV 패턴을 지키지 않고 레이어드 아키텍처 패턴 등 새로운 패턴으로 처음부터 구조를 잡고 진행했으면 유지보수 용이성과 관련된 아쉬움이 덜 했을 것이다.
이 프로젝트의 목표는 실 사용자 대상으로 서비스를 운영해보는 경험이 목표있고 이를 성공적으로 달성했다. 추가적으로 성능 개선, pytest를 활용해서 테스트 작성 등 시도해보고 싶었던 포인트는 모두 달성했다. 이후에 좀 더 개선할 만한 포인트를 찾아서 진행할 예정이다.(AWS와의 통합, 추가적인 기능 등...)
마지막으로 해당 글을 끝까지 읽는 사람은 많지 않겠지만, 취업을 앞두고 있다면 꼭 실사용자 대상으로 서비스를 운영하는 경험을 추천한다. 해당 서비스를 사용하는 사용자는 많아야 150명 정도이지만 단순히 개발하는 것에 비해 서비스를 운영하는 것은 더 높은 차원의 노력이 필요하고 그 만큼 성장하는 것 같다. 또한 유저들에게 감사 인사를 받거나 서비스를 잘 사용해주는 것에서 정말 보람을 느꼈고, 개발자로서의 정체성을 다시 한 번 느끼는 기회가 됐던 것 같다.
(서비스 시연 영상)
https://butter-yew-22b.notion.site/4749bc9926b74cce9be449f50c2851b8