Memcached 설치 및 사용 방법 (2012-09-13)


목차

개요

  • memcached 는 이름 그대로 메모리를 사용해 캐시서비스를 제공해주는 데몬이다. memcache 라는 이름의 라이브러리가 많은데, 여기서 설명하는 건 '데몬'이다.
  • memcached 는 LiveJournal을 개발한 Brad Fitzpatrick 이 개발했다. 유투브나 아마존, 위키피디아, 소스포지, 트위터 등 많은 대형 사이트에서 사용되고 있는 데몬이다.
  • memcached 운용시 시스템의 메모리 상황를 모니터링 해, 스왑과 락이 일어나지 않도록 해야한다. memcached는 스왑이 일어나지 않을 정도로 메모리는 여유있다는 가정하에 개발되었다고 한다. 메모리가 부족해 스왑이 일어나 메모리에 저장했던 컨텐츠가 스왑 디스크에 저장되면, 심각한 속도 저하가 있을 것이다.
  • memcached 는 메모리에 데이터를 캐시하는 데몬이며, 아래와 같은 사용은 지양해야 한다.
    • 파일의 캐시로 사용하지 말 것. 단순한 파일 캐시는 OS상에서 제공해준다. memcached는 파일을 읽고 가공한 것을 캐시해야 한다.
    • 데이터베이스 쿼리 결과의 캐시로 사용하지 말 것. 위와 비슷한 이유다. 요즘 나오는 대부분의 상용 DBMS는 쿼리결과를 캐시해주기 때문에 이중으로 캐시할 필요 없다.
  • memcached - a distributed memory object caching system

설치

  • libevent

    memcached 설치에는 libevent 라이브러리가 필요하다. 이 라이브러리는 이벤트(예를 들어 시그널이나 타입아웃)가 발생했을 때 콜백 함수를 쉽게 호출할 수 있도록 도와주는 라이브러리다.
    libevent
    root@wl ~/src # wget --no-check-certificate https://github.com/downloads/libevent/libevent/libevent-2.0.20-stable.tar.gz
    root@wl ~/src # tar xvfz libevent-2.0.20-stable.tar.gz
    root@wl ~/src # cd libevent-2.0.20-stable
    root@wl ~/src/libevent-2.0.20-stable # ./configure CFLAGS="-I/usr/local/ssl/include" CPPFLAGS="-I/usr/local/ssl/include"
    root@wl ~/src/libevent-2.0.20-stable # make
    root@wl ~/src/libevent-2.0.20-stable # make install
    
  • memcached

    root@wl ~/src # wget http://memcached.googlecode.com/files/memcached-1.4.15.tar.gz
    root@wl ~/src # tar xvfz memcached-1.4.15.tar.gz
    root@wl ~/src # cd memcached-1.4.15
    root@wl ~/src/memcached-1.4.15 # ./configure --enable-dtrace
    root@wl ~/src/memcached-1.4.15 # make
    root@wl ~/src/memcached-1.4.15 # make install
    
  • libMemcached

    • memcached 는 TCP로 데몬에 접속해 메시지를 주고 받는 방법으로 사용한다. 따라서 라이브러리를 제공하지 않고 memcached 프로토콜 스펙만 제공해준다. 이를 래핑해 사용하기 쉽도록 만든 라이브러리가 libmemcached 이다.
    • libMemcached 는 현재 Solaris Studio 12.3 으로 컴파일 되지 않는다. (#pragma once 미지원) 솔라리스에 번들된 GCC컴파일러로도 안된다. 필자의 경우 GCC 4.6.2 를 사용했다. GCC 4.6.2를 설치하려면 윈디하나의 솔라나라: GCC (작성중)를 참고하자. 이 문서의 내용을 진행함에 있어 libMemcached 라이브러리는 컴파일하지 않아도 된다.
    • libMemcached
    root@wl ~/src # wget --no-check-certificate https://launchpad.net/libmemcached/1.0/1.0.10/+download/libmemcached-1.0.10.tar.gz
    root@wl ~/src # tar xvfz libmemcached-1.0.10.tar.gz
    root@wl ~/src # cd libmemcached-1.0.10
    root@wl ~/src/libmemcached-1.0.10 # ./configure --with-memcached --disable-64bit --enable-umem
    ...
    ---
    Configuration summary for libmemcached version 1.0.5
    
       * Installation prefix:       /usr/local
       * System type:               pc-solaris2.10
       * Host CPU:                  i386
       * C Compiler:                gcc (GCC) 4.6.2
       * Assertions enabled:        yes
       * Debug enabled:             no
       * Warnings as failure:       no
    
    ---
    root@wl ~/src/libmemcached-1.0.5 # /usr/sfw/bin/gmake
    root@wl ~/src/libmemcached-1.0.5 # /usr/sfw/bin/gmake install
    

실행

  • memcached 설정 및 실행

    • 데몬을 띄울 포트번호와 사용할 최대 메모리 양을 정한다. 기본 포트는 11211 번이며, 사용할 최대 메모리는 64MB이 기본 값이다.
    • memcached 는 별도의 설정 파일을 제공하지 않는다.
    root@wl ~ # memcached -h 1)
    memcached 1.4.15
    ...
    -l <addr>     바인드할 주소
    -p <num>      TCP 포트 번호 (기본값: 11211)
    -U <num>      UDP 포트 번호 (기본값: 11211, 0 인경우 사용안함)
    -s <file>     UNIX 소켓 경로 (네트워크 지원 안함)
    -d            데몬으로 실행
    -u <username> 전환할 사용자 이름(루트로 실행시)
    -m <num>      최대 메모리(MB단위, 기본값: 64)
    -M            데이터 저장시 메모리가 부족할 경우 오류를 반환(기본값은 오래된 데이터를 삭제)
    -c <num>      최대 접속 개수 (기본값: 1024)
    -P <file>     PID 파일 저장 위치. -d 옵션 사용시 사용
    -f <factor>   증가 팩터값. (기본값: 1.25)
    -n <bytes>    키+값+플래그를 저장할 최소 단위(기본값: 48)
    -L            large memory pages 사용(가능한경우)
    -t <num>      사용할 쓰레드 개수 (기본값: 4)
    -v            로그 보임
    -vv           자세한 로그 보임
    -vvv          매우 자세한 로그 보임
    ...
    
    1) 도움말. 중요하다고 생각되는건 번역했다. 대형 사이트의 경우 도움말을 참조해 메모리관련 옵션 최적화를 반드시 수행해야 한다.

    아래 커맨드는 windy 사용자의 권한을 사용하고, 64MB 메모리를 사용하고, 큰 페이지를 사용하며, 127.0.0.1만 바인드(외부 접속 불가)하는 데몬을 띄운다. PID 파일은 /tmp/memcached.pid 에 있다.
    root@wl ~ # memcached -u windy -d -m 64 -l 127.0.0.1 -p 11211 -L -P /tmp/memcached.pid
    
    memcached 는 kill 을 사용해 종료하면 된다.
    root@wl ~ # kill `cat /tmp/memcached.pid`
    
  • memcached 프로토콜 예제

    memcached 의 프로토콜은 두가지다. 바이너리 프로토콜과 아스키 프로토콜이 그것인데, 아스키 프로토콜은 telnet 등을 사용해 아래와 같이 이용할 수 있다. 부하가 많이 걸리는 곳에서는 바이너리 프로토콜이 아스키 프로토콜에 비해 성능이 '약간' 좋은 것으로 알려져있다.
    root@wl ~ # telnet localhost 11211 
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    stats settings 1)
    STAT maxbytes 67108864
    STAT maxconns 1024
    STAT tcpport 11211
    STAT udpport 11211
    STAT inter NULL
    STAT verbosity 0
    STAT oldest 0
    STAT evictions on
    STAT domain_socket NULL
    STAT umask 700
    STAT growth_factor 1.25
    STAT chunk_size 48
    STAT num_threads 4
    STAT num_threads_per_udp 4
    STAT stat_key_prefix :
    STAT detail_enabled no
    STAT reqs_per_event 20
    STAT cas_enabled yes
    STAT tcp_backlog 1024
    STAT binding_protocol auto-negotiate
    STAT auth_enabled_sasl no
    STAT item_size_max 1048576
    STAT maxconns_fast no
    STAT hashpower_init 0
    END
    set mykey 0 100 2
    hi 2)
    STORED
    get mykey 3)
    VALUE mykey 0 2
    hi
    END
    Ctrl+]
    telnet> Ctrl+D
    Connection to localhost closed.
    root@wl ~ # 
    
    1) 세팅 확인
    2) 데이터 저장
    3) 데이터 추출
  • 기본 튜닝

    TCP 스펙상 접속을 끊은 후 TIME_WAIT 대기 시간이 발생한다. 30~240초로 설정할 수 있는데, 대부분의 운영체제에서는 240초가 기본값이다. 하지만 이 값은 memcached 를 운용하는 환경에서는 너무 길다. 트래픽이 많은 곳에서 memcached 와 빈번하게 통신하다 보면, TIME_WAIT가 수만개 이상 발생하는 경우가 있고, 이로인해 memcached 에 접속이 안되는 현상이 발생하는 경우가 있기 때문이다. (어느 정도까지 문제 없는지는 운영체제마다, 설정마다 다르다) 따라서 각 운영체제 마다 이를 줄여주는 옵션이 있다. memcached 를 실행한 서버에서 아래와 같이 30초로 세팅한다. 참고로 TCP표준은 최소 60초 이상을 권장한다.
    • 솔라리스
      root@wl ~ # ndd -set /dev/tcp tcp_time_wait_interval 30000
      root@wl ~ # export EVENT_NOEVPORT=1
      
    • 리눅스
      root@wll ~ # vi /etc/sysctl.conf 
      net.ipv4.ip_local_port_range = 16384 65534
      net.ipv4.tcp_fin_timeout = 30
      net.ipv4.tcp_tw_reuse = 1
      root@wll ~ # sysctl -p
      
    • 윈도
      C:\>edit tcptimewait.reg
      [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
      "TcpTimedWaitDelay"=dword:0000001e
      C:\>regedit /S tcptimewait.reg
      
  • memcached 와 DTrace

    현재 지원되는 프로바이더와 프로브는 아래와 같다. (아래 예제를 따라하기 전에 먼저 memcached 데몬을 실행해야 한다)
    root@wl ~ # dtrace -l -m memcached
       ID   PROVIDER            MODULE                          FUNCTION NAME
     2752 memcached16537         memcached                      assoc_delete assoc-delete
     2753 memcached16537         memcached                        assoc_find assoc-find
     2754 memcached16537         memcached                      assoc_insert assoc-insert
     2755 memcached16537         memcached               complete_update_bin command-add
     2756 memcached16537         memcached              complete_nread_ascii command-add
     2757 memcached16537         memcached               complete_update_bin command-append
     2758 memcached16537         memcached              complete_nread_ascii command-append
     2759 memcached16537         memcached              complete_nread_ascii command-cas
     2760 memcached16537         memcached                      do_add_delta command-decr
     2761 memcached16537         memcached            process_delete_command command-delete
     2762 memcached16537         memcached                process_bin_delete command-delete
     2763 memcached16537         memcached               process_get_command command-get
     2764 memcached16537         memcached                   process_bin_get command-get
     2765 memcached16537         memcached                      do_add_delta command-incr
     2766 memcached16537         memcached               complete_update_bin command-prepend
     2767 memcached16537         memcached              complete_nread_ascii command-prepend
     2768 memcached16537         memcached               complete_update_bin command-replace
     2769 memcached16537         memcached              complete_nread_ascii command-replace
     2770 memcached16537         memcached               complete_update_bin command-set
     2771 memcached16537         memcached              complete_nread_ascii command-set
     2772 memcached16537         memcached                 process_bin_touch command-touch
     2773 memcached16537         memcached                          conn_new conn-allocate
     2774 memcached16537         memcached                          conn_new conn-create
     2775 memcached16537         memcached                         conn_free conn-destroy
     2776 memcached16537         memcached                 dispatch_conn_new conn-dispatch
     2777 memcached16537         memcached                        conn_close conn-release
     2778 memcached16537         memcached                      do_item_link item-link
     2779 memcached16537         memcached                    do_item_remove item-remove
     2780 memcached16537         memcached                   do_item_replace item-replace
     2781 memcached16537         memcached             do_item_unlink_nolock item-unlink
     2782 memcached16537         memcached                    do_item_unlink item-unlink
     2783 memcached16537         memcached                    do_item_update item-update
     2784 memcached16537         memcached                    conn_set_state process-command-end
     2785 memcached16537         memcached                   process_command process-command-start
     2786 memcached16537         memcached              dispatch_bin_command process-command-start
     2787 memcached16537         memcached                    do_slabs_alloc slabs-allocate
     2788 memcached16537         memcached                    do_slabs_alloc slabs-allocate-failed
     2789 memcached16537         memcached                     do_slabs_free slabs-free
     2790 memcached16537         memcached                  do_slabs_newslab slabs-slabclass-allocate
     2791 memcached16537         memcached                  do_slabs_newslab slabs-slabclass-allocate-failed
    
    아래와 같이 memcached_watch.d 스크립트를 작성해 DTrace 를 실행하고 위의 프로토콜 예제를 다시 따라하면 [command-set fired]가 출력될 것이다. Ctrl+C를 눌러 종료한다.
    root@wl ~ # cat memcached_watch.d
    /* TO RUN: dtrace -s memcached_watch.d -p `pgrep -x memcached` -q */
    dtrace:::BEGIN
    {
            printf("Hit Ctrl-C to end.\n");
    }
    memcached*:::command-set
    {
            printf("command-set fired\n");
    }
    root@wl ~ # dtrace -s memcached_watch.d -p `pgrep -x memcached` -q
    Hit Ctrl-C to end.
    command-set fired
    ^C
    

JAVA/JSP

  • JAVA에서 memcached 를 사용하기 위한 라이브러리 중 spymemcached에 대해 소개한다.
  • 설치는 단순히 http://spymemcached.googlecode.com/files/spymemcached-2.8.4.jar 를 받아 CLASS PATH에 복사해놓으면 된다.
  • 홈페이지: spymemcached
memcache.java
 다운로드 (717 바이트)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package test;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
 
import net.spy.memcached.MemcachedClient;
 
/**
 * Memcached Sample.
 * WindyHana's Solanara - Memcached http://www.solanara.net/solanara/memcached
 * java test.MemcachedSample
 */
public class MemcachedSample {
    /**
     * 테스트 함수
     * @param args
     */
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        MemcachedClient c;
        try {
            c = new MemcachedClient(new InetSocketAddress("localhost", 11211));
            c.set("someKey", 3600, new HashMap<String, Object>());
            Object myObjeckt = c.get("someKey");
            c.delete("someKey2");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

PHP

PHP에서 memcached 를 사용하기 위한 모듈은 두가지를 제공한다. PECL:memcachePECL:memcached 가 그것인데, 두가지 모두 memcached 에 접속해 사용한다. 단지 PECL:memcache는 memcached 에 직접 접속하는 PHP 모듈이고, PECL:memcached 는 libMemcached 를 통해 memcached 에 접속하는 모듈이라는 점이 다르다. PHP에서의 사용 방법이나 지원 함수도 조금씩 다르므로 매뉴얼을 확인하자.
  • PECL:memcache 를 설치하기 위해서는 autoconf 가 필요하다. 윈디하나의 솔라나라: Autotools - autoconf를 참고해 autoconf 를 설치하자.
  • PECL:memcache 설치 및 설정

    root@wl ~/src # mkdir phpmemcache
    root@wl ~/src # cd phpmemcache
    root@wl ~/src/phpmemcache # /usr/local/php/bin/pecl download memcache
    root@wl ~/src/phpmemcache # tar xvf memcache-2.2.7.tgz
    root@wl ~/src/phpmemcache # cd memcache-2.2.7
    root@wl ~/src/phpmemcache/memcache-2.2.7 # /usr/local/php/bin/phpize
    root@wl ~/src/phpmemcache/memcache-2.2.7 # CFLAGS="-xc99" ./configure -with-php-config=/usr/local/php/bin/php-config
    root@wl ~/src/phpmemcache/memcache-2.2.7 # make
    root@wl ~/src/phpmemcache/memcache-2.2.7 # cp memcache.php /usr/local/php/lib/php
    root@wl ~/src/phpmemcache/memcache-2.2.7 # cp ./modules/memcache.so /usr/local/php/lib/php/extensions
    
    php.ini 를 수정해야 한다. 윈디하나의 솔라나라: SAMP의 [PHP 설정]를 참조해 extension_dir 을 설정하고 아래와 같이 extension 을 추가해야 한다.
    root@wl ~ # vi /usr/local/php/lib/php.ini
    extension=memcache.so
    
  • memcached 세션 설정

    PHP의 세션 핸들러로 memcached 를 설정할 수 있으며, 저장 경로로 두개 이상의 memcached 데몬을 사용할 수 있다. php 세션을 위한 memcached 의 키값은 [memc.sess.key] 형식으로 정해진다.
    root@wl ~ # vi /usr/local/php/lib/php.ini
    session.save_handler = memcache
    session.save_path = "127.0.0.1:11211"
    
  • PHP - memcache 테스트

    memcache.phps
     다운로드 (534 바이트)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?php session_start(); ?>
    <pre>
    <?php
    $presessdata = @$_SESSION["data"];
    $_SESSION["data"] = @$_SESSION["data"] + 1;
     
    $memcache = new Memcache;
    $memcache->connect("localhost", 11211);
    print_r($memcache->getStats());
     
    $items = array(
        'key1' => 'value1',
        'key2' => 'value2',
        'key3' => 'value3'
    );
    foreach ($items as $k => $v) {
        $memcache->set($k, $v);
    }
    var_dump($memcache->get(array('key1', 'key3')));
    var_dump($memcache->get('key2'));
    var_dump($memcache->get('key4'));
    ?>
    SESSION: <?php echo $_SESSION["data"]; ?>
    </pre>
    아래와 유사하게 출력되어야 한다.
    Array
    (
        [pid] => 16700
        [uptime] => 425530
        [time] => 1333412724
        [version] => 1.4.15
        [libevent] => 2.0.20-stable
        [pointer_size] => 32
        [rusage_user] => 6.387770
        [rusage_system] => 4.913250
        [curr_connections] => 5
        [total_connections] => 57
        [connection_structures] => 7
        [reserved_fds] => 20
        [cmd_get] => 148
        [cmd_set] => 45
        [cmd_flush] => 0
        [cmd_touch] => 0
        [get_hits] => 100
        [get_misses] => 48
        [delete_misses] => 6
        [delete_hits] => 0
        [incr_misses] => 0
        [incr_hits] => 0
        [decr_misses] => 0
        [decr_hits] => 0
        [cas_misses] => 0
        [cas_hits] => 0
        [cas_badval] => 0
        [touch_hits] => 0
        [touch_misses] => 0
        [auth_cmds] => 0
        [auth_errors] => 0
        [bytes_read] => 66307
        [bytes_written] => 36315
        [limit_maxbytes] => 67108864
        [accepting_conns] => 1
        [listen_disabled_num] => 0
        [threads] => 4
        [conn_yields] => 0
        [hash_power_level] => 16
        [hash_bytes] => 262144
        [hash_is_expanding] => 0
        [expired_unfetched] => 0
        [evicted_unfetched] => 0
        [bytes] => 27437
        [curr_items] => 20
        [total_items] => 45
        [evictions] => 0
        [reclaimed] => 0
    )
    array(2) {
      ["key1"]=>
      string(6) "value1"
      ["key3"]=>
      string(6) "value3"
    }
    string(6) "value2"
    bool(false)
    SESSION: 1
  • igbinary

    • php serializer중 하나다. PHP의 표준 serializer 보다 메모리를 덜 소비하고, 성능도 더 좋다. libmemcached 와 PECL:memcached 를 설치해야 memcached 와 함께 사용할 수 있다.
    • PECL: igbinary
    root@wl ~/src # /usr/local/php/bin/pecl download igbinary
    root@wl ~/src # tar xvfz igbinary-1.1.1.tgz
    root@wl ~/src # cd igbinary-1.1.1
    root@wl ~/src/igbinary-1.1.1 # /usr/local/php/bin/phpize
    root@wl ~/src/igbinary-1.1.1 # CFLAGS="-xc99" ./configure --with-php-config=/usr/local/php/bin/php-config
    root@wl ~/src/igbinary-1.1.1 # make
    root@wl ~/src/igbinary-1.1.1 # cp ./modules/igbinary.so /usr/local/php/lib/php/extensions
    root@wl ~/src/igbinary-1.1.1 # vi /usr/local/php/lib/php.ini
    extension=igbinary.so
    session.serialize_handler=igbinary
    ;apc.serializer=igbinary


출처 - http://www.solanara.net/solanara/memcached




'Computer Science > Cache' 카테고리의 다른 글

EHCache  (0) 2013.02.11
Hazelcast 소개  (0) 2012.11.06
Memcached의 확장성 개선  (0) 2012.11.06
memcached를 적용하여 사이트 성능 향상  (0) 2012.11.06
Memcached 구현 소개  (0) 2012.11.06
Posted by linuxism
,


Memcached의 확장성 개선 



이 글은 INTEL사에서 작성한 Enhancing the Scalability of Memcached를 번역한 기사입니다. 본 내용은 2012년도 9월17일(월)에 개최되는 개발자 컨퍼런스 DEVEIW 2012에서 원문과 동일한 제목의 세션으로 발표가 진행될 예정입니다. hello world 독자분들과 DEVIEW 참석자분들을 위한 참고자료로 기고되었으며 PDF로도 다운받으실 수 있습니다.

Memcached의 확장성 개선.pdf



1    Memcached 및 웹 서비스 소개

Memcached는 Facebook[1], Twitter[2], Reddit[3] 및 YouTube[4]와 같은 클라우드 및 웹 서비스 제공 회사에서 사용하는 key-value 메모리 캐시로, 웹 데이터를 소비자에게 서비스하는 데 있어 지연 시간을 줄이고 데이터베이스 및 컴퓨팅 서버[5]에 대한 증설을 줄여주게 한다. Latency를 줄이는 것 외에도 memcached의 확장성 있는 아키텍처 (scale-out) 는 memcached 서버를 간단하게 추가만 하여 처리량을 높일 수 있다. 그러나 코어 수가 4개를 넘으면 성능 저하가 발생하기 때문에 수직 scalability (scale-up) 에는 문제가 있다[1][6].

이 연구에서는 왕복 시간 (RTT, Round-Trip Time)에 대한 1밀리초 이하의 지연에 대한 SLA (Service Level Agreement)를 유지하면서 concurrent data structure, 새로운 캐시 교체 알고리즘 및 네트워크 최적화를 활용하여 Intel® Xeon® E5 서버 프로세서에서 현 버전(v1.6)의 오픈 소스와 비교했을 때 6배 이상 처리량을 높이는 memcached 최적화 방법을 소개한다. 그림 1은 v1.6 memcached 베이스라인에 비해 6배 빠른 속도의 최적화된 버전을 보여준다. 여기서는 32개의 논리적 코어에서 최적화된 버전이 초당 3억 1천 5백 만 처리량 (RPS)을 제공하며, 1밀리초의 절반 수준의 RTT를 보인다.

 

110359feab8e3e3490e287abc13a3568.png

그림 1 기본 오픈 소스 코드 버전 1.6.0_beta1 및 최적화된 버전의 구성을 사용하여 측정한 최대 처리량 비교

 

Memcached는 key-value 쌍으로 이뤄진 간단한 데이터 타입을 저장하며, NoSQL 데이터베이스와 유사하지만 NoSQL처럼 영구적 (persistent) 이지는 않다. Memcached는 모든 key-value 쌍을 메모리에 저장하므로 서버장애나 오류가 발생했을 때 저장된 데이터가 모두 손실된다. 이 글에서는 키 (key)와 해당 키의 값(value)을 지칭할 때 '캐시 항목 (cache item)' 이라는 용어를 사용한다. 이때 키는 고유한 값이다. 웹 서비스 아키텍처에서 memcached 애플리케이션은 그림 2에서 보는 바와 같이 프론트엔드 (Front-end) 웹 서버 (또는 계층 (tier))와 백엔드 (Back-end) 데이터베이스 사이에 위치한다.

 

41cf0e92e34a5b5cc714e9e1d138b696.png

그림 2 프론트엔드 웹 계층 및 백엔드 데이터베이스 사이에 위치하는 memcached 서버

 

Memcached의 용도는 데이터 요청을 가로채어 가능한 경우 이를 캐시 (시스템 메모리)에서 직접 서비스 하게 만들고, 백엔드 데이터베이스에 연결된 디스크 스토리지 access를 줄이는 것이 목적이다. 그리고 미리 계산된 값을 캐시에서 저장하고 조회하게 하여, 요청 때 마다 많은 양의 계산을 피할 수도 있다. 두 경우 모두 조회 또는 data 결과를 계산하는 시간이 줄어들게 된다. Memcached 논리적 캐시에는 서버군들로 구성이 되어, 각 서버는 전체 논리적 캐시의 일부분으로 시스템 메모리를 제공한다 [7][8][9]. 예를 들어, 각 노드에 128GB의 메모리 공간이 있는 10개 노드로 구성된 memcached 클러스터는 전채적으로 1.28TB의 논리적 캐시를 제공한다. 논리적 캐시는 웹 서비스를 위한 영구 데이터 저장소인 백엔드 데이터베이스 또는 컴퓨팅 서버의 query/계산 결과 데이터로 채워진다. 캐시 항목은 LRU (Least Recently Used) 정책과 TTL (Time To Live)을 사용하여 유지 관리된다. 항목이 제거되면 해당 슬롯은 최신 항목으로 채워진다.

웹 서비스에서 하나의 요청 (http request) 은 memcached, 데이터베이스 및 기타 서비스로의 여러 번의 요청이 필요할 수 있다. 효율적인 캐싱 전략을 사용하면 memcached에 대한 조회 횟수는 데이터베이스 조회 횟수보다 몇 십 배 많을 수 있다 (DB 조회를 줄임). Memcached의 시스템 메모리에서 데이터 조회는 같은 데이터를 데이터베이스에서 조회하는 것보다 열 배 이상 빠르며 (마이크로 초 대 밀리 초 단위), 대부분의 경우 데이터를 on-demand로 계산/조회 하는 것보다 몇 십 배 이상 빠르다. 따라서 웹 서비스 요청에 대해 빠른 사용자 응답 시간을 제공하기 위해서는 데이터베이스 조회 및 on-demand 계산은 피해야 한다.

1.1    용어

이 글에서는 다음과 같은 용어를 사용한다.

LRU (Least Recently Used) – memcached에서 캐시 항목 추가를 위한 공간을 확보하기 위해 캐시에서 필요 없는 항목을 결정 eviction 알고리즘. 가장 오래된 항목들을 evict하는 알고리즘.

Base – 수정되지 않은 기본 버전(1.6.beta_0)의 memcached이며, memcached.org에서 직접 다운로드 가능.

Bags – 성능 향상을 위해 최적화된 버전의 memcached 약어. 'Bags'은 'Bag LRU'라는 수정된 LRU 캐시 업데이트 전략이고, 개발된 코드의 첫 번째 섹션이다.

RTT (Round Trip Time) – memcached 요청이 경과된 시간으로, 클라이언트로 응답이 올 때까지 클라이언트에서 서버로 가는 요청 경과 시간 포함.

SLA (Service Level Agreement) – 사용자에 대한 수용할 수 있는 웹 서비스 요청 응답 시간을 유지하기 위해 허용되는 최대 RTT. 응답 시간이 SLA를 넘어서는 경우 웹 서비스 요청을 생성하는 데 수용할 수 없는 응답 시간이 나타난다. (프런트엔드 웹 티어로 전송되는) 웹 서비스 요청당 (memcached논리적 캐시로 전송되는) 여러 memcached 요청이 있을 수 있다. 이 글에서는 프런트엔드 웹 계층 클라이언트 요청에 대한 memcached SLA 응답 시간은 1밀리초이다.

NIC (Network Interface Card) – 시스템에 사용된 네트워크 카드.

2    memcached 아키텍처

2003년 memcached가 도입될 당시에는[5] 여러 코어가 있는 x86 프로세서가 매우 적었으며 멀티 소켓 서버의 가격이 매우 비쌌다. Intel® Xeon® 듀얼코어는 2005년, 쿼드코어 프로세스는 2009년까지는 출시되지 않았다. 오늘날, 듀얼 소켓, 8코어 및 16코어 서버 시스템은 웹 서비스를 위한 구성 요소이며, 프로세서 개발 로드맵에 따르면 코어 개수의 증가는 계속 될 것으로 보인다[10]. 이러한 첨단 시스템은 여러가지 워크로드에서 여러 스레드 또는 프로세스를 사용하여 동시에 실행할 수있게 되어 있으나, memcached는 현재의 데이터 구조 및 소프트웨어 아키텍처 때문에 아직 그것이 불가능하다 [1][6].

2.1    데이터 구조

memcached는 다음과 같은 4가지 주요 데이터 구조가 있다.

해쉬 테이블 데이터 구조는 bucket 배열이다. 배열 크기 (k) 는 항상 2의 거듭제곱이고 '2k-1'의 값을 구해 hash 마스크로 사용한다. Bit-wise AND (예: hash_value & hash_mask) 계산으로 hash 값이 들어 있는 bucket을 신속하게 찾아낸다. bucket들은 NULL로 끝나는 캐시 항목의 linked-list이다. 그림 3는 이러한 데이터 구조를 보여준다.

 

1b2f7e87c8ac423b9a23756721082e93.png

그림 3 캐시 항목 조회에 사용되는 해쉬 테이블 데이터 구조

 

제거 (eviction) 순서를 결정하는 데 사용되는 LRU는 단일 크기의 슬랩 (아래 설명된 메모리 할당 단위) 마다 존재하며, 그 슬랩의 모든 캐시 항목들을 접근 순서대로 유지하는 double linked-list이다. Double linked-list를 형성하기 위한 포인터들은 각 캐시 항목 구조에 유지되며, 캐시 항목이 LRU에서 위치 조정될 때마다 수정된다. LRU list에서 어떤 캐시 항목을 제거 (eviction) 하는 경우 이 list의 tail에 있는 캐시 항목부터 검사하여 메모리 재사용이 가능한 가장 오래된 캐시 항목을 찾아낸다. 그림 4는 LRU 데이터 구조를 보여준다.

 

5b16f2e681a6f2a0a01050e1470669d0.png

그림 4 캐시 항목 제거 순서를 결정하는 데 사용되는 현 버전의 오픈 소스 LRU 데이터 구조

 

캐시 항목 데이터 구조에 key/value 쌍 데이터가 들어있고, 또 다음과 같은 metadata 들이 들어있다.

슬랩 할당자는 캐시 항목에 대한 메모리 관리 기능을 제공한다. 캐시 항목은 상대적으로 크기가 작아서, 시스템 call (malloc/free)을 사용한 작은 메모리 청크 (chunk)의 할당 및 해제는 속도가 느리고 스래싱 (thrashing)이 발생할 가능성이 높다. 그래서 memcached는 이 방법 대신 메모리 할당 단위로 슬랩을 사용한다. 슬랩은 내부에 많은 항목들을 포함할 수 있는 큰 메모리 chunk이다. 예를 들어 1,024 byte 메모리 chunk의 슬랩은 64바이트 이하 캐시 항목을 16개까지 저장 가능하다. 슬랩 할당자는 이렇게 큰 메모리를 할당하고 free list를 유지한다. 캐시 항목이 접근 될 때마다 슬랩 할당자는 저장할 값 크기를 확인하고 수용할 수 있을 만큼 큰 슬랩내의 캐시 항목을 돌려준다. 어떤 경우에는 이 방법이 공간을 비효율적으로 사용하기도 하지만 성능이 좋고 메모리 스래싱을 피할 수 있다.

2.2    명령어

Memcached 서버는 3가지 기본 명령어를 지원한다.

또한 통계 (stats), 교체 (replacements), 연산 (arithmetic), 플러시 (flush) 및 업데이트 (updates)를 비롯한 15가지의 기타 명령들이 있다. 이런 명령어들의 코드 경로는 일반적으로 위 기본 명령의 순서를 따른다. 예를 들어, 교체 (replacement)는 먼저 캐시 항목을 삭제 (DELETE)한 후 새로운 캐시 항목을 해쉬 테이블 및 LRU에 저장 (STORE) 한다. GET 명령은 memcached 여러 명령 중 workload에서 가장 많이 사용되는 명령이어서 여기서는 GET 명령을 중점적으로 다룬다[11].

3가지 기본 명령어 (GET, STORE, DELETE)에 대한 memcached에서의 실행 흐름은 다음과 같다.

1. NIC에 요청 packet이 도착하고 libevent에 의해 처리

2. worker thread

3. Hash 값은 키 데이터에서 생성

4. 해쉬 테이블과 LRU 처리를 위해 global cache lock 획득 (Critical Section 시작)

5. Global cache lock 해제 (Critical Section 끝)

6. 응답 구성

7. (GET만 해당) global cache lock 확인 (assert)

8. 요청한 클라이언트 (프론트엔드 웹 서버)로 응답 전송

2.3    프로세스 흐름

그림 5에 데이터 요청 처리 시 cache cluster 관점에서 memcached 프로세스 흐름이 설명되어있다. 여러 서버가 함께 작동하여 더욱 큰 단일 논리적 데이터 캐시 역할을 수행한다. 기본적으로 이러한 서버는 대규모 DHT (distributed hash table: 분산 해쉬테이블)을 구성한다. 그림 5를 찬찬히 살펴보면 클라이언트 (일반적으로 사용자를 위한 응답을 구성하는 웹 tier 서버 형태나 계산용 데이터가 필요한 컴퓨팅 서버 형태) 가 memcached로의 요청을 담당하는 것을 알 수 있다. Memcached가 시작되면 이러한 요청을 처리하기 위해 일반적으로 서버의 프로세서 (core) 개수에 맞게 일정한 수의 worker thread가 생성된다.

 

f0159cfc6148ef7040a3d4fbd546810a.png

그림 5 오픈 소스 v1.6에서 캐시 항목 명령 (STORE/GET/DELETE)을 위한 프로세스 흐름

 

클라이언트 요청들은 각 worker thread로 분산되어 처리된다. GET 명령의 경우, 각 스레드는 키와 그 키의 값 (value) 데이터 위치를 찾기 위해 해쉬 테이블 조회를 해야 한다. 또한 그 key-value가 최근에 액세스되었음을 표시하고 제거 (eviction) 순서를 업데이트하기 위해 LRU list 앞으로 이동한다. 안타깝게도 이 두 가지 code path에 공유 데이터 구조에 대한 serialization (순차화)이 필요하다. 그렇게 데이터를 찾으면 worker thread가 시스템 메모리에서 데이터를 조회하고 요청한 클라이언트로 전송한다.

해쉬 테이블로의 접근을 보호하는 global cache lock을 통해 해쉬 테이블의 스레드 안정성 (thread-safety)을 보장한다. 또한 캐시 제거 (eviction) 관리하기 위해 유지되는 LRU linked-list에서는, 스레드 안정성을 보장하기 위해 캐시 항목의 LRU linked-list가 수정될 때도 동일한 global cache lock을 사용한다. 스레드는 네트워크 프로토콜 래퍼 (wrapper)인 'libevent'에 의해 만들어진다. 스레드는 'libevent'에서 요청을 대기하고 있다가 요청을 받으면 패킷에서 데이터를 빼내고 명령을 디코딩 한다. 캐시 항목에 대한 명령 (GET/STORE/DELETE/ARITHMETIC)이라고 가정한다면, 스레드는 키에서 hash 값을 계산하고 global cache lock을 획득한다. 스레드는 명령이 완료될 때까지 lock을 유지한 후 lock을 해제하고 응답을 구성한다. 이렇게 하면 memcached가 효과적으로 순차화 (serialize) 되어 데이터 일관성 및 스레드 안전성이 보장된다.

2.3.1     해쉬 테이블 조회

Lock을 획득하고 난 후 해쉬 테이블 탐색 (traversal) 및/또는 수정 (manipulation)이 수행된다. 순차처리 (serialized) 된 해쉬 테이블 접근은 STORE 또는 DELETE 명령 중에 포인터 쓰래싱 (pointer thrashing)과 linked-list bucket이 corrupt 되는 것에 대한 염려를 덜어준다. 해쉬 테이블 구현을 현재 있는 그대로 남겨두고 lock만 제거하면, linked-list에서 서로 인접한 두 캐시 항목이 삭제되고 작업이 완료되었을 때 linked-list가 정상적인 체인에 있는 캐시 항목을 더 이상 가리키지 않게 되어 버린다. 이 내용은 그림 6을 통해 확인할 수 있으며 각 행은 시간 경과 순서이다.

 

6905ca9d170b1b9d1e561f7f62f05462.png

그림 6 두 개의 스레드에서 캐시 항목을 동시에 제거 시 linked-list 손상

 

2)에서 한 스레드는 노란색 캐시 항목 (#3)을 제거하고, 한 스레드는 주황색 캐시 항목 (#4)을 제거한다. 두 개의 스레드 모두 이전 캐시 항목 포인터를 수정한다. 3)에서는 두 개 모두 다음 캐시 항목을 가리키는 포인터를 NULL로 변경한다. 마지막으로4)에서는 캐시 항목이 제거되면 dangling pointer가 되어 버린다.

GET 명령에도 lock이 필요하므로 linked-list를 변경하는 동안 bucket을 탐색하지 않는다. 변경 되고 있는 linked-list를 탐색하면 스레드에서 존재하지 않는 메모리를 가리키는 포인터를 참조하여 세그멘테이션오류 (segmentation fault)를 일으키거나 존재하는 캐시 항목을 누락 (miss)시킬 수 있다. 해쉬 테이블 데이터 구조의 레이아웃은 그림 3을 참조한다.

2.3.2    LRU 수정

해쉬 테이블 수정이 완료되면 캐시 항목은 LRU eviction에 사용되는 double linked-list 구조 (그림 4)에 추가 (STORE)되거나 삭제 (DELETE)된다. GET 명령을 처리할 때 캐시 항목은 LRU의 현재 위치에서 제거되고 리스트의 head에 추가된다. 같은 LRU 리스트에 위치한 두 개의 캐시 항목을 제거하거나 head에 동시에 삽입할 경우 리스트가 손상될 수 있으므로, LRU를 수정하는 동안에는 global cache lock을 유지한다. 깨지는 포인터가 하나 더 많다는 것을 제외하면 앞서 설명한 해쉬 테이블의 linked-list 손상과 동일하다.

2.3.3    기타 순차 처리된 (serial) 명령

해쉬 테이블 조회 및 LRU 수정 global 캐시 lock에 의해 보호되는 기본 코드 세그먼트이지만 다른 명령도 lock으로 보호되어야 한다. 캐시 제거 (evictions), 할당(allocations) 및 플러시 (flushes)에서도 global cache lock을 사용해야 한다. 이러한 명령은 LRU 또는 해쉬 테이블에 접근함으로 여러 스레드에 의한 동시 접근 (concurrent) 로 인해 손상이 발생할 수 있다. 또한 이 명령으로 수정되는 캐시 항목 플래그는 global cache lock에 의해 보호되어야 스레드 안정성이 보장된다. 플래그 사용의 예로 ITEM_LINKED 플래그가 있으며 이 플래그는 해쉬 테이블에서 캐시 항목을 제거할 때 지워져야 한다. Global cache lock을 통해 한 번에 하나의 worker 스레드만 이 플래그를 수정할 수 있도록 되어있다.

2.3.4     병렬 작업

Memcached 명령에 대한 대부분의 프로세스 흐름은 global cache lock에 의해 보호되어야 한다. 이 기능 외에 병렬 처리되는 작업에는 네트워크 전송 (수신 및 송신), 패킷 디코딩, 키 해싱 및 데이터 응답 구성이 있다.

2.4    성능 병목

Global cache lock은 [1] 및 [6]에 설명된 것처럼 스레드가 4개 이상인 경우 성능 병목이 되는 주 원인이다. 이 증상은 테스트 (그림 7 및 표 1)를 통해서도 확인할 수 있다. 그림 7은 오픈 소스 memcached에서 코어가 4개를 넘어설 때 처리량이 감소되는 것으로 나타난다. 이 lock은 coarse-grained (긴 code path를 serialize하기 때문에) 이어서, 순차 실행 시 소요되는 시간이 많으며 worker 스레드 간의 lock 경합이 높다.

이를 확인하기 위해 Intel® Vtune™ Amplifier XE1을 사용하여 stack trace를 생성하여 profiling 한 결과 대부분의 실행 시간이 lock (global cache lock 대기 등)에서 소요된 것으로 나타났다. Stack trace를 더 자세히 확인한 결과 global cache lock이 경합을 유발한 것으로 나타났다. 문제의 신속한 확인을 위해 global cache lock을 제거하자 (안전하지 않지만 효율적이라는 가정 하에), read-only (GET) 워크로드에서 처리량이 크게 증가했다.

여러 다른 연구에서도 밝혀진 것과 같이 lock 경합이 프로그램의 병렬 성능 저하를 야기한다. 참고 문헌 [12]는 경합과, 경합이 프로그램 성능 저하에 어떤 영향을 주는지에 대한 심도 있는 분석이 되어 있다. 우리는 global cache lock에 대한 경합을 피하면 병렬 성능 및 애플리케이션 성능 scalability가 개선될 것이라는 결론을 내렸다.

 

점유율

함수

DSO (Dynamic Shared Object)

60.40%

_spin_lock

[kernel.kallsyms]

1.40%

ixgbe_poll

/lib/modules/…/ixgbe/ixgbe.ko

1.10%

net_rx_action

[kernel.kallsyms]

1.10%

__pthread_mutex_lock_internal

/lib64/libpthread-2.12.so

1.10%

assoc_find

/usr/local/lib/memcached/default_engine.so

1.10%

copy_user_generic_string

[kernel.kallsyms]

1.00%

tcp_sendmsg

[kernel.kallsyms]

0.90%

irq_entries_start

[kernel.kallsyms]

0.80%

pthread_mutex_unlock

/lib64/libpthread-2.12.so

0.70%

GI_vfprintf

/lib64/libc-2.12.so

0.70%

audit_syscall_exit

[kernel.kallsyms]

0.60%

ip_route_input

[kernel.kallsyms]

0.60%

ixgbe_resume

/lib/modules/…/ixgbe/ixgbe.ko

0.50%

tcp_ack

[kernel.kallsyms]

표 1 8개의 스레드로 memcached가 실행되는 서버에서 캡처한 routine 별 CPU 사용량 순위

 

f74c61a56a068aae8f666571cd1710aa.png

그림 7 3~4개의 코어에서 최대 성능을 표시하는 Base (1.6 버전) memcached의 확장 성능

3    memcached 최적화

이 절에서는 multicore 서버에서 다중 스레드 실행을 위한 애플리케이션 확장성을 개선하기 위해 memcached를 최적화하는 것에 대해 간략히 설명하려고 한다. 현재 memcached 아키텍처를 확인한 후 global cache lock과 기본 성능 병목 현상을 최소화하거나 제거하는 기술을 알아본다.

목표는 다음과 같다.

이러한 목표에 대한 진행 상태를 측정하기 위해, 트랜잭션 set를 replay한 가상 워크로드를 사용하여 Base의 hit rate 측정했다. 이 절에 설명된 최적화는 오픈 소스 memcached 1.6 버전에 새로운 엔진으로 구현되었다. 2012년 7월 현재, 이 최적화된 버전은 GitHub (https://github.com/rajiv- kapoor/memcached/tree/bagLRU) 에서 소스 코드를 다운로드할 수 있다.

3.1    데이터 구조

최적화를 위해 두 개의 데이터 구조, 해쉬 테이블과 LRU가 수정되었다.

  • 해쉬 테이블 lock 메커니즘이 병렬 액세스를 허용되게 바뀜
  • Bag LRU – 데이터 구조가 single linked-list bag (즉, 컬렉션)을 가진 서로 다른 크기의 LRU list의 배열로 변경됨

해쉬 테이블에서 알고리즘은 변경하고 있지만 해쉬 테이블 데이터 구조에 대한 물리적 변경은 없다. 해쉬 테이블 bucket을 수정하는 데 필요한 striped lock을 구현한다. striped lock은 해쉬 테이블 영역에 구현된 세분화된 lock 집합 (global 해쉬 테이블 lock과 대조적)이다. 이 구현을 위해 먼저 Z개의 lock 집합을 생성하여 초기화한다. 여기서 Z는 2의 거듭제곱이며 2의 거듭제곱을 유지하면서 비트마스크를 사용하여 lock ID를 신속하게 계산한다. linked-list을 이동하기 위해 'Z-1' 값을 잠글 bucket과 bit-wise AND를 수행하여 해당 bucket을 보호하는 lock이 획득된다. 이 프로토콜은 모든 Z번째 bucket (그림 8)에 대해 공유된 lock을 제공하여 해쉬 테이블 lock에 대한 경합을 줄인다. 테스트에서는 lock의 개수로 Z=32가 적합한 값이었다.

LRU는 대규모 데이터 구조 변경이 필요한 위치이다. 병렬 LRU를 더 많이 구축하여 병렬 해쉬 테이블의 이점을 얻기 위해 병렬 LRU 데이터 구조에 대한 개념을 조사했다. Lock 회피 LRU 알고리즘과 데이터 구조에는 [13] 및 [14]와 같은 두가지 좋은 아이디어가 있다.

이 두 개념으로부터 힌트를 얻은 bag LRU 개념으로 캐시 항목의 single linked-list 'bags'을 구현했다. 각 'bag'은 비슷한 시간대에 insert나 touch된 항목들이 들어있고, atomic insert가 가능하다. 이 모델에 필요한 캐시 항목 'clean-up' 기능이 필요하며 이 용도로 'cleaner thread'가 존재한다. 이 스레드는 만료되었거나 유효하지 않은 캐시 항목을 제거하고 bags에서 유지관리를 수행한다. 다이어그램 및 로직을 비롯한 새로운 데이터 구조에 대한 보다 자세한 설명은 3.5 절을 참조하면 된다.

3.2    명령

2.2 절에 설명된 memcached 명령이나 프로토콜에는 변경된 내용이 없다. 차이점이라면 명령 실행 방법론뿐이다.

 

f302b51a604d5cd6a92599d873b6bc93.png

그림 8 각 bucket을 보호하는 striped lock 추가

 

3.3    프로세스 흐름

그림 9는 수정 후 업데이트된 프로세스 흐름을 나타낸다. 위에서부터 아래까지 클라이언트, libevent 및 worker thread 기능이 동일하게 유지된다. 해쉬 테이블 조회를 위한 순차화된 명령과 LRU 처리가 병렬 방식으로 작동하도록 수정된다.

DELETE 및 STORE 명령은 striped lock (이전에 설명됨) (그림 8)이 있는 병렬 해쉬 테이블 방식을 사용하며 GET 명령은 non-blocking 및 병렬 방식으로 실행된다.

 

9ac6a7d19d283d1d46df44d3a6590022.png

그림 9 최적화된 버전의 1.6.0_beta1에서 캐시 항목 명령 (STORE/GET/DELETE)에 대한 프로세스 흐름

 

아래 설명된 알고리즘 변경은 hash chain 손상을 방지하고 제거 중인 캐시 항목 포인터를 유지한다. 캐시 항목 제거시 스레드가 preempt 되더라도 올바른 bucket에서 진행할 가능성이 높다. 이는 차단 해제된 GET과는 미묘한 차이가 있다. 캐시 항목에 대해 스레드가 오랫동안 preempt 된 후 그 캐시 항목이 이동되었다면 GET은 false negative를 반환한다 (memcached가 캐시 항목이 해쉬 테이블에 실제로 있음에도 불구하고 '캐시 항목 없음'을 반환).

이 방법에 대한 찬반 양론이 있는데, memcached는 영구적 (persistent) 이지 않은 "cache" data를 다루기 때문에 false negative 문제보다 lock을 제거하여 추가 처리량을 실현하는 것에 촛점을 맞추었다. False negative가 문제가 되는 경우 striped lock을 사용하면 된다 (어느 정도의 성능 저하가 있다). 하지만 예상한 대로 코어 수가 늘어나면 striped lock이 다시 경합 지점이 될 수 있다.

이렇게 하여 LRU 처리는 위의 3.2 절과 3.5 절에 설명된 것처럼 non-blocking 및 병렬화 되었다. 각 bags에서는 single linked-list가 있어서 캐시 항목을 atomic하게 삽입할 수 있어서 새로운 캐시 항목을 bag에 넣는 동안에 lock이 필요가 없어졌다. 여전히 CAS (Compare-and-Swap) 명령이 필요하며 이 경우 STORE 명령들만 수행되는 경우에는 경합 지점이 될 수 있다. 그러나 가장 많이 쓰이는 명령인 GET에 대해서 최적화를 했기 때문에 lock 또는 GET에 필요한 CAS 없이 bag LRU를 구현할 수 있었다.

3.4    병렬 해쉬 테이블 조회

Memcached 트랜잭션 실행해보면 가장 처음으로 나타나는 성능저하 문제는 순차화된 해쉬 테이블 조회이다. 이전의 대부분의 Memcached 병렬 성능 고도화 노력들은 스레드가 캐시의 특정 부분 (예. 파티션) 만 액세스하는 해쉬 테이블 분할에 중점을 두었다. 이렇게 하면 [16]에서 보여진 대로 lock 경합을 제거하고 성능을 높일 수 있다. 이 방식의 단점은 단일 파티션의 캐시 항목을 여러 차례 자주 액세스하여 해당 파티션을 처리하는 스레드 들이 포화 상태일 수 있다는 점이다. 마찬가지로 어떤 파티션을 선호하는 트래픽 패턴을 가진 워크로드에서는 처리 성능이 저하될 수 있다. 파티션의 전체적인 성능은 lock 경합을 유발하지 않는 캐시를 조회 하는 스레드 들로 제한된다.

이런 구현 방식은 트래픽의 대부분을 차지하는 GET 명령의 처리량을 최적화한다. 참고 문헌 [11]에서는 Facebook의 캐시 서버의 상당 부분을 차지하는 트래픽이 GET 명령임을 보여준다. 이 비율은 불과 몇 개 되지 않는 write가 대부분인 workload의 캐싱 tier만 제외하면, 어떤 경우 GET이 99.8%를 차지할 만큼 높다. 이 내용은 [15] 및 [16]에서 수행한 성능 테스트와 일치한다 (두 테스트 방법 모두 GET 명령에 대한 처리량만 측정했다).

이러한 특징을 염두에 두고 GET 명령을 처리할 때 어떠한 lock도 필요 없는 병렬 해쉬 테이블이 설계되었다. [12에서 보면 lock 경합은 아무리 짧더라도 성능에 영향을 준다 (트랜잭션 지연, Latency SLA에 의한 전체 처리량). 미래에는 CPU가 더 빨라지고 코어 수도 늘어나므로 lock 경합도 증가할 것이다. 새로운 설계에서는 이러한 경향을 고려하여 GET에 대한 lock 경합을 완전히 제거했다.

3.4.1    GET lock 제거

GET에서 lock을 제거하기 위해서는 hash chain 확장 (expansion) 과 수정 (즉, 삽입 및 제거) 이라는 두 가지 경우를 해결해야 한다.

해쉬 테이블이 bucket보다 훨씬 많은 양의 캐시 항목을 포함하는 경우 '확장 (expansion)'이 필요하다. 이 경우 캐시 항목을 더 많이 추가하면 hash chain의 길이가 늘어나고 캐시 항목을 찾는데 필요한 시간과 RTT가 증가한다. 이렇게 시간상 손해를 보지 않기 위해서, bucket 양의 2배인 새로운 해쉬 테이블을 다음과 같이 할당한다.

  • 모든 캐시 항목을 이전 테이블에서 가져옴
  • 키들을 해싱
  • 캐시 항목들을 새로운 테이블에 삽입

 

Assoc_get() 로직:

bucket 결정을 위한 hash 해쉬 마스킹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1.     If (! expanding)
 
2.     |    hash chain bucket을 확인하여 있는 경우 캐시 항목 반환
 
3.     |    If (캐시 항목이 없음)
 
4.     |    |     If (확장 && 확장 bucket이 bucket보다 큼)
 
5.     |    |    | 새로운 큰 테이블에서 bucket 찾기
 
6.     |    |    | 이전 bucket이 비워질 때까지 대기
 
7.     |    |    | 새로운 hash bucket을 확인하여 있는 경우 캐시 항목 반환
 
8.     else if expanding
 
9.     |    확장이 이전 테이블에 있는지 확인
 
10.    |    hash chain bucket을 확인하여 있는 경우 캐시 항목 반환
 
11.    |    If (캐시 항목이 없음)
 
12.    |    |    If (확장 bucket이 bucket보다 큼)
 
13.    |    |    새로운 큰 테이블에서 bucket 찾기
 
14.    |    |    |    이전 bucket이 비워질 때까지 대기
 
15.    |    |    |    새로운 hash bucket을 확인하여 있는 경우 캐시 항목 반환

캐시 항목을 찾을 때 확장 중인지 확인한다. 확장 중이 아니라면 GET 명령이 정상적으로 실행되고, 항목이 있는 경우 찾은 캐시 항목을 반환한다. 이때 확장이 다시 시작이 되었을 경우가 있는데, 그 사이 캐시 항목이 이동될 수 있다. 이 경우 프로세스는 이전 bucket (이동 전에 있었던 hash table의 bucket)이 비워질 때까지 대기한 후 그 캐시 항목에 대한 새로운 해쉬 테이블과 bucket을 확인한다.

만약 해쉬 테이블의 확장이 진행 중인 경우 이전 bucket이 이미 이동되었는지 확인한다. 이전 bucket이 이동되지 않은 경우 이전 bucket에서 캐시 항목을 찾는다. 비슷하게, 캐시 항목이 없는 경우 이전 bucket에 'bucket 이동'이 발생했는지 확인한다. 그런 경우 새로운 해쉬 테이블을 확인한다.

다음으로 해결해야 할 문제는 STORE 및 DELETE 코드에서 처리되는 hash chain 수정이다. 포인터가 올바른 순서로 변경되기만 한다면 GET에는 hash chain traversal 대한 문제가 없다.

3.4.2    STORE/DELETE lock 제거

STORE 및 DELETE 명령은 현재 오픈 소스 구현과 매우 비슷하다. Hash chain 및 동일한 chain 또는 캐시 항목에 대한 동시에 여러 건의 삽입/삭제를 안전하게 처리하기 위해 STORE 및 DELETE는 그림 8에서 설명된 striped lock을 사용한다.

 

Assoc_delete() 로직:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1.     캐시 항목 bucket 확인
 
2.     bucket lock 획득
 
3.     If (expanding)
 
4.     |    If (확장 bucket이 bucket보다 큼)
 
5.     |    |    bucket lock 해제
 
6.     |    |    새로운 bucket 확인
 
7.     |    |    bucket lock 획득
 
9.     |    캐시 항목이 있는 경우 bucket에서 캐시 항목 삭제
 
10.    |    bucket lock 해제
 
11.    Else if not expanding
 
12.    |    캐시 항목이 있는 경우 bucket에서 캐시 항목 삭제
 
13.    |    bucket lock 해제

확장 중인 bucket 이동을 비롯한 모든 hash chain 수정에는 striped lock이 필요하다. 이렇게 하면 bucket lock을 얻은 후 확장을 확인하기 때문에 STORE 및 DELETE가 보다 간편해진다. STORE 또는 DELETE routine에서 수정이 필요한 bucket이 확장으로 인하여 이동 중 이라면, 이전 bucket이 비워지고 새로운 bucket을 확인할 때까지 bucket lock을 잡고 있어야 한다. Lock을 획득한 후 이동이 시작되면 lock이 해제 될 때까지 확장하지 못한다. 이 로직은 STORE, DELETE 및 MOVE에 동일하게 적용된다.

 

Assoc_insert() 로직:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1.     캐시 항목 bucket 확인
 
2.     bucket lock 획득
 
3.     If (expanding)
 
4.     |    If (확장 bucket이 현재 bucket보다 큼)
 
5.     |    |    bucket lock 해제
 
6.     |    |    새로운 bucket 찾기
 
7.     |    |    bucket lock 획득
 
8.     |    bucket 조회. 캐시 항목이 없는 경우, bucket에 삽입
 
9.     |    bucket lock 해제
 
10.    Else if (not expanding)
 
11.    |    bucket 조회. 캐시 항목이 없는 경우, bucket에 삽입
 
12.    |    bucket lock 해제

3.5    병렬 LRU Eviction

캐시 항목 제거 순서를 결정하기 위해서는 일반적인 double linked list LRU 구현체를 대신할 수 있는 병렬 알고리즘이 필요하다. 'Bag' 구현의 개념은 캐시 항목을 타임스탬프 기반의 'bags'로 그룹화하는 것이다. 이러한 bag에서는 캐시 항목의 single linked list가 저장되므로 CAS (Compare-And-Swap)를 사용하여 list의 tail에 atomic하게 삽입할 수 있다. 이 설계는 STORE만 수행되는 경우에는 CAS가 병목 지점이 될 수 있지만, 새로운 캐시 항목을 bag에 배치하는 동안 lock 이 필요 없다는 장점이 있다. 다시 언급하지만 대부분의 명령인 GET 명령을 위주로 최적화하려는 취지이기 때문에, lock이나 CAS 없이 GET이 수행될 수 있도록 bag LRU를 구현하였다.

새로운 설계에서도 이전 캐시 항목을 제거하고 새로운 bag LRU 구조를 유지하는 방법이 필요하다. 이를 위해 백그라운드로 실행할 'cleaner thread'를 구현한다. 이 스레드는 만료되거나 유효하지 않은 캐시 항목을 제거하고 LRU에서 유지관리를 수행한다. 이전 버전에서 GET 명령을 수행했던 명령 (즉, 만료 시 제거)은 이 백그라운드 스레드로 이동하므로 GET 명령의 처리량이 증가하고 GET 명령 트랜잭션당 오버헤드가 감소한다. cleaner thread 역시 캐시의 적중 비율을 높이는 이점을 제공한다. 빠른 만료로 삽입된 캐시 항목은 캐시가 오래될 때까지 공간을 차지하는 대신 만료되기 몇 분 이내로 정리된다.

3.5.1    Linked-list 구조

새로운 Linked-list 구조는 기존의 LRU의 prev 그리고 next 포인터를 재사용하므로 추가 메모리 할당이 필요하지 않다. next포인터는 새로운 single linked-list에서의 다음 캐시 항목을 기존과 같이 가리키고 prev pointer는 그 캐시 항목의 bag membership인 마지막으로 조회된 시점의 "최신 bag"을 표시한다. Cleaner thread는 bag을 정리하는 동안 이 포인터를 사용한다.

3.5.2    추가 데이터 구조

d2b59221a9588d5ca5b68472949dbb97.png

그림 10 bag 데이터 구조

5ac097a39c67c89b2fb8789935bacd0b.png

그림 11 bag list 데이터 구조

 

Bag LRU는 bag 식별을 위한 LRU 배열과 실제 bag 데이터 구조, 이 두 가지 데이터 구조가 필요하다.

첫 번째 데이터 구조는 슬랩 id당 하나의 bag head 배열인 bag LRU list이다 (그림 10). 이 구조는 global 변수에서 참조되며 bag LRU가 초기화될 때 초기화된다. 이 배열의 각 bag head는 다음 field들이 필요하다.

  • 최신 bag에 대한 포인터 (Newest bag)
  • 최신 대체 bag에 대한 포인터 (Newest Alternative bag)
  • Bag list에서 가장 오래된 bag (Oldest bag)
  • 통계를 위한 bag의 개수

"최신 bag"은 현재 새로 할당된 캐시 항목으로 채워지는 bag이다. 가장 오래된 bag은 cleaner thread가 bag 정리를 시작하고 제거 (eviction) 스레드가 캐시 항목을 제거하는 bag이다. Cleaner thread는 최신 bag으로의 삽입으로 인한 lock 경합을 피하기 위해 "최신 대체 bag"을 사용한다. bag에 이미 존재하고 최근에 액세스된 캐시 항목들은, 제거 순서 (eviction order)를 유지하기 위해, cleaner thread에 의해 최신 bag으로 이동되어야 할 캐시 항목들은 "최신 대체 bag"에 들어가게 된다.

두 번째로 필요한 데이터 구조는 캐시 항목의 single linked-list인 bag이다 (그림 11). bag은 다음 field 들을 포함한다.

  • Bag에서 "가장 최신" 및 "가장 오래된" 캐시 항목을 가르키는 포인터
  • Bag의 캐시 항목 개수 카운터 'count'
  • lock
  • Bag 열고 닫은 시간 (타임스탬프) - 현재 사용되지는 않음

Bag을 정리할 때 cleaner thread는 bag에 있는 가장 오래된 캐시 항목부터 정리하기 시작한다. Worker thread의 eviction (제거) 작업도 같은 위치에서 시작된다. "가장 최신" 캐시 항목 포인터는 캐시 항목이 삽입될 때나 bag이 병합될 때 빠르게 bag의 끝에 추가할 수 있도록 해준다. 'count'는 새 bag을 열거나 이전 bag을 최신 bag에 병합하는 시기를 결정하는데 쓰인다. 'count' 필요한 경우 통계 및 디버깅에 사용될 수 있다. 모든 bag에 lock이 필요한 이유는 cleaner thread와 worker thread의 'eviction' 간의 스레드 안정성을 위해서이다. Eviction worker thread와 cleaner thread가 같은 bag에 있는 캐시 항목을 이동하는 경우 스레드 중 하나가 bag을 손상시킬 수 있다 (그림 6에 linked-list 손상이 표시됨). Cleaner thread는 각 bag에서 작업 시간이 매우 적으므로 cleaner thread와 worker thread 사이에 lock 경합이 발생할 가능성은 낮다. 또한 worker thread는 상위 호출 스택에서 serialized 되므로 이러한 스레드간의 lock 경합은 없다.

3.5.3    데이터 구조에 대한 명령

이 절에서는 새로운 데이터 구조 및 알고리즘에서 초기화, 캐시 항목 삽입 및 LRU list 정리와 같은 명령을 설명한다.

먼저, 추가된 모든 lock을 초기화해야 하며 사용할 각 슬랩 크기에 맞는 초기 bag을 생성한다. 모든 구조가 초기화된 후에는 cleaner thread가 생성되고 cleaning (정리) 루프에서 시작된다.

 

Bag LRU 초기화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.     global eviction lock 초기화
 
2.     cleaner lock 초기화 (현재 하나의 lock만 슬랩 id당 하나의 cleaner thread가 될 수 있음)
 
3.     Bag head 구조 배열 생성
 
4.     각 슬랩 id에 대해
 
5.     |    첫 번째 bag 초기화
 
6.     |    bag lock 초기화
 
7.     |    bag 배열 head가 최신 bag으로 이 id를 가리킴
 
8.     |    bag 카운터 증가
 
9.     cleaner thread 생성 및 실행

정리 루프에서는 스레드가 bag이 가득 차 있는지 계속해서 확인하며 필요한 경우 새 bag을 해당 슬랩에 추가한다. 주기적으로 cleaner thread는 bag에 있는 모든 캐시 항목을 확인하여 유효하지 않거나 만료된 캐시 항목을 제거하고 최소 캐시 항목 개수 임계 값 미만인 bag을 병합한다.

 

Cleaner thread 루프

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1.     무한 루프
 
2.     |    cleaner lock 획득
 
3.     |    각 슬랩 id에 대해
 
4.     |    |    "최신 bag"이 가득찬 경우
 
5.     |    |    |    새로운 bag 생성 및 초기화
 
6.     |    |    |    최신 bag 다음 bag 포인터가 새로 초기화된 bag을 가리킴
 
7.     |    |    |    bag holder = 최신 bag
 
8.     |    |    |    최신 bag = 새로 초기화된 bag
 
9.     |    |    |    최신 대체 (Newest alternate) = bag holder
 
10.    |    |    |    atomic increment of bag count
 
11.    N번째 iteration시 bag 정리
 
12.    cleaner lock 해제
 
13.    지정된 간격 동안 sleep

캐시 항목이 LRU에 삽입될때, 캐시 항목의 슬랩 크기에 맞는 최신 bag에 삽입된다. bag이 정해지면 캐시 항목은 CAS를 사용하여 최신 bag에서 최신 캐시 항목 바로 뒤 (끝)에 삽입된다. Atomic 삽입이 실패하면 스레드는 bag의 끝을 나타내는 NULL 포인터를 찾아 다시 CAS를 시도한다. 성공적으로 삽입될 때까지 CAS를 시도한다.

 

LRU에 없는 새로운 캐시 항목 삽입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1.     새로운 캐시 항목 next 포인터 = NULL
 
2.     If (현재 open bag이 비어 있음)
 
3.     |    If ( CAS (현재 bag 최신 캐시 항목, NULL, 새로운 캐시 항목)){
 
4.     |    |    새로운 캐시 항목 prev 포인터 = 현재 bag
 
5.     |    |    현재 bag 최신 캐시 항목 - 새로운 캐시 항목
 
6.     |    |    bag 캐시 항목 카운터 atomic increment
 
7.     |    |    반환
 
8.     swap 캐시 항목 = 현재 bag 최신 캐시 항목
 
9.     While ( !CAS (swap 캐시 항목 next 포인터, NULL, 새로운 캐시 항목)){
 
10.    |    While (swap 캐시 항목 next 포인터 != NULL)
 
12.    |    |    null을 찾을 때까지 swap 캐시 항목 next 포인터를 따라가서 다시 전환 시도
 
13.    새로운 캐시 항목 prev 포인터 = 현재 bag
 
14.    현재 bag 최신 캐시 항목 = 새로운 캐시 항목
 
15.    bag 캐시 항목 카운터 atomic increment

GET의 경우 캐시 항목이 LRU에 이미 존재하므로 간단하게 해당 타임스탬프와 LRU에서 위치를 수정한다. 이러한 수정은 새 타임스탬프를 캐시 항목에서 last accessed 필드에 복사하고 캐시 항목 prev 포인터 (캐시 항목이 insert된 bag)가 그 슬랩의 최신 bag을 가리키도록 한다.

 

LRU에 이미 있는 캐시 항목 업데이트

1
2
3
1.    캐시 항목 prev 포인터 = 현재 bag
 
2.    캐시 항목 타임스탬프 업데이트

3.5.3.1    LRU list 정리

LRU list을 정리할때 필요한 작업들이 몇 개 있다: 캐시 항목 재정렬, bucket 병합, 캐시 항목 제거 및 캐시 덤핑.

 

Cleaner thread가 bag list을 조회할때 캐시 항목 prev 포인터를 확인한다. 포인터가 현재 정리 중인 bag을 가리키는 경우 그 캐시 항목을 현재 위치에 남겨 둔다. 그 항목이 다른 bag을 가리키는 경우는 항목은 처음 삽입된 이후 한번 이상 조회되었다는 뜻이다. 이 경우 cleaner thread는 현재 bag에서 항목을 제거하고 상주해야 하는 bag에 삽입한다. 위에 설명된 것처럼 LRU 크기에서 최신 bag인 경우 경합을 피하기 위해 '최신 대체 (newest alternate)' bag에 넣는다.

 

두 개의 bucket 병합

1
2
3
4
5
6
7
8
9
10
11
1.    오래된 bucket의 최신 캐시 항목 다음 포인터 = 최신 bucket의 가장 오래된 캐시 항목
 
2.    최신 bucket의 오래된 캐시 항목 = 오래된 bucket의 오래된 캐시 항목
 
3.    추가한 모든 캐시 항목에 대해
 
4.    |    캐시 항목이 오래된 bag을 가리켰던 경우
 
5.    |    |    새로운 bag을 가리킴
 
6.    오래된 bag에 있는 캐시 항목의 양 만큼 최신 bag 카운터 증가

두 개의 bucket을 병합하는 로직은 두 개의 linked-list을 함께 연결하고 둘 중 더 최근 bag에 배치한다. 그런 다음 prev 포인터가 오래된 bag을 가리켰던 모든 캐시 항목이 최신 bag을 가리키도록 하고 오래된 bag을 회수 (reclaim) 해야 한다.

 

캐시 항목 제거 (eviction)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1.     슬랩 id에 대해 eviction lock 획득
 
2.     슬랩 id에서 할당된 bag들 중에서 가장 오래된 bag 선택
 
3.     최신 대체 항목 이전에 있고 캐시 항목을 가지고 있는 첫 번째 bag 찾기
 
4.     만약 bag을 찾을 수 없는 경우 두 개의 새로운 bag을 이 슬랩 id에 추가한 후 bag을 모두 확인
 
5.     여전히 bag을 찾을 수 없는 경우
 
6.     |    캐시 항목을 제거 중단하고 오류 반환
 
7.     |    eviction lock 해제
 
8.     찾은 bag lock
 
9.     Bag에서 가장 오래된 캐시 항목부터 확인
 
10.    refcount가 1 또는 0인 캐시 항목 찾거나 bag에 있는 처음 N개의 캐시 항목 중에서 locked 캐시 항목 삭제
 
11.    |    캐시 항목을 찾은 경우 bag에서 제거하고 자원 해제
 
12.    |    슬랩 할당자로부터 새로운 캐시 항목 얻기 시도
 
13.    |    if (캐시 항목을 제거했으나 할당자에서 얻지 못함)
 
14.    |    |    다른 캐시 항목을 제거하고 다시 시도
 
15.    |    else
 
16.    |    |    캐시 항목을 제거하려는 시도를 중단하고 오류 반환
 
17.    Bag unlock
 
18.    eviction lock 해제

Worker thread들이 캐시 항목을 evict (제거)하기 위해서는 global eviction lock을 이용해야 정합성이 보장된다. 그런 다음 cleaner thread와 충돌을 피하기 위해 bag을 lock해야 한다. 이렇게 하면 worker thread가 bag을 수정하는 유일한 스레드이며 제거 기준을 만족하는 캐시 항목들을 제거 (evict) 할 수 있다 (오픈 소스 버전과 동일).

 

캐시 덤핑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.    cleaner lock 획득
 
2.    eviction lock 획득
 
3.    새로운 bag을 추가하여 다른 스레드에 영향을 주지 않고 제거할 수 있도록 함
 
4.    각 슬랩 id에 대해
 
5.    |    각 캐시 항목에 대해
 
6.    |    |    If (캐시 항목이 캐시 덤프 시간보다 오래됨)
 
7.    |    |    캐시 항목 해제
 
8.    eviction lock 해제
 
9.    cleaner lock 해제

캐시를 덤핑하는 것은 앞에 설명한 캐시 항목을 제거하는 방식과 같지만, 명령에 지정된 시간보다 오래된 캐시 항목들을 모두 evict (제거) 해야 한다.

3.6    Global cache lock 제거

병렬 hash 조회, LRU 정렬 및 병렬 캐시 (evict) 제거를 관리하기 위한 기능들을 넣고 나면, global cache lock을 GET/STORE/DELETE 코드 경로에서 제거 하는 것이 거의 가능하다. Ref count 관리는 변경되어야 하며, lock 대신 ref count 변경 시 atomic incr / decr를 사용한다. 이렇게 해서 global lock이 더 이상 필요하지 않게 되어 제거할 수 있다.

1
2
3
4
5
1.    Lock (global_cache_lock) ➟ global lock 제거
 
2.     . . . . . (GET/STORE/DELETE)
 
3.    Unlock (global_cache_lock) ➟ global lock 제거

4    테스트 환경 구성 및 메트릭스

새로운 memcached 아키텍처의 개발 및 디버깅이 완료되면 성능상 이점을 확인하기 위한 설정 및 테스트 방법을 정의해야 한다.

4.1    하드웨어 구성

이 절에서는 사용 한 모든 시스템의 하드웨어 구성을 설명한다.

4.1.1    SUT (System under Test)

이전 평가에 의하면 1GbE NIC는 쉽게 포화 상태가 되므로 네트워크의 병목을 제거하기 위해 10GbE NIC를 사용한다.

Memcached는 주로 TCO (Total Cost of Ownership)가 중요한 대형 server farm에 deploy된다. 와트당 성능 결과를 최대화하고 TC를 감소하는 데 그 목적이 있다. 테스트 시스템은 Quanta Computer에서 제조한 2-Socket Open Compute 2.0 시스템 보드이다.

시스템 하드웨어:

  • 1.5U chassis에서 Dual-Socket Open Compute 2.0 시스템 보드(반폭)
  • 2 x Intel® Xeon® E5-2660 2.2Ghz 프로세서(95W TDP)[10]
  • 128GB DRAM x 16, 8GB DDR3 PC3 12800 ECC
  • Intel® Niantic 82599 10Gb 직접 연결 SFP+ 이더넷 컨트롤러[10]
  • Arista 7124 스위치 – 24포트 SFP+

4.1.2    로드 생성기 (load generator)

기존에 사용해 온 오픈 소스 버전의 memcached에는 로드 생성기 한대면 충분한 load를 줄 수 있다. 반면에 수정한 버전을 테스트하기 위해서는 더 많은 클라이언트가 필요하다. 한 개의 클라이언트가 초당 약 800K의 request (rps)를 발생할 수 있으므로, 적절한 로드를 생성하기 위해서 4개의 클라이언트로 구성한다. 모든 로드 생성기는 다음과 하드웨어로 구성되어 있고, 각각 800K+ rps의 load를 생성하게 했다.

  • 1.5U chassis에서 Dual-Socket Open Compute 2.0 시스템 보드 (반폭)
  • 수량 2 – Intel® Xeon® E5-2660 2.2Ghz 프로세서(95W TDP)[10]
  • 16GB DRAM – 수량 2, 8GB DDR3 PC3 12800 ECC
  • Intel® Niantic 82599 10Gb Direct Attach SFP+ 이더넷 컨트롤러[10]

4.2    소프트웨어(SW) 구성

이 절에서는 SUT 및 클라이언트 시스템에 대한 소프트웨어 구성과 네트워크 최적화 및 전력 모니터링 구성을 검토한다.

4.2.1    테스트 시스템 구성

Memcached 테스트를 실행하기 위해 다음 소프트웨어가 설치된다.

  • CentOS (Community ENTerprise Operating System), 6.2 버전 – Linux Kernel 2.6.32
  • Libevent 1.4.14b-stable
  • Intel® ixgbe 3.8.14 NIC 드라이버
  • Memcached-1.6.0_beta1 및 Memcached-1.6.0_beta1-bags

소프트웨어를 설치한 후 다음 명령어로 memcached를 실행한다.

 

memcached –p 11211 -u nobody –t <thread> -m 128000 –d

 

이 명령에서 thread 는 memcached에서 실행할 worker thread 개수다. 모든 테스트에서 memcached를 테스트하기 위해 수정되는 유일한 변수는 스레드 개수다. 이 워크로드 드라이버 및 입력 매개변수는 최대 처리량과 확장성을 확인하는 데 활용된다.

 

4.2.2    클라이언트 시스템 구성

다음 소프트웨어로 클라이언트 시스템을 구성한다.

  • CentOS (Community ENTerprise Operating System), 6.2 버전 – Linux Kernel 2.6.32
  • Intel® ixgbe (10GbE) 버전 3.8.14 NIC 드라이버
  • mcblaster

운영 체제 및 네트워크 드라이버 소프트웨어가 설치된 상태에서 synthetic cache lookup 워크로드를 발생시키고 memcached 인스턴스의 성능 (즉, 트랜잭션 처리량, 지연/응답 시간)을 측정하는 데 mcblaster를 사용된다. 이 테스트는 2단계로 구성되어 있다. 먼저 다음을 사용하여 k/v를 memcached 인스턴스에 로드 한다.

 

./mcblaster –p 11211 -t 16 -z 64 –k 1000000 –r 10 –W 75000 –d 20 <memcached host>

 

이 명령은 캐시에 모두 64바이트 값을 포함하는 100만 개의 키를 로드시킨다. 다음 테스트 단계에서는 모든 클라이언트가 일정한 초당 부하 발생 속도에서 이러한 키에 대한 요청을 임의 순서로 동시에 전송하고 해당 속도에서 처리량 (throughput) 과 지연 시간 (latency)을 측정한다.

 

./mcblaster –p 11211 -t 16 -z 64 –k 1000000 –r <rate> –W 0 –d 120 <memcached host>

 

이 테스트에서 memcached의 평균 RTT를 모니터링 하면서 부하 발생률을 증가시킨다. 최대 처리량은 SLA인 1msec 의 평균 RTT를 초과하지 않는 가장 높은 처리량이다.

Memcached ([15] 및 [16]) 성능을 측정한 report를 가이드로 해서, GET 명령 성능을 평가했다. 대부분의 k/v 캐싱 서버는 GET 명령이 대부분인 workload이며 [11], workload mix에 있는 STORE 명령수는 미미하여 GET 성능에 거의 영향을 주지 않는다[16].

4.2.3    네트워크 설정

최대 처리량을 위한 Intel® Hyper-Threading(HT) 설정(사용/사용 안 함) 에 따른 두 가지 네트워크 구성이 있다.

Intel® HT가 사용되지 않을 때는 다음 설정이 사용된다.

  • 링 버퍼 크기를 rx (receive) 및 tx (transact, send) 모두에 대해 96으로 줄임
    • 목표: 지연 감소 (RTT)
    • 명령어
      • ethtool –G ethX rx 96
      • ethtool –G ethX tx 96
  • GRO (generic receive offload) 사용 안 함
    • 목표: 지연 감소(RTT)
    • 명령어: ethtool K ethX gro off
  • set_irq_affinity.sh 셸 스크립트를 실행하여 CPU에 대한 NIC 인터럽트 affinity 조정
    • 목표: 네트워크 인터럽트 경합 감소
    • 명령어: ./set_irq_affinity.sh ethX
    • 참고: 커널에서 irqbalance 서비스가 사용 안 함으로 설정되어 있거나 변경 사항을 덮어쓰는지 확인한다. (killall irqbalance)

이러한 설정은 최신 ixgbe 드라이버 (테스트를 실행한 경우 3.8.14)와 함께 사용할 때 최대의 처리량을 제공한다. 들어오는 패킷이 물리적 CPU 코어를 고르게 향하도록 하여 네트워크 인터럽트의 로드를 분산하는 데 flow detector (ixgbe 드라이버에 포함)가 사용된다. 이렇게 하면 CPU가 여러 코어 간 사용률을 고르게 유지할 수 있다.

Intel® HT 사용된 경우 최적의 네트워크 구성은 물리적인 CPU 1개당 1개의 memcached thread를 구성하고, 2개의 NIC queue가 추가적인 16개의 논리적인 core로 affinity를 조정하는 것이다 (affinity에 대한 자세한 내용은 표2를 참고하라). Memcached process에 대하여 affinity 조정을 하여 구성함으로써, 대부분의 NIC 처리 (network packet 처리)가 memcached 스레드에 인접한 논리적 코어 처리하도록 하여 Intel® HT 을 사용하지 않을 때에 비해 높은 성능을 제공한다.

 

d529c864e98d6251d01884f01e5ecd2f.png

표 2 Intel® HT 기술 memcached 설정의 경우 논리적 코어와 memcached 및 네트워크 트래픽 공유

4.2.4    전력 측정

테스트를 위해 Yokogawa WT210 디지털 전원 측정기로 모니터링 했다. 시스템이 로드 된 상태에서 매 5초마다 전력량을 수집한다. 그리고 평균 갑들은 다음 절의 차트에 있다.

참고로 모든 서버는 비디오 카드가 장착되어 있고 최적이 아닌 냉각 솔루션을 장착하고 있다. 실 데이터 센터 환경에서는 이 글에 소개된 수치에 비해 서버당 전력 소비량이 낮을 것으로 예상된다.

5    요점

성능상 이점을 요약하자면 그림 12는 <1ms RTT (Round-trip time)의 Latency SLA를 유지하는 동안 SUT (system under test) 최대 서버 용량 (Request per second: RPS)을 보여준다. 연구의 일관성을 위해 이러한 측정은 GET 명령과 함께 수행했다.

테스트 결과 최적화된 memcached는 물리적 프로세서당 하나의 스레드에서 최적으로 수행되므로, 16개의 물리적 코어가 있는 SUT에서16개의 스레드를 사용할 때 가장 좋은 성능을 보인다. Intel® HT 및 Turbo 모드 사용시 모두 성능 향상을 보인다.

두 가지 오픈 소스 base 결과는 스레드를 8개에서 16개로 늘릴 때 성능상 이점이 없음을 보여준다 (즉, lock 경합으로 인한 scalability 문제). 최적화된 'Bag' 결과는 Intel® HT 기술 (+8%) 및 Turbo (+23%)를 추가함에 따라 성능이 증가하는 것을 보여준다. Intel® HT 기술을 통한 성능 향상은 동일한 물리적 코어에 HT 기능인 논리적 프로세서가 하나씩 더 추가됨으로써 worker thread와 네트워크 인터럽트를 동시에 context swap없이 처리 할 수 있기 때문이다. 마찬가지로 Turbo 모드는 프로세서 주파수를 높여 상당한 성능 향상을 보인다. 최고 처리량은 Intel® HT 및 Turbo (+31%) 기능을 사용할 때 추가로 성능 향상이 되어 315만 (3.15M) RPS 까지 처리 가능하다. 동일하게 구성된 베이스라인 (0.525M) 에서 최고의 결과3.15M를 비교했을 때 6배의 성능 향상이 있음을 알 수 있다.

그림 13은 그림 12에서 최적화된 4가지 케이스의 memcached 평균 RTT (round-trip time) 이다. 흥미롭게도1ms SLA를 넘자마자 RTT가 감소되는 지점 (각각 260us, 285us, 215us, 205us)들이 있다.

실제로는 RPS 로드에 관계없이 응답 시간 SLA가 300us 미만이 되도록 관리하여 RTT가 감소하는 지점이 없어야 한다. 서버당 처리량을 최대화하기 위해 수백만의 RPS를 처리 가능하게 하도록 "Bags" 사용 서버는 300us 임계 값 하의 일정한 RTT를 보여준다.

그림 14는 중간 값 RTT < 1ms SLA에서 코어 수 증가에 따른 최대 RPS 처리량을 보여준다. 이 테스트에서 Turbo는 사용하고 Intel® HT 기술은 사용하지 않았다. 코어 수가 늘어남에 따라 여러 코어 프로세서에서 작동하는 병렬 데이터 구조 및 알고리즘에 의해 memcached도 선형적으로 확장된다. 이러한 선형적 확장은 차세대 다중 코어 프로세서로 계속 이어질 것으로 예상된다. GET 명령 실행을 위해 스레드 간 non-blocking 처리가 필요하기 때문이다.

 

b592fee350b245e144a1a3c4ea94ba4e.png

그림 12 1밀리초 미만 RTT SLA를 유지한 상태에서 최대 GET 명령 수

 

b75f1749ab323934e9e0f555fbdec42b.png

그림 13 GET RPS 비율 증가에 따른 중간 RTT 값

 

 

79e003e8e3db7f8ff74003c41a3f8ff7.png

그림 14 중간값 RTT가 1밀리초 미만인 SLA에서 코어 수 증가에 따른 최대 RPS 처리량

 

표 3은 최적의 베이스라인 및 최적화된 memcached 결과에 SLA의 최대 로드에서 전원 메트릭스를 추가한다. 6배의 성능 델타 외에도 와트당 성능이 베이스라인에 비해 3.4배 향상된다. 전원 및 냉각이 총 소유 비용의 상당한 부분을 차지하는 데이터 센터에서 와트당 완료되는 명령을 최대화하는 것이 중요하다. 그래픽 카드가 없고 SUT보다 나은 냉각 솔루션을 갖춘 데이터 센터 환경에서는 이러한 서버당 전원 수가 낮다.

 

74b8fe11816240124d59eb178bab3bf0.png

표 3 최대 처리량 < 1ms SLA에서 실행되는 동안 wall에서 전원 측정

6    결론 및 해야 할 일

최적화된 memcached는 베이스라인에 비해 처리량을 6배까지 증가시키며 와트당 성능은 3.4배까지 증가시킨다. 이러한 수치는 반폭 서버 보드에서 전체 chassis (즉, 보드 2개)에서 추정한 값이며 SUT (system under test) 구성은 256GB 메모리를 갖춘 1.5U 서버당 630만 RPS를 제공하며 1.5U 새시당 1TB (32GB * 16 DIMM 슬롯) 메모리까지 확장할 수 있다. 해쉬 테이블 및 LRU를 재설개하여 병렬 데이터 구조를 활용함으로써 속도 향상을 얻을 수 있다. 이러한 데이터 구조를 통해 GET 명령에 대한 모든 lock을 제거하고 SET 및 DELETE 명령에 대한 lock 경합을 완화하여 2소켓 Intel® 시스템에서 1~16개의 코어를 사용하여 측정했을 때 속도가 꾸준히 증가하는 것으로 나타났다.

향후 연구에서는 bag 버전이, 코어 수가 늘어나는 것으로 계속 확장성이 늘어나는지 그리고 공유 구성 요소에서 전원 상환을 통해 와트당 성능 향상이 증가하는 지 등에 대해 4소캣 서버와 마이크로 서버 플랫폼에서 조사할 예정이다.

다음 조사 분야는 캐시 교체 정책이다. 업데이트된 캐시 항목 정렬 및 제거와 cleaner thread의 추가와 함께 새로운 대체 정책을 평가할 수 있다 (예: LFU, ARC (adaptive replacement cache) 및 MQ). Cleaner thread의 주요 기능 향상은 worker thread의 개입 없이 자율적으로 캐시를 관리할 수 있는 특성이 있다. 프로그램 흐름을 수정하지 않고도 새로운 대체 정책 평가를 할 수 있으며 캐시 히트율을 높일 수 있다.

7    감사의 말

이 자료가 나오기까지 수고해주신 프로젝트 팀원인 Rajiv Kapoor (Intel Corporation), George Chaltas (Intel Corporation), Alex Duyck (Intel Corporation), Dean Chandler (Intel Corporation), Mike Bailey (Oregon State University)에게 깊은 감사의 말을 전한다.

8    참고 문헌

[1] P. Saab, "Scaling memcached at Facebook," 12 December 2008. [Online]. Available:https://www.facebook.com/note.php?note_id=39391378919&ref=mf. [Accessed 1 April 2012].

[2] Twitter Engineering, "Memcached SPOF Mystery," Twitter Engineering, 20 April 2010. [Online]. Available:http://engineering.twitter.com/2010/04/memcached-spof-mystery.html. [Accessed 1 April 2012].

[3] Reddit Admins, "reddit's May 2010 "State of the Servers" report," Reddit.com, 11 May 2011. [Online]. Available: http://blog.reddit.com/2010/05/reddits-may-2010-state-of-servers.html. [Accessed 1 April 2012].

[4] C. Do, "YouTube Scalability," Youtube / Google, Inc., 23 June 27. [Online]. Available:http://video.google.com/videoplay?docid=-6304964351441328559. [Accessed 1 April 2012].

[5] memcached.org, "memcached - a distributed memory object caching system," Memcached.org, 2009. [Online]. Available: http://memcached.org/about. [Accessed 27 February 2012].

[6] N. Gunther, S. Subramanyam and S. Parvu, "Hidden Scalability Gotchas in Memcached and Friends," in Velocity 2010 Web Performance and Operation Conference, Santa Clara, 2010.

[7] B. Fitzpatrick, "Distributed caching with memcached," Linux J., vol. 2004, no. 1075-3583, p. 124, 2004.

[8] B. Fitzpatrick, "LiveJournal's Backend A history of scaling," August 2005. [Online]. Available:http://www.slideshare.net/vishnu/livejournals-backend-a-history-of-scaling. [Accessed 1 April 2012].

[9] N. Shalom, "Marrying memcached and NoSQL," 24 October 2010. [Online]. Available:http://natishalom.typepad.com/nati_shaloms_blog/2010/10/marrying-memcache-and-nosql.html. [Accessed 1 April 2012].

[10] Intel Corporation, "ARK - Your Source for Intel Product Information," Intel.com, 2012. [Online]. Available:http://ark.intel.com/. [Accessed 2012 1 April].

[11] B. Atikoglu, Y. Xu, E. Frachtenberg, S. Jiang and M. Paleczny, "Workload Analysis of a Large-Scale Key-Value Store," in ACM SIGMETRICS/Performance 2012 Conference, London, 2012.

[12] J. Preshing, "Locks Aren't Slow; Lock Contention Is," Preshing on Programming, 18 November 2011. [Online]. Available: http://preshing.com/20111118/locks-arent-slow-lock-contention-is. [Accessed 1 April 2012].

[13] B. Agnes, "A High Performance Multi-Threaded LRU Cache," codeproject.com, 3 February 2008. [Online]. Available: http://www.codeproject.com/Articles/23396/A-High-Performance-Multi-Threaded-LRU-Cache.[Accessed 1 September 2011].

[14] M. Spycher, "High-Throughput, Thread-Safe, LRU Caching," Ebay Tech Blog, 30 August 2011. [Online]. Available: http://www.ebaytechblog.com/2011/08/30/high-throughput-thread-safe-lru-caching/. [Accessed 1 September 2011].

[15] D. R. Hariharan, "Scaling Memcached – Harnessing the Power of Virtualization," in VMWorld 2011, Las Vegas, 2011.

[16] M. Berezecki, E. Frachtenberg, M. Paleczny and K. Steele, "Many-Core Key-Value Store," in Green Computing Conference and Workshops (IGCC), 2011 International, Orlando, 2011.

[17] S. Hart, E. Frachtenberg and M. Berezecki, "Predicting Memcached Throughput using Simulation and Modeling," Orlando, 2012.


출처 - http://helloworld.naver.com/helloworld/151047



'Computer Science > Cache' 카테고리의 다른 글

EHCache  (0) 2013.02.11
Hazelcast 소개  (0) 2012.11.06
Memcached 설치 및 사용 방법  (0) 2012.11.06
memcached를 적용하여 사이트 성능 향상  (0) 2012.11.06
Memcached 구현 소개  (0) 2012.11.06
Posted by linuxism
,


memcached를 적용하여 사이트 성능 향상

데이터베이스 및 데이터 소스에서부터 읽기 감소

요약:  오픈 소스 memcached 도구는 디스크나 데이터베이스와 같이 상대적으로 속도가 느린 소스에서부터 정보를 로드하는 것(및 처리하는 것)을 방지하도록 자주 사용하는 정보를 저장하기 위한 캐시입니다. 이는 전용 상황에서나 기존 환경에서 여유 메모리를 다 사용하는 메소드 중 하나로 배치할 수 있습니다. memcached의 간결성에도 불구하고 이는 때때로 올바르지 않게 사용되거나 잘못된 환경 유형에서 솔루션으로 사용됩니다. memcached 사용을 최상으로 활용하는 시점에 대해 알아봅시다.


소개

memcached는 애플리케이션 프로세스의 속도를 높이는 데 사용된다. 여기에서는 사용자의 애플리케이션과 환경 내에서 이를 배치하기 위한 모범 사례에 주목할 것이다. 이는 저장해야 하는 것과 저장하지 말아야 할 것, 데이터의 유연한 분배를 처리하는 방법 및 데이터의 memcached로 저장된 버전을 업데이트하기 위한 메소드를 규제하는 방법이 포함된다. 또한 IBM WebSphere® eXtreme Scale을 비롯한 고가용성 솔루션의 지원에 대해서도 다룰 것이다.

모든 애플리케이션, 특히 많은 수의 웹 애플리케이션은 정보에 액세스하여 클라이언트에 리턴하는 속도를 최적화해야 한다. 하지만, 동일한 정보가 자주 리턴된다. 데이터 소스(데이터베이스 또는 파일 시스템)로부터 데이터를 로드하는 것은 비효율적이며, 정보에 액세스하려 할 때마다 궁극적으로 동일한 쿼리를 실행한다면 특히 비효율적이다.

비록 많은 수의 웹 서버는 정보를 다시 보내기 위해 캐시를 사용하도록 구성할 수 있지만, 이는 대부분 애플리케이션의 동적인 특성과 함께 작동하지 않는다. 이러한 점에서 memcached가 유용할 수 있다. 이는 네이티브 언어객체를 포함하여 모든 것을 보유할 수 있는 일반화된 메모리 저장소를 제공하여, 사용자는 다양한 정보를 저장할 수 있으며 많은 애플리케이션과 환경에서부터 액세스할 수 있다.

기본

memcached는 많은 서버에서 여유 RAM을 활용하도록 설계된 오픈 소스 프로젝트로서, 자주 액세스하는 정보에 대해 메모리 캐시로서 작동한다. 핵심 요소는 캐시(cache)라는 단어의 사용이다. 즉, memcached는 아무 곳에서부터 로드할 수 있는 정보의 임시 스토리지를 메모리 내에 제공한다.

예를 들어, 일반적인 웹 기반 애플리케이션을 살펴보자. 동적으로 동작하는 웹 사이트 조차도 대개 페이지의 사용 내내 일부 컴포넌트나 정보 상수가 있다. 블로그 내에서 개별 블로그 포스트를 위한 카테고리의 목록은 페이지 보기 사이에 정기적으로 변경될 가능성이 낮다. 쿼리를 통해 데이터베이스로 이 정보를 매번 로드하는 것은, 특히 데이터가 변경되지 않을 때에는 비교적 비용이 많이 든다. 그림 1에서 블로그 내에서 캐시될 수 있는 일부 페이지 단편을 볼 수 있다.


그림 1. 일반적인 블로그 페이지의 캐시 가능한 요소
다이어그램에서는 캐시 가능한 블로그 요소 및 레이아웃을 표시한다. 즉, 맨 위의 Page Header, 왼쪽의 Current post lists, 오른쪽의 About, Post History, External Links 및 Category list 등이다. 

이 구조를 블로그, 게시자 정보, 설명의 — 블로그 자체를 포스트하는 경우에도 — 다른 요소로 추론한다. 단지 홈페이지의 내용을 표시하기 위해서 발생하는 10-20개의 데이터베이스 쿼리와 포맷팅을 식별할 수 있다. 이를 매일 수 백 또는 수 천 번 이상의 페이지 보기로 반복하면, 사용자의 서버 및 애플리케이션은 페이지 내용을 표시하는 데 필요한 것보다 훨씬 더 많은 쿼리를 실행할 것이다.

memcached를 사용하면 데이터베이스에서부터 로드된 포맷된 정보를 웹 페이지에서 직접 사용이 가능한 형태로 저장할 수 있다. 그리고 정보가 데이터베이스와 다른 처리를 통해 디스크에서부터가 아니라 RAM에서부터 로드되었기 때문에, 정보로의 액세스도 거의 동시적이다.

다시 반복하면, memcached는 디스크나 데이터베이스와 같이 상대적으로 속도가 느린 소스에서부터 정보를 로드하고 처리하는 것을 방지하도록 자주 사용하는 정보를 저장하기 위한 캐시이다.

memcached로의 인터페이스는 네트워크 연결을 통해 제공된다. 이는 여러 클라이언트와 함께 단일 memcached 서버(또는 이 기사의 후반부에서 시연할 여러 서버)를 공유할 수 있다는 뜻이다. 네트워크 인터페이스는 빠르고, 성능을 향상시키기 위해 서버는 의도적으로 인증 또는 보안 통신을 지원하지 않는다. 하지만 이로 인해 배치 옵션을 제한해서는 안 된다. memcached 서버는 네트워크의 내부에 존재해야 한다. 네트워크 인터페이스가 실용적이고 memcached 인스턴스를 여러 개 배치하기 쉬워서 여러 장비에서 여분의 램을 활용할 수 있고 캐시 전체 크기를 늘릴 수 있다.

memcached를 사용하는 스토리지 메소드는 많은 언어에 사용 가능한 해시 또는 연관 배열과 유사한 단순한 키워드/값 쌍이다. 키와 값을 제공하여 정보를 memcached로 저장하고, 지정된 키로 정보를 요청하여 정보를 복구한다.

정보는 다음 중 하나의 경우가 발생하지 않는 한, 캐시에서 영구적으로 보존된다.

  1. 캐시에 할당된 메모리가 고갈된다 — 이 인스턴스에서 memcached는 LRU(최근에 가장 적게 사용된) 메소드를 사용하여 캐시에서부터 항목을 제거한다. 최근에 사용되지 않은 항목이 캐시에서부터 삭제되며, 오래된 항목부터 액세스된다.
  2. 항목이 구체적으로 삭제된다 — 언제나 항목을 캐시에서부터 삭제할 수 있다.
  3. 항목이 만료된다 — 개별 항목은 만기가 있어서, 키에 대해 저장된 정보가 너무 오래될 가능성이 높을 때에 항목을 캐시에서부터 비울 수 있다.

이러한 상황은 캐시에서의 정보를 최신 상태로 보장하는 애플리케이션 논리와 결합하여 사용할 수 있다. 이러한 내용을 염두에 두고 애플리케이션에서 memcached를 사용하는 최상의 방법에 대해 살펴보자.

memcached를 사용하는 경우

애플리케이션 성능을 개선하기 위해 memcached를 사용할 때 수정 가능한 다양한 핵심 프로세스와 단계가 있다.

정보를 로드할 때에 일반적인 시나리오는 그림 2에 표시된다.


그림 2. 표시하기 위해 정보를 로드하는 일반적인 순서
다이어그램에서는 데이터를 로드하는 것에서부터 데이터를 처리/포맷하여 데이터를 클라이언트에 전송하는 것까지의 흐름을 보여준다. 

일반적인 경우에 단계는 다음과 같다.

  1. 하나 이상의 쿼리를 실행하여 데이터베이스에서부터 정보를 로드한다.
  2. 표시(또는 향후 처리)에 적합한 정보를 포맷한다.
  3. 포맷된 데이터를 사용하거나 표시한다.

memcached를 사용할 때에 캐시를 수용하기 위해 애플리케이션 논리를 약간 변경할 수 있다.

  • 캐시에서부터 정보를 로드하도록 시도한다.
  • 정보의 캐시된 버전이 존재하는 경우에 이를 사용한다.
  • 존재하지 않는 경우:
    1. 하나 이상의 쿼리를 실행하여 데이터베이스에서부터 정보를 로드한다.
    2. 표시 또는 향후 처리에 적합한 정보를 포맷한다.
    3. 정보를 캐시로 저장한다.
    4. 포맷된 데이터를 사용한다.

이는 그림 3에 요약되어 있다.


그림 3. memcached를 사용할 때 표시하기 위해 정보 로드
다이어그램에서는 요청된 데이터가 캐시에 존재하는 경우, 모든 처리 단계를 건너뛰어 시간이 절약된 것을 보여준다. 

이렇게 하면 데이터의 로딩은 대체적으로 3단계 프로세스가 된다. 즉, 캐시나 데이터베이스에서부터 데이터를 로드하고 적절하게 캐시에 저장하는 것이다.

최초로 이 프로세스가 발생하면, 데이터가 정상적으로 데이터베이스나 다른 소스에서부터 로드되고 memcached로 저장된다. 다음에 정보에 액세스하면 데이터베이스에서부터 로드하는 것이 아니라 memcached에서부터 빼내어 시간과 CPU 사이클을 줄여준다.

이 등식의 다른 쪽은 memcached 내에서 저장될 수 있는 정보를 변경하는 경우, memcached 버전을 업데이트하는 동시에 백엔드 정보를 업데이트하도록 보장하는 것이다. 이는 그림 4에 표시된 일반적인 순서를 그림 5에서의 약간의 수정으로 수정한다.


그림 4. 일반적인 애플리케이션에서 데이터 업데이트 또는 저장
다이어그램에서는 데이터를 업데이트하는 것에서부터 데이터를 처리/포맷하여 업데이트된 데이터를 클라이언트에 전송하는 것까지의 흐름을 보여준다. 

그림 5에서는 memcached를 사용하여 수정된 순서를 보여준다.


그림 5. memcached를 사용할 때 데이터 업데이트 또는 저장
다이어그램에서는 데이터를 업데이트하는 것에서부터 데이터를 처리/포맷하여 memcached에 저장하고 업데이트된 데이터를 클라이언트에 전송하는 것까지의 확대된 흐름을 보여준다. 

예를 들면, 기본으로 블로그를 사용하여 블로그 시스템이 데이터베이스에서 카테고리의 목록을 업데이트할 때에 업데이트는 이 순서를 따라야 한다.

  1. 데이터베이스에서 카테고리 목록 업데이트
  2. 정보 포맷
  3. 정보를 memcached에 저장
  4. 정보를 클라이언트에게 리턴

memcached 내에서 스토리지 조작은 원자적이기 때문에, 클라이언트는 부분적인 데이터만 받지 않고 정보가 업데이트되어 이전 버전이나 새 버전을 받게 된다.

대부분의 애플리케이션에서 이 두 개의 조작에 대해 우려해야 한다. 사람들이 사용하는 데이터에 액세스하면 이는 캐시에 자동으로 추가되고 캐시에서 자동으로 업데이트되는 데이터로 변경된다.

키, 네임스페이스 및 값

memcached를 사용하면서 중요한 고려사항은 캐시 내에 저장한 데이터를 어떻게 조직하고 이름을 지정하는가이다. 이전 블로그 예제에서 분명한 것은 일관된 이름 지정 구조를 사용해야 한다는 것이다. 이렇게 하여 블로그 카테고리, 히스토리 및 기타 정보를 로드할 수 있고, 정보를 로드하거나(및 캐시를 업데이트하거나) 데이터를 업데이트할 때에(및 캐시를 다시 업데이트할 때) 이를 사용할 수 있다.

사용자가 사용하는 정확한 이름 지정 시스템은 특정 애플리케이션에 적합하지만, 일반적으로는 대개 고유 ID 종류를 기반으로 하는 기존 애플리케이션의 유사한 구조를 사용할 수 있다. 이는 데이터베이스에서부터 정보를 빼내거나 정보 콜렉션을 대조할 때에 발생한다.

블로그 포스트를 예제로 사용하여 카테고리의 목록을 category-list 키로 항목에 저장할 수 있다. blogpost-29와 같이 포스트 ID에 대해 하나의 포스트로 연관되는 값을 사용할 수 있는 동시에 항목에 대한 설명은 29가 블로그 포스트 ID인 blogcomments-29에 저장할 수 있다. 이 방법에서는 정보를 식별하는 다른 접두부를 사용하여 엄청나게 다양한 정보를 캐시에 저장할 수 있다.

memcached 키/값 저장소의 간결성(및 보안의 부재)이라는 것은, 동시에 동일한 memcached 서버를 사용하여 다수의 애플리케이션을 지원하려는 경우, 특정 애플리케이션에 속한 데이터를 식별하는 다른 수량사의 형태를 사용하는 것을 고려할 수 있다는 의미이다. 예를 들어, blogapp:blogpost-29와 같은 애플리케이션 접두부를 추가할 수 있다. 키는 자유 형식이기 때문에, 키 이름으로 아무 문자열이나 원하는 대로 사용할 수 있다.

값 저장에 대해서는 캐시에 저장한 정보가 애플리케이션에 적합한지 확인해야 한다. 예를 들어, 블로그 시스템을 사용하면 원시 HTML이 아니라 포스트 정보를 포맷하기 위해 블로그 애플리케이션에서 사용한 오브젝트를 저장하려 할 수 있다. 이는 동일한 기본 구조가 애플리케이션 내에서 여러 장소에 사용되는 경우 더 실용적일 수 있다.

Java™, Perl, PHP 및 기타 등등을 비롯하여 대부분의 언어 인터페이스는 memcached 내에서 스토리지에 대해 언어 오브젝트를 직렬화할 수 있다. 이를 통해 사용자는 애플리케이션 내에서 수동으로 재구성하는 것이 아니라 전체 오브젝트를 저장하고 메모리 캐시에서부터 이후에 복구할 수 있다. 많은 오브젝트 또는 이들이 사용하는 구조는 특정한 종류의 해시 또는 배열 구조를 기반으로 한다. JSP 환경과 JavaScript 환경 사이에 동일한 정보를 공유하려 할 때와 같은 교차언어 환경에서는 JSON(JavaScript Object Notation) 또는 심지어 XML과 같이 아키텍처 중립형 포맷을 사용할 수 있다.

memcached를 채우고 사용하기

오픈 소스 제품이자 기존 오픈 소스 환경 내에서 작업하도록 개발된 memcached는 광범위한 환경과 플랫폼에서 지원된다. memcached 서버와 통신하기 위한 인터페이스는 다양하여, 종종 모든 언어에 대해 여러 구현으로 나타난다. 일반적인 라이브러리와 툴킷에 대한 참고자료를 확인한다.

지원되는 제공 인터페이스와 환경을 모두 나열할 수는 없지만, 이들은 모두 memcached 프로토콜로 제공되는 기본 API를 지원한다. 이러한 설명은 간결하며, 다른 값을 사용하여 오류를 표시할 수 있는 다른 언어의 컨텍스트 내에서 다루어야 한다. 기본 함수는 다음과 같다.

  • get(key) — 지정된 키를 저장한 memcached에서부터 정보를 얻는다. 키가 존재하지 않는 경우에 오류를 리턴한다.
  • set(key, value [, expiry]) — 캐시에서 ID 키를 사용하여 지정된 값을 저장한다. 키가 이미 존재하는 경우 업데이트된다. 만료 시간은 초 단위이며, 값이 30일(30*24*60*60) 미만인 경우 상대 시간으로 처리되고, 또는 이 값 이상인 경우 절대 시간(에포크)으로 처리된다.
  • add(key, value [, expiry]) — 키가 존재하지 않는 경우 캐시에 추가하고, 또는 키가 이미 존재하는 경우 오류를 리턴한다. 이는 키가 이미 존재하는 경우 업데이트하지 않고 명시적으로 새 키를 추가하려고 하는 경우 유용할 수 있다.
  • replace(key, value [, expiry]) — 지정된 키의 값을 업데이트하고, 키가 존재하지 않는 경우에 오류를 리턴한다.
  • delete(key [, time]) — 캐시에서부터 키/값 쌍을 삭제한다. 시간을 제공하는 경우, 지정된 기간 동안 이 키로 새 값을 추가하는 것이 차단된다. 제한시간을 통해 데이터 소스에서부터 값을 항상 다시 읽도록 보장할 수 있다.
  • incr(key [, value]) — 지정된 키를 하나씩 또는 지정된 값만큼 증분한다. 수적인 값에서만 작동한다.
  • decr(key [, value]) — 지정된 키를 하나씩 또는 지정된 값만큼 감소시킨다. 수적인 값에서만 작동한다.
  • flush_all — 캐시에서 모든 현재 항목을 무효화(또는 만료)한다.

예를 들어, Perl에서 기본 연산 세트는 Listing 1에 표시된 대로 처리될 것이다.


Listing 1. Perl에서 기본 연산 세트
use Cache::Memcached;

my $cache = new Cache::Memcached {
    'servers' => [
                   'localhost:11211',
                   ],
    };

$cache->set('mykey', 'myvalue');

Listing 2에 Ruby에서의 동일한 기본 연산을 보여준다.


Listing 2. Ruby에서 기본 세트 연산
require 'memcache'
memc = MemCache::new '192.168.0.100:11211'

memc["mykey"] = "myvalue"

두 개의 예제 모두에서 동일한 기본 구조로, memcached 서버를 설정한 다음에 값을 지정하거나 설정하는 것을 볼 수 있다. Java 기술에서 이를 작동하는 것을 비롯하여 다른 인터페이스도 사용 가능하여, 사용자가 WebSphere 애플리케이션에서 memcached를 사용할 수 있다. memcached 인터페이스 클래스를 통해 Java 오브젝트를 바로 memcached로 직렬화할 수 있기 때문에, 복잡한 구조를 저장하고 로드할 수 있다. WebSphere와 같은 환경에서 배치할 때에 두 가지 사항이 매우 중요하다. 즉, 서비스의 복원성(및 memcached를 사용할 수 없는 경우에 해야 할 일)과 WebSphere eXtreme Scale과 같은 여러 환경이나 애플리케이션 서버를 사용할 때 성능을 개선하기 위해 캐시 스토리지를 증가시키는 방법이다. 다음으로 이러한 문제점을 모두 살펴볼 것이다.

복원성 및 가용성

memcached에 대한 가장 일반적인 질문 중 하나는 "캐시를 사용할 수 없을 때 어떠한 일이 발생하는가?"이다. 이전 섹션에서 명확히 한 바와 같이 캐시에서의 정보가 유일한 정보의 소스가 되어서는 안 된다. 다른 위치에서부터 캐시에 저장된 데이터를 로드할 수 있어야 한다.

현실은 애플리케이션의 성능을 저하시키는 캐시로부터 정보에 액세스할 수 없다 하더라도, 이로 인해 애플리케이션의 작동을 중지해서는 안 된다. 다음과 같이 발생 가능한 몇 가지의 시나리오가 있다.

  1. memcached 서비스가 고장나면, 애플리케이션은 원본 데이터 소스로부터의 정보를 로드하고 필요한 경우 표시하기 위해 포맷하도록 폴백(fall back)해야 한다. 또한 애플리케이션은 memcached에서의 정보를 로드하고 저장하기 위해 계속 시도해야 한다.
  2. memcached 서버가 다시 사용 가능하게 되면 애플리케이션이 자동으로 데이터를 저장하도록 시도해야 한다. 캐시된 데이터를 강제로 다시 로드해야 할 필요는 없으며, 사용자는 캐시를 정보로 로드하고 채우는 표준 액세스를 사용할 수 있다. 결과적으로 캐시는 가장 일반적으로 사용되는 데이터로 다시 채워진다.

다시 말해서, memcached는 정보의 캐시이고 유일한 소스가 아니다. memcached 서버를 손실하면 memcached 서버가 백업될 때까지 성능이 저하될 수는 있지만, 그렇다고 해서 애플리케이션을 더 이상 쓰지 못하게 되어서는 안 된다. 실제로 memcached 서버가 상대적으로 간결하고, 손상방지(crash-free)가 아니라고 해도 간결성으로 인해 오류가 더 적게 발생한다.

캐시 분배

memcached 서버는 네트워크 전체에서 키에 대해 값을 저장하는 캐시에 불과하다. 여러 시스템이 있는 경우, 모든 여유 시스템에 memcached 인스턴스를 설정하여 엄청난 양의 네트워킹된 RAM 캐시 스토리지를 제공하고 싶을 것이다.

이 아이디어를 접하게 되면, 시스템 사이에 키/값 쌍을 복사하는 특정 분배 또는 복제 메커니즘의 종류가 필요하다고 가정하려 할 것이다. 이 접근방식의 문제점은 실제로 사용 가능한 RAM 캐시를 늘리는 것이 아니라 감소시키는 것이다. 그림 6을 살펴보면 각각 memcached 인스턴스로 액세스가 있는 세 개의 애플리케이션 서버가 있음을 볼 수 있다.


그림 6. 여러 memcached 인스턴스의 잘못된 사용
다이어그램에서는 각각 1GB 캐시 공간을 양산하는 세 개의 애플리케이션 서버를 지원하는 memcached의 세 개로 분리된 1GB 인스턴스를 보여준다. 

비록 각 memcached 인스턴스의 크기가 1GB(RAM 캐시의 3GB 부여)라고 하더라도 각 애플리케이션 서버가 자체 캐시만 보유하는 경우(또는 memcached 인스턴스 사이에 데이터의 복제가 있는 경우) 전체 설치는 여전히 각 인스턴스에 걸쳐서 1GB의 캐시 복제만 보유할 것이다.

memcached가 네트워크 인터페이스에 걸쳐서 정보를 제공하기 때문에 단일 클라이언트는 memcached 인스턴스에서부터 액세스 권한이 있는 데이터에 액세스할 수 있다. 데이터가 각 인스턴스에 걸쳐서 복사 또는 복제되지 않는 경우, 결과적으로는 그림 7에 표시된 대로 각 애플리케이션 서버에 사용 가능한 3GB RAM 캐시가 나타난다.


그림 7. 여러 memcached 인스턴스의 올바른 사용
다이어그램에서는 전체적으로 3GB 공유 캐시 공간을 양산하는 세 개의 애플리케이션 서버를 지원하는 memcached의 세 개로 된 대화식 1GB 인스턴스를 보여준다. 

이 접근방식의 문제점은 키/값 쌍을 어느 서버에 저장하도록 선택하는 것과 값을 복구하려고 할 때에 어느 memcached 서버와 대화하는지 어떻게 결정하는가이다. 솔루션은 검색 테이블과 같은 복잡도나 memcached 서버가 이 프로세스를 처리할 것이라는 예상을 무시하는 것이다. 그 대신에 memcached 클라이언트는 단순하게 생각해야 한다.

memcached 클라이언트는 이 정보를 판별하기 위해 보유하는 것이 아니라 정보를 저장할 때에 지정한 키에서 단순한 해싱 알고리즘을 사용한다. memcached 서버 목록에서부터 정보를 얻거나 저장할 때에 memcached 클라이언트는 일관된 해싱 알고리즘을 사용하여 키에서부터 수적인 값을 유추한다. 예를 들어 설명하면, mykey 키는 23875 값으로 변환된다. 정보를 저장하는 중이거나 얻는 중이든지 여부와 상관 없이, memcached 서버에서부터 고유 ID를 로드하는 것과 동일한 키를 사용하게 된다. 따라서 이 예제에서 "mykey"는 항상 23875의 값에 해시를 얻게 될 것이다.

두 개의 서버가 있는 경우 memcached 클라이언트는 이 수적인 값에 단순한 계산(예: 계수)을 수행하여 첫 번째나 두 번째로 구성된 memcached 인스턴스에 값을 저장해야 하는지 여부를 결정한다.

값을 저장할 때에 클라이언트는 저장하기 위해 사용된 키와 서버에서부터 해시 값을 결정한다. 값을 얻으면 클라이언트는 키에서부터 동일한 해시 값을 결정하고 정보를 얻기 위해 동일한 서버를 선택한다.

모든 애플리케이션 서버에서 동일한 서버 목록(및 동일한 순서)을 사용하는 경우 모든 애플리케이션 서버는 동일한 키를 저장하거나 검색할지 묻는 경우 동일한 서버를 선택하게 된다. 동일한 1GB의 공간을 복제하는 것이 아니라 현재 3GB의 memcached 공간을 공유하고 있어서, 캐시를 더 많이 사용하도록 부여하여 더 많은 사용자를 위해 애플리케이션의 성능을 개선할 가능성이 높다.

이 프로세스에 복잡도가 있지만(서버를 사용할 수 없는 경우 어떠한 일이 발생하는가와 같이), 문서에서 자세한 정보를 제공한다(참고자료 참조).

대표적인 memcached 오용 사례

memcached는 단순한 도구인데 memcached 인스턴스를 원래 의도와 다른 부적절한 방식으로 사용하고 싶어할 수도 있다.

memcached는 데이터베이스가 아니다

memcached의 가장 일반적인 잘못된 사용은 아마도 캐시가 아니라 데이터 저장소로서 사용하는 것이다. memcached의 기본 용도는, 그렇지 않았으면 다른 소스에서부터 복구되거나 구성될 수 있는 데이터에 대한 응답 시간을 개선하는 것이다. 예로는 데이터베이스에서부터 정보를 복구하는 것이다(특히, 사용자에게 정보가 표시되기 전에 포맷하거나 조작하는 경우). memcached는 메모리에 이 정보를 저장하도록 설계되어, 데이터가 복구될 때마다 해당 태스크의 반복적인 수행을 방지한다.

애플리케이션을 실행하는 데 필요한 유일한 정보의 소스로 memcached를 사용해서는 안 된다. 즉, 데이터는 항상 다른 소스에서도 끌어올 수 있어야 한다. 또한 memcached는 키/값 저장소에 불과하다는 것을 유의하자. 데이터에 걸쳐 쿼리를 수행하거나 정보를 추출하기 위해 내용 전체에 대해 반복할 수는 없다. 이를 대규모 단위에서 사용하는 데 필요한 데이터의 오브젝트나 블록을 저장하기 위해 사용해야 한다.

데이터베이스 행 또는 파일을 캐시하면 안 된다

데이터베이스에서부터 로드되면서 데이터의 행을 저장하기 위해 memcached를 사용할 수 있다 하더라도 이는 실제로 쿼리 캐싱이며, 대부분의 데이터베이스는 자체의 쿼리 캐싱 메커니즘을 제공한다. 이와 동일한 내용을 파일시스템으로부터의 파일이나 이미지와 같은 다른 오브젝트에 대해서도 적용할 수 있다. 많은 수의 애플리케이션과 웹 서버는 이러한 유형의 작업에 대해 이미 훌륭하게 최적화된 솔루션을 보유하고 있다.

로드하고 포맷한 후에 정보의 전체 블록을 저장하기 위해 memcached를 사용하면 이로부터 더 많은 유용성과 성능 개선을 확보하게 된다. 블로그 예제를 보면, 정보를 저장하는 최상의 지점은 블로그 카테고리를 오브젝트로 포맷한 후이거나 심지어 HTML로 포맷한 이후였다. 그 다음에 memcached에서부터 컴포넌트(블로그 포스트, 카테고리 목록, 포스트 히스토리 등)를 로드하고 완성된 HTML을 다시 클라이언트에게 쓰면 블로그 페이지의 구성을 수행할 수 있다.

memcached는 안전하지 않다

최대 성능을 보장하기 위해 memcached는 인증이나 암호화 중 어떠한 형태의 보안도 제공하지 않는다. 이는 사용자의 memcached 서버로의 액세스 권한을 애플리케이션 배치 환경의 동일한 비공개 구역에 두고 처리해야 한다는 의미이다. 보안이 필수인 경우 UNIX® 소켓을 사용하고 현재 호스트 액세스하는 memcached 서버에서 애플리케이션만 허용한다.

이는 유연성과 복원성 및 네트워크에서 여러 시스템에 걸쳐 RAM 캐시를 공유하는 기능이 제거되지만, 이는 이 상황에서 사용자의 memcached 데이터를 보안하는 유일한 솔루션이다.

스스로 제한해서는 안 된다

memcached 인스턴스를 사용해서는 안 되는 사항이 있지만, 그럼에도 불구하고 memcached의 유연성을 무시해서는 안 된다. memcached가 애플리케이션과 동일한 아키텍처 상의 레벨에 있기 때문에, 이를 통합하고 연결하기에 쉽다. 그리고 memcached를 활용하기 위해 애플리케이션을 변경하는 일은 복잡하지 않다. 게다가 memcached가 캐시에 불과하기 때문에, 문제점이 발생할 때에 애플리케이션의 실행을 중지하지 않아도 된다. 올바르게 사용된다면 서버 인프라의 나머지 부분에서 로드를 낮춘다(데이터베이스와 데이터 소스에서부터 읽기 절감). 이는 하드웨어를 더 많이 요구하지 않고도 클라이언트를 더 많이 지원한다는 의미이다.

그러나 이는 단지 캐시에 불과하다는 점을 유의하자!

요약

이 기사에서는 memcached 및 이를 사용하는 최상의 방법에 대해 다루었다. 특히 정보가 저장되는 방법, 합리적인 키를 선택하는 방법 및 저장하기 위해 정보를 선택하는 방법에 대해 살펴보았다. 또한 여러 서버의 사용, memcached 인스턴스가 고장날 때 해야 하는 일과 아마 가장 중요하게도 memcached를 사용하지 않는 방법을 비롯하여 모든 memcached 사용자가 경험하는 일부 키 배치 문제에 대해서도 살펴보았다.

memcached의 성능과 유용성은 오픈 소스 애플리케이션이자 매우 간결하고 쉬운 목표로 되어 있기 때문에, 이 간결성에서부터 나온다. 정보의 광대한 RAM 저장소를 제공하고, 네트워크에서 이를 사용 가능하고 광범위한 다양한 인터페이스와 언어 등을 통해 액세스 가능하게 하여 memcached를 엄청나게 다양한 설치로 통합할 수 있다.


참고자료

교육

  • MySQL memcached 문서는 일반적인 데이터베이스 배치 환경에서 memcached를 사용하는 방법에 대해 많은 정보를 제공한다. 

  • IBM solidDB product family를 확인하여 IBM의 상용 캐싱 솔루션인 solidDB®에 대해 배워보자.

  • developerWorks 팟캐스트에서 소프트웨어 개발자를 위한 흥미로운 인터뷰와 토론을 들을 수 있다. 

  • developerWorks의 기술 행사 및 웹 캐스트를 통해 최신 정보를 얻을 수 있다. 

  • Twitter의 developerWorks 페이지를 살펴보자. 

  • IBM 오픈 소스 개발자에게 유익한 컨퍼런스, 기술 박람회, 웹 캐스트 및 기타 행사를 확인하고 참여하자. 

  • developerWorks 오픈 소스 영역에서는 오픈 소스 기술을 활용하여 개발 작업을 수행하고 이러한 기술을 IBM 제품과함께 사용하는 데 도움이 되는 사용 정보, 도구 및 프로젝트 업데이트와 가장 인기 있는 기사 및 튜토리얼을 확인할 수 있다. 

  • My developerWorks 커뮤니티는 매우 다양한 주제를 다루는 일반 커뮤니티로 성공적으로 운영되고 있다. 

  • 무료로 제공되는 developerWorks On demand demos를 통해 IBM 및 오픈 소스 기술에 대해 배우고 제품 기능을 익히자. 

제품 및 기술 얻기

토론

필자소개

Martin Brown은 8년 넘게 기술 필자로 활약해왔다. Brown은 다양한 주제를 다루는 수 많은 책을 집필했고 기사를 작성했다. Brown은 펄, 파이썬, 자바(Java™), 자바스크립트, 베이직, 파스칼, 모듈라-2, C, C++, 레볼, gawk, 셸 스크립트, 윈도우(Windows®), 솔라리스, 리눅스, BeOS, 맥 OS X을 비롯하여 웹 프로그래밍, 시스템 관리, 통합에 이르리까지 다양한 개발 언어와 플랫폼을 경험했다. Brown은 마이크로소프트(Microsoft®) SME(Subject Matter Expert)이며 ServerWatch.com, LinuxToday.com, IBM developerWorks에 주기적으로 기고한다. Brown은 또한 컴퓨터월드, 애플 블로그, 기타 사이트에 주기적으로 블로그 기사를 올린다. 연락 주소는 Brown이 운영하는 웹 사이트를 참조하기 바란다.


출처 - http://www.ibm.com/developerworks/kr/library/os-memcached/index.html





'Computer Science > Cache' 카테고리의 다른 글

EHCache  (0) 2013.02.11
Hazelcast 소개  (0) 2012.11.06
Memcached 설치 및 사용 방법  (0) 2012.11.06
Memcached의 확장성 개선  (0) 2012.11.06
Memcached 구현 소개  (0) 2012.11.06
Posted by linuxism
,