2008. 12. 3. 15:33

memcached 서버에 저장된 캐시 키 목록 얻기

memcached 서버에 데이터를 저장하게 되면 키의 해쉬값을 얻어 서버에 저장한다. 따라서 서버로부터 캐시 키 목록을 얻는 것은 불가능하다..... 라고 생각했었는데, memcached 서버는 캐시 키 목록을 저장하고 있었다!!!

이전에 포스팅한 "Spring에서 memcached 사용하기 (2) - memcached Aspect -" 에서는 이러한 이유로 특정 조건에 캐시를 만료시키기 위해 키 목록을 JVM에 유지하도록 했었다. 이러한 방법은 실제 캐시 서버에 저장된 캐시 키 목록과 어플리케이션 서버(JVM)에 저장된 캐시 키와의 동기화 문제를 야기시킬 수 밖에 없다. 매번 캐시 서버로부터 캐시 키 목록을 얻을 수 있다면 네트워크 비용은 발생하겠지만 적어도 동기화 문제로 인해 오동작 하는 것은 막을 수 있을 것이다.

이제 memcached 서버로부터 캐시 키 목록을 얻는 방법을 알아보도록 하자.

기본적으로 MemCachedClient의 통계 데이터는 Map<String, Map> 으로 리턴된다. 이 때 Key는 캐시 서버 주소이고, Map은 서버 주소에 대한 통계 데이터인 Map (보통 Map<String, String>) 객체이다.

statsItems() 메소드가 리턴하는 통계 데이터의 키 값은 "itemname:number:field" 형식으로 리턴된다. 여기서 가운데 요소인 number가 중요한데 이 숫자가 바로 slabNumber 이다. slabNumber는 memcached 에서 내부적으로 메모리를 관리하기 위한 시스템 상의 chunk 번호라고 생각하면 된다. 즉, memcached는 메모리를 일정 크기의 단위로 쪼개서 관리/사용 하는데 이 단위가 slab 이다.

이 slab number를 알고 있으면 statsCacheDump() 메소드를 통해 해당 slab number의 캐시 덤프를 얻을 수 있다. 바로 이 덤프에 키 목록이 존재한다.

즉, 정리하자면 statsItems() 메소드를 호출하게 되면 캐시에 저장되어 있는 데이터에 대한 정보를 얻을 수 있는데 여기서 slab number를 알아낼 수 있고, 이 slab number에 해당하는 캐시 덤프를 statsCacheDump() 메소드를 호출하여 얻어내면 그 안에서 캐시 키 목록을 찾을 수 있는 것이다.


아래는 위와 같은 memcached 서버로부터 캐시 키 목록을 얻어내는 KeyPool 클래스이다.

import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.danga.MemCached.MemCachedClient;

/**
 * 키 관리. MEMCACHED 서버에 저장되어 있는 키 목록을 읽어온다. (별도의 키 목록을 작성하여 캐시에 저장하지 않는다.)
 * MEMCACHED 가 제공하는 통계 정보를 사용하여 키 목록을 얻는다.
 *
 * @author jhcho
 * @version 2008. 12. 02
 */
@SuppressWarnings("serial")
public class AdvancedMemCachedKeyPool implements CacheKeyPool {
    private MemCachedClient mcc;

    public void setMemCachedClient(MemCachedClient mcc) {
        this.mcc = mcc;
    }

    @SuppressWarnings("unchecked")
    private List<String> getKeyList() throws Exception {
        List<String> list = new ArrayList<String>();

        // 통계 정보 얻기
        Map statsItems = mcc.statsItems();
        for (Object server : statsItems.keySet()) {
            String serverName = server.toString();

            // 서버에 저장된 데이터들
            List<Integer> slabNumberList = new ArrayList<Integer>();
            Map items = (Map) statsItems.get(server);
            for (Object key : items.keySet()) {
                String[] keyArr = key.toString().split(":"); // itemname:number:field
                if (keyArr[2].equals("number")) {
                    Integer slabNumber = new Integer(keyArr[1]);
                    if (!slabNumberList.contains(slabNumber)) {
                        slabNumberList.add(slabNumber);
                    }
                }
            }

            // 키 목록 얻기
            String[] serverArr = new String[] { serverName };
            for (Integer slabNumber : slabNumberList) {
                Map statsCacheDump = mcc.statsCacheDump(serverArr, slabNumber,
                        Integer.MAX_VALUE);
                Map keyMap = (Map) statsCacheDump.get(serverName);
                for (Object key : keyMap.keySet()) {
                    list.add(URLDecoder.decode(key.toString(), "UTF-8")); // default encoding
                }
            }
        }

        return list;
    }

    public boolean contains(String key) {
        try {
            return getKeyList().contains(key);
        } catch (Exception ex) {
            return false;
        }
    }

    public void add(String key) {
        // DO NOTHING
        // 캐시 서버에 데이터를 저장하면 MEMCACHED에 자동으로 키가 저장되므로 별도의 추가 처리가 필요 없다.
    }

    public void remove(String key) {
        // DO NOTHING
        // 캐시 서버에서 데이터가 삭제되면 MEMCACHED에 자동으로 키가 삭제되므로 별도의 추가 처리가 필요 없다.
    }

    public void remove(String[] keys) {
        // DO NOTHING
        // 캐시 서버에서 데이터가 삭제되면 MEMCACHED에 자동으로 키가 삭제되므로 별도의 추가 처리가 필요 없다.
    }

    public String[] list() {
        try {
            return getKeyList().toArray(new String[0]);
        } catch (Exception ex) {
            return new String[0];
        }
    }

}

위에서 눈여겨봐야 할 메소드는 getKeyList() 메소드이다. 여기서는 MemCachedClient의 statsItem() 메소드와 statsCacheDump() 메소드를 사용해 캐시 서버로부터 키 목록을 얻어오고 있다.


Trackback 0 Comment 0