[문제해결] MemcachedClient getClient Exception : java.lang.IllegalStateException: Shutdown in progress 2015-10-15

1. 문제상황

> (Was(Tomcat) * 2) + DB(Mysql)*2 + memcached*1 구성의 게임서버환경에서 서비스가 정지되어 버리는 현상이 발생하였다. 접속시간대 로그는 다음과 같이 Xmemcached가 멈췄다는 에러를 출력하고 있었다. 그리고 이상한건 WAS가 죽어있었다.

ERROR ({http-bio-80-exec-40} MemcachedConnection.java[getClient]:252) [2015-10-14 18:49:45,948] - ==> MemcachedClient getClient Exception : java.lang.IllegalStateException: Shutdown in progress
...
Exception : net.rubyeye.xmemcached.exception.MemcachedException: Xmemcached is stopped

급한대로 WAS를 시작하여 서비스를 재개하였다. 다음날 똑같은 피크타임에 다시 서비스가 정지 되었다. 로그에 보여지는 에러는 다음과 같이 어제와 달랐다.

Exception : net.rubyeye.xmemcached.exception.MemcachedException: There is no available connection at this moment
Exception : java.util.concurrent.TimeoutException: Timed out(5000 milliseconds) waiting for operation while connected to 000.000.000.000:11211

2. 문제확인

> 첫째날은 커넥션이 실패 하였고 둘째날은 가능한 커넥션이 없으며 타임아웃이 발생하였다. 다음과 같은 부분을 의심해 보았다.

* 부하가 몰리는 시간에 타임아웃이 발생한 것으로 보아 서버 1대로 충분하지 않다.
  ==> CBT 기간이었으므로 부족한건 무리가 있어보인다.
* 너무 무분별한 캐시로딩을 시도 했다.
  ==> for문에서 계속 캐시로딩을 시도한부분 발견
* 자바에서 사용하는 Xmemcached 버그이다. (구글링)
  ==> 첫날 WAS가 멈춘 것은 Xmemcached의 버그인것으로 보인다. 로그를 남기지 않았기에 정확하지는 않다.
* 사용자가 너무 몰려 Memcached에 설정된 MAX_CONNECTION을 넘었다.
  ==> 회원 1명당 친구가 최대 50인데 이것을 건건이 조회 했다면 넘을 수도 있다고 판단된다.

3. 문제해결

> 이러한 문제에 대하여 여러가지 대처 사항을 적용하였다.

* 가장 현명한 것은 무분별한 Call을 프로그램 상에서 막는 것으로 보인다.
* 프로그램 상에서 힘들다면 서버를 늘려 부하를 분산시킨다.
* 서버의 스팩을 고려하여 최대 접속 수를 늘려준다.
* 정확한 문제 파악을 위하여 memcached 로그 설정 옵션 (파일크기가 너무 커질 수 있음에 주의하자)
  ==> OPTIONS="-vv >> /var/log/memcached 2>&1"
* 피크타임때 MEMCACHED 상태를 수시로 확인한다. (다음 내용을 파일에 받아 tail로 확인)
  ==> /usr/bin/memcached-tool 127.0.0.1:11211 stats

[memcached] 2. 활용로직 2014-10-12

> 웹 어플리케이션 서버는 일차로 메모리에 캐싱되어있는지 확인한다. 데이터가 있다면 디비서버에 접근하지 않고 그대로 사용하며 없다면 접근하여 데이터를 읽어와 캐시서버에 저장한 후 사용한다. 이렇게 되면 데이터의 수정이 없는 이상 디비에 부하를 주지 않아도 된다.

memcached_system


> 개발로직을 CRUD 로 구분하여 살펴보면 다음과 같다.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
/**
Create
-- 별도처리가 필요없다.
*/
mysql.create(query);
/**
Read
-- 메모리캐시에 있는 데이터를 사용하되 없으면
   디비에서 읽어와 메모리캐시에 저장하고 사용한다.
*/
var data;
data = memcached.get(key);
if(data == null){
   data = mysql.select(query);
   memcached.set(data);
}
/**
Update
-- Read시 다시 생성되도록 삭제한다.
*/
mysql.update(query);
memcached.delete(key);
/**
Delete
-- 동시에 삭제한다.
*/
mysql.delete(query);
memcached.delete(key);
- memcached에는 테이블이 존재하지 않으며 key-value형태의 데이터셋만 존재한다. 그러므로 key string 구성시 prefix를 잘 구성하여야 한다.
- 데이터 모델링시 디비와 캐시를 동시에 관리할 수 있도록 많은 고려가 필요하다.

[memcached] 1. 기본개념 2014-10-10

> memcached는 고성능의 분산 메모리 객체 캐싱 시스템으로 데이터베이스의 부하를 완화시켜 동적 웹어플리케이션의 속도를 향상 시킬 수 있다. 웹어플리케이션의 임시 데이터 저장용 메모리라고 할 수 있다.

>>> 장점

시스템의 사용되지 않는 일부 메모리를 활용할 수 있어 남는 자원을 효율적으로 사용해 성능을 향상 시킬 수 있다.

memcached– 초창기의 캐시 시스템은 [그림1]과 같이 각노드가 완전히 독립적으로 운영되어 데이터를 조회 또는 저장시 어느서버를 이용할지 관리되어야 하며 용량의 제한으로 인해 비생산적이며 자원 낭비적인 시스템으로 구성되었다.

– memcached는 이러한 제약사항의 해결을 위한 메커니즘을 제공 하는데 [그림2]와 같이 consistent hash 알고리즘을 사용하여 물리적인 별도의 캐시서버를 로직상 하나의 서버로 보고 사용할 수 있도록 한다. 즉, 개발자는 서버의 대수와 상관없이 한개의 객체만을 활용하여 저장 및 조회하게 되므로 능률적이고 대용량의 캐시시스템을 갖게 되므로 활용도가 넓어질 수 있다. 또한 기업의 입장에서는 오래된 저사양 서버의 남는 메모리를 활용할 수 있게되 비용면에서 효율적일 수 있다.

>>> 단점

– 재부팅시 소멸되기 때문에 영구적인 저장용 시스템으로 활용할 수는 없으며 다시 데이터가 저장되는 기간동안은 디비에서 부하를 받아내야만 한다.

– 데이터 수정 및 삭제시 데이터베이스 & memcached에 동시에 진행되어야 하므로 개발 복잡도가 증가한다.

>>> 실무에서의 활용

– 기본적인 기능만을 제공하는 캐시서버 이기 때문에 일부에서는 redis와 비교해 기능이 부족하다고 지적하지만 활용하기에 따라 많은 범위에 활용될 수 있고 충분히 대량의 트래픽을 받아낼 수 있도록 구성할 수있다.