Memcached는 아주 유명한 오픈소스 인메모리 캐시 솔루션입니다. 많은 사람들이 사용하기에 이미 그 사용법이 굉장히 많이 알려져있습니다. 그런데, 자주 사용하던 사람들이 아니면 잘 모르는 이상한 동작이 memcached 에는 하나 있습니다. 그것이 바로 expire 입니다.
보통 expire를 Memcached 에 셋팅할때는 second 단위로, expire 되어야 할 상대 시간을 넣습니다. 즉 대부분 예상은 아래와 같이 생각하게 됩니다.
expected expire time = current time + set expire time
그리고 이것은 아주 잘 동작합니다. 우리가 30일 이상 expire time 을 설정하기 전까지는…
넵… 주변에서 expire time 을 30일 이상, 즉 60 * 60 * 24 * 30 이상으로 설정하기 전까지는 잘 쓰다가, 왜 30일 이상으로 설정하면 데이터가 바로 사라져서 정상 동작하지 않는다라고 말씀하시는 분들이 속출합니다. 물론 이게 30일인지 아는것도 시간이 걸린다는… 미안해… 에단…
사실 이 문제는 memcached를 오래 다뤄부신 분들은 대부분 한번씩 겪어보는 장애(?) 또는 현상입니다. 왜냐하면 신기하게도 memcached 는 30일이 넘어가는 값에 대해서는 해당 값이 절대 시간이라고 처리를 해버립니다. 왜냐고 묻지 말아주세요. 제가 만든건 아니라서…
먼저 expire 를 처리하게 되는 items.c 안의 do_item_get 을 살펴보도록 하겠습니다. item이 expire 가 되는 경우는 주로 item 에 접근하게 될 때, 시간을 체크합니다.
item *do_item_get(const char *key, const size_t nkey, const uint32_t hv, conn *c) { ...... if (it != NULL) { was_found = 1; if (item_is_flushed(it)) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.get_flushed++; pthread_mutex_unlock(&c->thread->stats.mutex); if (settings.verbose > 2) { fprintf(stderr, " -nuked by flush"); } was_found = 2; } else if (it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.get_expired++; pthread_mutex_unlock(&c->thread->stats.mutex); if (settings.verbose > 2) { fprintf(stderr, " -nuked by expire"); } was_found = 3; } else { it->it_flags |= ITEM_FETCHED|ITEM_ACTIVE; DEBUG_REFCNT(it, '+'); } } ...... }
코드를 보면 it->exptime 이 0이 아니고 it->exptime REALTIME_MAXDELTA 라는 조건이 있습니다. 그리고 그 값은 위에 606024*30 으로 정의되어 있습니다. 즉 30일이죠.
#define REALTIME_MAXDELTA 60*60*24*30 static rel_time_t realtime(const time_t exptime) { if (exptime == 0) return 0; if (exptime > REALTIME_MAXDELTA) { if (exptime <= process_started) return (rel_time_t)1; return (rel_time_t)(exptime - process_started); } else { return (rel_time_t)(exptime + current_time); } }
위의 코드를 보면 exptime 이 0일때는 값을 0으로 리턴합니다. exptime이 0일 때는 expire time이 설정되지 않은 아이템입니다. 이제 그 다음 코드를 보시면 exptime > REALTIME_MAXDELTA 라는 조건이 있습니다. 그리고 그 값은 위에 606024*30 으로 정의되어 있습니다. 즉 30일이죠.
그래서 설정한 exptime이 30일이 넘어가면, extpime <= process_started 조건에 만족하면 그냥 1로 셋팅합니다. 즉 1초 뒤에 다 지워지는 겁니다. process_started 는 프로세스가 처음에 시작한 시간입니다. 그리고 REALTIME_MAXDELTA 보다 적으면, current_time 에 exptime을 저장합니다. 즉 상대시간으로 인식이 되는 거죠.
그리고 마지막으로 current_time은 clock_handler 함수에서 설정되어집니다. 아래와 같이 current_time 에서는 이미 process_started 가 빠져 있습니다.
static void clock_handler(const int fd, const short which, void *arg) {
……
struct timeval tv;
gettimeofday(&tv, NULL);
current_time = (rel_time_t) (tv.tv_sec – process_started);
……
}
그럼 30일 이상의 값을 셋팅하고 싶을 때는 어떻게 해야 할까요? 넵 바로 그날짜에 맞는 시간값을 넣어주시면 됩니다. 예를 들어 지금부터 한달 뒤인 2016/11/22 에 맞는 unixtimestamp 로 설정하시면 됩니다. 즉 오늘 날짜를 구해서 거기에 원하는 날짜 만큼 더한 unixtimestamp로 expire time을 설정하시면 제대로 된 expire를 설정할 수 있습니다.
자 이제, memcached의 expire time을 설정할때는 항상 주의하셔야 합니다. 반대로 Redis 는 그냥 -_- 정한 값이 expire time 입니다. Redis 에서는 이런 이슈는 없습니다.