[문제해결] 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 로 구분하여 살펴보면 다음과 같다.

/**
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와 비교해 기능이 부족하다고 지적하지만 활용하기에 따라 많은 범위에 활용될 수 있고 충분히 대량의 트래픽을 받아낼 수 있도록 구성할 수있다.