Aerospike的内存管理

Aerospike使用jemalloc进行内存分配和管理。利用了jemalloc提供的扩展特性来更细力度的进行内存控制。

jemalloc的扩展能力

  1. jemalloc提供扩展接口来创建独立内存管理单元arena。可基于该arena进行内存申请和释放,arena之间的内存管理无任何冲突。

    • mallctl("arenas.extend", &arena, &arena_len, NULL, 0),用于创建arena
    • mallocx(size, arena_flags)用于在指定arena上申请内存空间
    • free(p)释放接口与标准接口无差。
  2. jemalloc提供扩展接口来创建线程级别的cache(tcache),分配内存空间时,可要求优先使用tcache进行空间分配(tcache空间优先),使用tcache的好处是,完全无冲突。因为一个arena还是有可能被分享给不同的线程共享使用,还是存在共享冲突的可能。

    • mallctl("tcache.create", &tcache, &len, NULL, 0),用于创建tcache
  3. jemalloc提供内存状态信息输出,方便查看定位内存使用情况:

    • mallctl("stats.allocated", &allocated, &len, NULL, 0),获取分配的内存数量
    • mallctl("stats.active", &active, &len, NULL, 0),获取分配的active pages信息

Aerospike如何使用jemalloc

下图为Aerospike使用jemalloc的大致关系:

  1. Aerospike启动时,创建了150个arena(#define N_ARENAS 150),线程在使用jemalloc进行内存分配时,会在第一次调用时,以round-robin的方式attach到其中一个arena上,后续的内存管理动作均基于该arena完成。

  2. Aerospike为每一个对应的namespace创建了一个指定的arena。当namespace指定了data-in-memory参数时(record数据将在内存中保留一份),此时该namespace的record内容,都将关联到该arena上来完成内存分配。

在具有一个namespace的实验环境下,我们可以看到,server总共分配出151(150+1)个arena,通过mallctl("arenas.narenas",...)进行统计,结果如下:

使用jemalloc的心得

之所以Aerospike以上述形式进行内存管理,是根据Aerospike研发自身的经验总结:

  1. 对于大多数在内存处理任务来说,per thread arena已经非常好了,因为大多数内存会在当前线程中申请,并在当前线程中释放( This works well with a purely-transactional model where the data is allocated and relatively quickly freed on the same thread. )

  2. 但是对于数据存储而言,由于数据访问的存储周期会跨多个线程,基于namespace的内存管理(以namespace为单位来构建arena,并基于该arena来管理内存)会更加有效( The main database object store, however, follows entirely different allocation patterns. These objects are usually persistent, potentially much larger, and may be accessed over time by many different threads (and sometimes are even accessed concurrently.) Therefore, the best pattern we have found is to have objects with the same general characteristics (i.e., those within the same Aerospike namespace, which roughly corresponds to a database )

Aerospike在使用jemalloc的总结可参考:In-Memory Computing At Aerospike Scale: When To Choose And How To Effectively Use JEMalloc。从文章数据来看,Aerospike改用jemalloc之后,内存使用效率变高,更加稳定:

After selecting a better dynamic memory allocator and changing the server to use the allocator effectively in a multi-threaded context, the system memory use per node quickly “flatlines” on the right side (with some residual “bouncing” as memory as additional memory is transiently allocated and given back to the system.)

同时,Aerospike还利用了jemalloc扩展接口,来统计进程的内容使用情况alloc.c::cf_alloc_heap_stats():

  • mallctl("stats.allocated", ...)
  • mallctl("stats.active",...)
  • mallctl("stats.mapped",...)

Aerospike后台会定时输出该类信息:

通过asinfo命令也可以直接获取该信息:

Aerospike自身的内存管理结构arenax

上面我们提到,Aerospike会为每个namespace创建一个对应的arena。在设置了data-in-memory时,这个arena用于管理该namespace的所有record的内存。请注意,这个arena只负责管理record部分,也就是value部分的内存。而无论是否设置data-in-memory,用于组织key的sprig(红黑树索引结构),并没有使用jemalloc提供的arena来管理。由于sprig的树节点(as_index)结构为定长的64byte,Aerospike使用了自己设计的arenax结构来管理该定长数据。

上图为arenax的组织结构。arenax为两层内存分配方式结构:

  • 第一层为stage,总共有256个stage,每个stage指向一块定长的内存区域(1G)。
  • 第二层,则为stage指向的这片内存区域,该片区域根据as_index的大小被切分成1677721块(1G/64)。
  • arenax维护了一个free list。当有as_index结构free时,则放回到该free list的头部。arenax实际维护的是该free list的头。
  • 当有malloc请求时,arenax首先会查看free list里面是否还有空闲slot,如果有的话,则将free list头上的slot返回给调用方。
  • 如果free list没有空闲slot,则从根据当前stage_id(at_stage_id)以及对应的element_id(at_element_id)拿到可用的slot,将该slot返回给调用方,并顺势调整at_stage_id, at_element_id。
  • 返回给调用方的是cf_arenax_handle(uint64_t),该句柄并不是内存地址,而是一个两级索引,前36位为第1级stage索引,后28位为第2级elemnt索引。在使用时,需要通过handle调用接口转换出指定的内存指针: void* cf_arenax_resolve(cf_arenax* arena, cf_arenax_handle h)
  • 光从arenax的组织来看,单台server,一个namespace的索引节点上限(as_index)数量:stage_count * slot_count_per_stage = 256*16777216 = 4294967296。基本上算无上限。
  • 为区分非法handlestage[0]slot[0]不做分配,初始状态是at_stage_id = 0, at_element_id = 1
文章目录
  1. 1. jemalloc的扩展能力
  2. 2. Aerospike如何使用jemalloc
    1. 2.1. 使用jemalloc的心得
  3. 3. Aerospike自身的内存管理结构arenax
|