오래간 만에 memcached 소슬 보니, 너무 잘못 이해하고 있거나 한것들이 많아서 처음부터 새로 보면서 이번에는 기록을 좀 남겨둘려고 합니다. 흔히들 memcached가 내부적으로 메모리를 어떻게 관리하는지 잘 아시지만, 코드 레벨로는 잘 모르실 수도 있기 때문에 그냥 정리합니다.
먼저 간단히 용어를 정리하자면…
- chunk_size : key + value + flag 정보를 저장하기 위한 최소 크기: 기본 48
- factor : item size 크기를 얼마만큼씩 증가시킬지 결정하는 값: 기본 1.25
- Chunk_align_bytes : chunk 할당시에 사용하는 align : 8
- item_size_max: 최대 item의 크기: 기본 1MB
그리고 사이즌 chunk_size * 1.25^(n-1) 형태로 증가하게 됨.
이제 slab.c를 보면 slabclass_t 를 볼 수 있습니다.
typedef struct { unsigned int size; /* sizes of items */ unsigned int perslab; /* how many items per slab */ void *slots; /* list of item ptrs */ unsigned int sl_curr; /* total free items in list */ unsigned int slabs; /* how many slabs were allocated for this class */ void **slab_list; /* array of slab pointers */ unsigned int list_size; /* size of prev array */ unsigned int killing; /* index+1 of dying slab, or zero if none */ size_t requested; /* The number of requested bytes */ } slabclass_t; static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];
MAX_NUMBER_OF_SLAB_CLASSES 는 201로 정의되어 있습니다. 즉 최대 201개의 slabclass 가 만들어지는데, 실제로는 chunk_size 와 factor 값에 따라서 최대 item_size_max를 넘어가 버리면, slabclass는 거기까지만 사용됩니다.(slab_init 를 보면 쉽게 알 수 있습니다.)
/** * Determines the chunk sizes and initializes the slab class descriptors * accordingly. */ void slabs_init(const size_t limit, const double factor, const bool prealloc) { int i = POWER_SMALLEST - 1; unsigned int size = sizeof(item) + settings.chunk_size; ...... ...... while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) { /* Make sure items are always n-byte aligned */ if (size % CHUNK_ALIGN_BYTES) size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); slabclass[i].size = size; slabclass[i].perslab = settings.item_size_max / slabclass[i].size; size *= factor; if (settings.verbose > 1) { fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n", i, slabclass[i].size, slabclass[i].perslab); } } power_largest = i; slabclass[power_largest].size = settings.item_size_max; slabclass[power_largest].perslab = 1; if (settings.verbose > 1) { fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n", i, slabclass[i].size, slabclass[i].perslab); } ...... ......
위의 소스에 나오는 size는 slab별로 할당되는 기본 사이즈의 크기이고 위의 slabclass 구조체에는 item_size_max(기본 1MB) 를 넣어주고, perslab 에는 item_size_max / size로 몇개의 아이템이 들어갈 수 있는지 들어가게됩니다.
그리고 이 slab 안에 array로 item 들이 할당되게 됩니다. 기본적으로 array의 크기는 16으로 설정되고 그 뒤로는 2배씩 증가하게 됩니다. 관련 함수는 grow_slab_list를 보시면 됩니다. 그리고 slab에서 사용하는 chunk가 항상 item_size_max 인것은 아니고, size * perslab으로 될 때도 있습니다.(do_slabs_newslab 에서 확인 가능, memory_allocate 를 이용함)
static int do_slabs_newslab(const unsigned int id) { slabclass_t *p = &slabclass[id]; int len = settings.slab_reassign ? settings.item_size_max : p->size * p->perslab; char *ptr; if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) || (grow_slab_list(id) == 0) || ((ptr = memory_allocate((size_t)len)) == 0)) { MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id); return 0; } memset(ptr, 0, (size_t)len); split_slab_page_into_freelist(ptr, id); p->slab_list[p->slabs++] = ptr; mem_malloced += len; MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id); return 1; }
slabclass 는 여기까지 하고, 다음번에는 실제 item 의 추가 삭제시에 어떻게 되는가에 대해서 정리해보도록 하겠습니다.