-
Notifications
You must be signed in to change notification settings - Fork 0
[운영이슈] 2023.03.30 배포 이슈 기록
이 글을 작성하는 날 ( 2023.03.20 12:00 a.m. )에 발생한 이슈에 대해 기록으로 남겨두려 합니다.
명식이 VERSION 2 업데이트를 진행하면서 [맛집 탭]에서 정렬 조건으로 "거리순"을 도입하기로 했습니다. "거리순"에 사용되는 컬럼은 가게 테이블(Store)에 있는 거리 속성(distance)입니다. 해당 컬럼으로 조회 시, order by에 사용됩니다.
가게 리스트를 조회하는 API에서 Controller에 파라미터로 Pageable를 넘겨 받기 때문에, sort 정렬 조건으로 어떠한 컬럼 ( 단, 해당 컬럼이 조회 시 사용되는 테이블 안에 포함되어야 합니다. )을 넘기더라도 요청을 받는 데에는 지장이 없습니다. 다만, 비즈니스 로직에서 native query에서 오류가 발생했습니다.
Native query 이슈에 대한 기록은 여기를 참고해주시기 바랍니다.
distance가 varchar 타입으로 DB에서 정의되어 있기 때문에 원활하게 정렬이 이루어지지 않아 이를 쿼리문에서 타입 캐스팅하였습니다. 코드 리뷰 과정에서 해당 컬럼의 타입을 String -> Long으로 변경하여 진행해도 DB에서는 이슈가 발생하지 않을 것 같다는 의견에 타입을 변경하였습니다. 이로 인해 DTO에서도 distance 타입을 Long으로 변경했습니다.
이 부분에서 IOS와 DTO 변수 타입의 불일치로 API가 원활하게 동작하지 않음을 발견하였습니다.
미쳐 고려하지 못했던 부분을 캐치해주신 PM이자 AOS 개발자인 @성식님 감사합니다.
위의 이미지와 같이 조회 시, 노출되던 가게 데이터가 보여지지 않는 이슈가 발생했습니다. 이슈 발생 당시에는 원인을 바로 파악하지 못했으나, 문득 정상적으로 동작하던 코드와 달라진 점을 떠올렸을 때 타입의 변경이 가장 먼저 떠올랐습니다. 다행히도 해당 이슈는 타입을 다시 Long -> String 으로 변경하면서 해결하였습니다.
아래 사진과 같이 오늘(2023.03.29) 갑자기 Github Actions의 속도가 현저하게 느려짐을 확인하였습니다. 평소 배포 소요 시간은 2-3분 내외였는데, 한 시간이 넘도록 진행되는 점에서 이상함을 감지했습니다.
build 과정에서 오류가 발생함으로 인해 시간이 느려진 것이라면, 그 원인 파악이 가능하도록 로그가 남는데 위의 경우에는 SUCCESS
이므로 workflow에서는 실패 지점이 없었습니다.
평소와 다르게 CD 과정이 오래 걸리는 이유에 대해 여러 케이스를 고민했습니다. 아래 2가지 케이스는 모두 추측입니다.
- 캐시 액션이 원활하게 동작하지 못함
빌드 속도를 빠르게 개선하기 위해 캐싱 작업을 workflow에서 사용 중입니다. 해당 작업이 특정 커밋 혹은 PR에서 원활하게 진행되지 않은 채 작업들이 Queue에 쌓이면서 발생한 오류이지 않을까 하는 생각을 했습니다. 이는 실제로 이러한 이유인지에 대한 검증이 아직 이루어지지 않은 상태입니다.
<추가 업데이트>
2023.03.31 @규범님과 이슈에 대해 논의한 후, 저희가 내린 결론을 정리합니다.
Github Actions는 workflow 파일에서 지정한 이벤트 트리거에 대해 이벤트가 들어오면, 해당 작업을 Queue에 넣어두고 선입선출 방식으로 작업을 수행합니다. 이슈 발생 당시, develop 브랜치로 PR을 날렸던 이벤트에 대해 작업이 완전히 수행되지 않은 상태에서 main 브랜치로 push 이벤트가 들어오면서 앞선 작업이 완료되지 않아 뒤에 들어오는 작업이 처리되지 않았습니다.
Github Actions를 이용해 자동 배포를 진행하는 것이 우선이었어서 main 브랜치로 push 이벤트를 계속 발생시킴으로 인해 Github Actions 내부 Queue에 오류가 발생했을 것으로 추측하고 있습니다.
- EC2 서버 메모리 부족
CD 작업에서 Github Actions와 Docker를 이용하고 있습니다. Docker를 사용하는 이유는 독립적인 컨테이너 환경으로 어플리케이션을 관리하고, 이미지로써 버전 관리에 용이하기 때문입니다. Docker를 이용해 EC2 PROD 서버 내에서 Spring Application과 Prometheus, Grafana를 컨테이너로 구동 중입니다.
현재 명식이 PROD 서버의 스펙은 t2.micro를 사용하고 있으며, 이는 AWS에서 지원하는 가장 낮은 사양의 서버입니다. 서버의 스펙을 가장 낮게 잡은 이유는 초창기 서비스이기도 하며, 유저의 수가 어느정도로 유입될지 알 수 없기 때문에 추후 auto-scaling( 혹은 scale-up )으로 대응하는 것이 비용적 측면에서 나을 것 같다고 판단했습니다.
VERSION 1의 경우, 누적 다운로드 수가 800명대로 t2.micro 스펙으로 충분히 감당할 수 있는 규모의 트래픽이었으며 서버 내에서 구동 중인 컨테이너 역시 Spring Application 하나 뿐이었습니다. 즉, 메모리 측면에서 부담이 가해지는 수준이 아니었습니다.
VERSION 2의 경우, 누적 다운로드 수가 2000명에 가까워지면서 트래픽이 이전보다 훨씬 늘었으며, 서버 내에서 구동 중인 컨테이너도 3대로 늘어났습니다. 뿐만 아니라, 기능의 확장 및 추가로 Spring Application 자체의 크기가 커지면서 메모리를 이전보다 더 많이 사용하게 되었습니다.
서버 내에서 도커 컨테이너가 사용하는 메모리는 아래 커멘드로 확인할 수 있으며, 실제 명식이 운영 서버의 결과값은 아래와 같습니다.
docker stats --format "table {{.Name}}\t{{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"
메모리 사용량을 보면, 다른 컨테이너에 비해 월등히 높음을 알 수 있습니다. ( 단, 비교군이 동일 서버 내의 다른 컨테이너 뿐입니다. VERSION 1 당시의 컨테이너가 차지하는 메모리 수치를 따로 기록해 둔 것이 없기 때문입니다. )
위의 사진에서 보면 운영 서버 중 PROD 1에는 구동 중인 컨테이너가 한 대 뿐인 것을 확인할 수 있습니다. 깃헙 액션으로 자동 배포가 이루어지지 않아 수동 배포로 진행하면서 PROD 1 도커 이미지를 임의로 pull
받아 run
하였을 때, 메모리 부족으로 다른 컨테이너까지 down 되었기 때문입니다.
테스트를 위해 PROD 1에는 spring 컨테이너만을 구동 중입니다.
해당 이슈의 원인으로 가장 유력하게 보고 있는 것이 EC2 서버 메모리 부족입니다. Github Actions의 작업 속도가 느려지는 것 역시 빌드와 push-pull 과정에서 메모리 소요가 있기 때문이라고 생각합니다.
이 글을 작성하는 시점에 Github Actions의 CI/CD workflow가 정상적으로 동작하지는 않습니다. ( 수동 배포로 운영 환경은 복구한 상태입니다. 🥲 ) 추측한 원인이 맞는지에 대한 검증 과정과 CICD 작업 속도 개선을 위해 리펙토링을 진행할 예정입니다.
해당 과정에서 참고할 레퍼런스는 여기입니다.
해당 이슈는 2번 이슈
에서 발견한 부분입니다. 수동 배포 과정에서 Spring Application 도커 컨테이너가 run
과 동시에 죽는 것을 발견했습니다. docker logs 컨테이너명
으로 확인한 결과, 로컬 환경에서 진행했던 몽고디비 설정으로 인해 서버가 down 되는 것이었습니다.
명식이는 RDB 기반으로 서비스를 구축하여 운영 중입니다. 운영 단계에 접어 들면서 요구사항의 변경과 식당 측으로부터 전달 받는 데이터의 포맷이 달라지는 점으로 인해 ERD의 변경이 잦아졌습니다. 이를 좀 더 유연하게 대응하고자 "컬럼의 수평적 확장"이 큰 특징인 MongoDB의 도입 의견이 나왔습니다. 이를 운영 단계에 적용할지에 대해 기준점이 필요하다고 생각하여 로컬 환경에서 테스트를 진행했습니다.
해당 마이그레이션 과정은 여기를 참고해주시기 바랍니다.
MongoDB로의 마이그레이션은 당장 진행하지 않는 걸로 파트원과 합의를 내린 상태였으며, 각자 로컬에서 작업할 수 있도록 develop - main
브랜치로 merge 하였습니다. 이 부분에서 문제가 발생하였습니다. 로컬 환경에서 테스트를 위해 MongoDB를 도커 컨테이너로 구동하였는데, 운영 환경에 있는 서버 내에는 MongoDB가 없기 때문에 Connection Error가 발생했습니다.
main 브랜치 코드에서 MongoDB와 관련된 코드는 전부 주석 처리로 ( 로컬에서 테스트는 진행할 수 있어야 하므로, 코드를 완전히 삭제하지 않았습니다. ) 변경하여 도커 이미지를 다시 빌드하였습니다. 해당 이미지를 각 운영 서버에서 pull - run
하니 정상적으로 구동 함을 확인했습니다.
명식이는 로컬 환경 ( = 개발 환경 )과 운영 환경으로만 구성되어 있습니다. 여기에 스테이징 환경 ( = QA 환경 )을 추가로 도입하여 운영 단계 이전에 충분한 테스트와 검증이 필요로 하다고 생각합니다.
해당 아이디어는 @성식님과 이슈 해결 과정에서 나온 것입니다.
비즈니스 로직의 검증을 위해 테스트 코드의 작성의 우선순위를 높여 기능 개발과 동시에 이루어지도록 해야 할 것 같습니다. CI를 통한 테스트 커버리지를 높이기 위함입니다.
해당 이슈 해결을 위한 원인 파악을 위한 재점검의 시간을 가질 예정이며, 그 과정에서 빌드 속도 개선과 병렬적 처리가 가능한 작업에 대해서는 병렬 처리 방식 도입을 고려하고 있습니다. 추가로 알림 발송 작업을 넣으려 합니다. ( 서버의 헬스체크와 깃헙 액션 작업의 성공 유무를 알려주는 것으로 생각하고 있습니다. )
이슈 발생부터 해결까지 대략 2시간 가량 소요되었습니다. 8시간 뒤면 사용자들이 명식이를 통해 학식을 조회하기 때문에 이슈 해결까지도 시간이 넉넉하다고 볼 수는 없었습니다. 🥺 빠른 시간 내에 운영만이라도 원활하게 이루어질 수 있도록 배포 이슈 해결을 위해 여러 관점에서 원인 파악과 해결 방안 모색을 하였습니다.
그 과정에서 깨달은 점은 다음과 같습니다.
- API 수정은 함부로 하지 말자.
API 수정 요청이 들어와도, 프론트엔드 측에서 바로 반영할 수 있는 부분이 아니라면 수정하지 않는 것이 안전하다고 느꼈습니다. 기존 API 연동과의 충돌이 발생할 수 있기( 아니 발생하기 ) 때문입니다.
- 다른 노드와의 연동은 신중히 하자.
MongoDB와 같은 다른 노드와의 연동과 그 설정이 운영 환경까지 올라가면, Connection Error가 발생하기 때문에 이는 주석 처리 혹은 main 브랜치에 merge 되지 않도록 주의를 해야함을 느꼈습니다. ( 자칫 "제 컴퓨터에서는 잘 되는데요?" 와 같은 상황이 발생할 수 있습니다. 😅 )
- 이슈가 터지면, 과거의 나를 의심하라.
어떠한 변경이 가해졌기 때문에 발생한 이슈이니 그 원인 파악을 위해서는 과거의 나를 가장 먼저 의심하는 것을 습관화 해야겠다고 다짐했습니다. 부주의하게 main 브랜치에 작업 내용을 반영하면서 발생한 이슈를 마주하니 아찔함이 가장 먼저 들었습니다. PR로 문서화를 해두어 과거의 어떤 작업을 했고 어느 부분을 변경했는지 바로 파악이 가능해서 이점은 좋았습니다.
이상으로 이슈 회고를 마치겠습니다. 🙇🏻♀️