업무 이야기, 장애편 (1)
0. 업무 환경
- 현재 근무 중인 회사는 서비스 회사이며, 회사 자체 IDC에 서비스 서버를 두고 고객사에 CLIENT JAVA 프로그램을 설치해 회사 서버와 TCP/IP, SOCKET으로 자료를 주고 받는다.
- CLIENT JAVA 프로그램 담당으로 주로 CLIENT 프로그램
개발/유지보수/고객사 장애 응대
등을 담당하고 있다. - 실제 고객사 환경은 엄청나게 다양하며, 간간히 SPARC, HP-UX 또는 kernel 버전이 jre 1.6도 안깔리는 구식 환경 등등 모두 처음 보는 환경에서 업무를 응대 했어야 했다.
- 근 몇달새 차세대 프로젝트가 마무리되어 배포되고 서비스가 런칭되어, CLIENT도 발맞춰 변경되었는데, 서비스 초기 극도의 불안정성과 버그 또는 장애가 발생하며, 수만은 장애를 응대하게 됐다.
- 장애를 분석하고, 고객사에게 해결책을 제시했던 것 또한 자산이기에 기록하기로 했다.
1. CLIENT JAVA 프로그램
Java Write Once, Run Anywhere
라는 슬로건으로 설명 되듯이, 하나의 소스코드로 리눅스, 윈도우 등 고객사 환경에 가리지 않고 실행 된다. 다만, 고객사의 Data Base는 CLIENT가 제공하지 않는데 여기가 포인트다. 다양한 고객사의 Data Base 환경 때문에 엄청나게 많은 장애가 이 부분에서 발생한다. 당장 지금 기억나는 것만 해도 Oracle의 물리적 저장 공간 제한 때문에 쿼리가 실패하고, 고객사가 테이블에 걸어논 트리거 때문에 CLIENT 프로그램의 쿼리가 같이 실패하고, MySQL의 특정 버전에서는 Auto_Increment가 테이블에 생성된 데이터에 맞춰 자동으로 보정되지 않아서 서버를 내렸다 올리면 Insert 쿼리가 실패하는 등 수많은 고객사 장애를 맞이했다.
어떤 작업을 진행하고 기록을 남기는 DB 작업이 실패했다 함은 과금, 통계 등 신뢰성에 금이 가는 것이기 때문에 고객사와의 수많은 실랑이와 줄다리기가 벌어졌다. 지금은 그 사이 어느 쯤에선가에서 Data Base는 고객사의 관리 영역 이므로 Data Base 장애가 CLIENT 프로그램 장애로 오염되는 것을 칼같이 찝어주고 분쟁점을 최소화 하고 있다. 다만, Data Base 장애 이외에 서비스가 새로 런칭하면서 새롭게 바뀐 CLIENT 프로그램 또한 초반에는 수많은 버그와 에러를 뿜어댓다. 이제 이 장애편을 포스팅 하면서 이때까지 경험한 장애를 기록하고자 한다.
2. SOCKET
입사 후 인계 받은 CLIENT JAVA 프로그램은 java.net 패키지의 레거시 소켓 함수를 사용하고 있었고, 예외 처리 및 이에 해당하는 로직은 모두 비즈니스 로직과 연계되어 있었다. 이 소켓 부분에서 깔끔하게 또는 시원하게 해결되는 장애가 아니라 쿰쿰한, 찝찝한 장애들이 많이 발생했는데, 대표적인 것으로 HEARBEAT 패킷이 전달 되었는데도, 특정 상황이 되면 소켓이 끊겨 클라이언트와 서버가 재접속 되는 상황이 발생했다.
소켓은 여러가지 옵션을 제공하는데, 자세한 내용은 소켓 옵션을 참조 바란다.
- HEART BEAT 패킷은 소켓이 제공하는 keepAlive 옵션 외에 실제 데이터를 클라이언트와 서버가 서로 약속한 양식에 맞춰 주고 받은뒤 소켓이 정상적으로 동작하는 것을 확인하는 작업이다.
- 30초마다 한번씩, 1분이 넘어가도 HEART BEAT 패킷이 오지 않으면, 서로 소켓을 끊기로 약속 되어 있다.
3. CLIENT 로그에는 HEART BEAT 패킷이 SEND가 정상적으로 진행 됐다는게 찍혀 있어요…
CLIENT 로그에는 HEART BEAT 패킷이 정상적으로 SEND 되었다는게 찍혀 있었다. 하지만, ACK 응답이 1초 내에 오는 일반적인 상황과는 달리 SEND 로그만 찍혀 있다. 누가 봐도 SERVER에서 안줬네 라는 말이 나오는 상황이지만, 실제 내용은 달랐다.
- 각각의 소켓은 RecvQueue와 SendQueue가 있으며 해당 큐의 사이즈는 Byte 기준으로 각각의 OS마다 디폴트 용량이 다르다.
- 클라이언트는 자신의 소켓 SendQueue에 Write 하면 OS단에서 전송하고, RecvQueue에서 수신받는 패킷에 대한 Read만 수행하면 된다.
- 각각 클라이언트와 서버의 RecvQueue SendQueue를 실시간으로 확인하고 싶으면 아래 명령어를 따라해보자.
CentOS 6.9 기준, IP는 확인하고 싶은 연결의 IP를 입력하면 된다.
watch -n 0.1 "ss -t | grep 192.168.0.1"
눈으로 모니터링을 하다보면, 주사기 같다는 생각을 하게 된다. 한쪽에서 주사기를 꾹 누르면 주사기 입구에서 내용물을 쭈욱 뱉어내듯이, SendQueue로 Write만 하면 반대편 RecvQueue로 전달되고 Read가 읽혀 RecvQueue에 있는 데이터를 빼줘야만, 서로 연결된 Send, Recv Queue가 원활히 순환한다.
여기서 문제는, 한쪽에서 Read 작업이 늦어지게 되면, 보내는쪽도 같이 데이터 순환이 밀리게 된다.
이 문제점 때문에 흔히 얘기하는 Peak 타임, 즉 서버 사용량이 많아 지는 시간대에는 Read 작업이 밀리게 되고, Client는 정상적으로 HEART BEAT 패킷을 Write 했지만, 서버에서는 밀림 현상 때문에 HEART BEAT 패킷을 받지 못해 소켓이 끊어진 것으로 판단해 연결을 끊어 버린 것이다.
4. 해결책
- Socket의 비즈니스 로직 의존도를 낮춰야 한다.
- Socket에서 Read한 데이터는 Queue를 사용해서 쌓아 둔다던가, I/O 속도가 빠른 DB 등을 캐쉬 형태로 사용한다던가, Socket과 비즈니스는 확실하게 분리 시켜야 된다.