2022.11
## 회고
과도한 웹소켓 전송 때문에 장애가 발생했다. 그래서 같은 장애를 예방하기 위해 일단 웹소켓 관련 지표를 수집하기로 했다. 웹소켓 연결 개수와 전송 개수를 수집하기로 했고, 장애의 원인이 일부 서버에만 웹소켓 연결이 몰려서 버티지 못했던 것이기 때문에 지표를 서버 별로 구분해서 수집해야 했다.
먼저 전송 개수를 수집할 때는 Ruby on Rails의 Instrumentation을 사용해서 웹소켓 전송이 될 때마다 CloudWatch에 metric을 전송하는 것을 고려했다. 하지만 웹소켓 전송마다 매번 CloudWatch의 putMetricData를 호출하면 비용이 상당히 많이 발생했다. CloudWatch 대신 StatsD나 Redis의 TimeSeries도 고려해보았지만 익숙하지 않은 도구인데다가 유지관리에도 적지 않은 리소스가 들어갈 것 같아서 곧 머리속에서 지웠다.
원래 기획대로 CloudWatch metric을 사용하되 1분에 한 번씩만 메트릭을 전송해서 최소한의 비용만 사용하기로 했다. 각 서버에서 전역변수로 AtomicFixnum 타입의 0을 설정하고, 웹소켓이 전송될 때마다 1씩 더했다. 그리고 1분에 한 번씩 CloudWatch에 전역변수 값을 전송하고 전역변수를 다시 0으로 설정했다.
이론상 완벽한 계획이었는데, 웹소켓을 전송해도 50% 정도의 확률로 전역변수가 증가하지 않았다. 로그도 남겨보고 검색도 하면서 멀티 프로세스가 범인이라는 것을 알아냈다. 당시 Rails 서버를 2개의 프로세스로 돌리고 있어서 프로세스 간에 전역변수가 공유가 안되었던 것이다. CloudWatch에 프로세스 별로 지표를 전송하는 방법을 찾아보았는데, Rails Instrumentation 내에서 process id를 찾을 방법이 없어서 포기했다.
결론적으로 웹소켓 전송 개수를 파일로 관리하기로 결정했다. 웹소켓이 전송될 때마다 로그 파일에 기록을 남기고, 1분마다 로그 파일의 줄 수를 세서 CloudWatch로 전송한 뒤 로그 파일을 삭제했다.
연결 개수를 수집할 때도 멀티 프로세스 문제에 맞닥뜨렸다. 1분마다 `ActionCable.server.connections.count`라는 메서드를 사용해서 연결 개수를 측정하려고 했는데, 한 프로세스의 값만 가져올 수 있었다. 그래도 다행히 연결 개수 측정하는 쪽에서는 process id를 가져올 수 있어서 CloudWatch metric의 dimension에 process id를 넣어서 전송했다.
몇 시간만에 끝날 줄 알았던 작업이었다. 그런데 비용을 고려하고 멀티프로세스라는 함정에 걸려서 며칠이 소요되었다. 그래도 문제를 해결하는 과정은 항상 재미있는 것 같다.