콘텐츠로 이동

(KO) CUBRID AREA 할당기 — 동일 크기 객체에 자리를 떼어 주는 슬랩(slab) 풀

목차

범용 malloc은 어떤 서브시스템이 동일한 크기의 레코드를 초당 수천 개씩 할당하고 풀어 내는 자리에서는 잘못된 도구다. 두 가지 비용이 큰 자리를 차지한다. 하나는 청크마다의 장부 비용이다. 24바이트짜리 DB_VALUE 한 개에 16~32바이트의 할당기 메타데이터가 들러붙으면, 워킹셋이 두 배로 부풀어 오른다. 다른 하나는 단편화다. 풀려 난 셀은 범용 할당기의 size-class 칸으로 돌아가, 다른 모든 클래스와 캐시 라인과 TLB 엔트리를 두고 다툰다. 방금 풀어 낸 셀이 바로 다시 쓰일 가능성도 거의 없다.

교과서적인 답이 슬랩 할당기(slab allocator)다. Jeff Bonwick이 The Slab Allocator: An Object-Caching Kernel Memory Allocator(USENIX Summer 1994)에서 내놓았고, 그 뒤 Linux 커널이 SLAB / SLUB / SLOB로 다듬어 두었다. 골격은 단순하다. 이어진 메모리 자리(슬랩 또는 블록)를 한 가지 크기의 셀로 미리 잘라 두고, 셀마다 free / in-use를 비트 한 자리로 적어 두며, 슬랩이 차면 다음 슬랩을 사슬에 매단다. 핫 패스에서는 전역 할당기를 절대로 건드리지 않는 식이다.

Database Internals(Petrov)의 4장 “Implementing B-Trees, Database System Concepts(Silberschatz, Korth, Sudarshan, 6판)의 13장이 모두 슬랩/풀 할당을 작고, 모양이 같고, 회전이 빠른 레코드”의 기본 규율로 끌어들인다. 벌크 로드 도중의 인덱스 노드, 커밋 시점의 로그 레코드, 컴파일 도중의 파스 트리 노드, 평가 단계의 식 atom 같은 것들이 그 식구다.

이 모델이 풀어 두는 구현 선택지가 셋이다. 본 문서의 나머지가 이 셋을 따라간다.

  1. 슬랩을 한 개로 둘 것인가, 여러 개로 갈라 둘 것인가. Bonwick의 커널 슬랩은 코어 사이로 늘어나도록 CPU별 매거진을 둔다. 임베디드와 데이터베이스 엔진은 객체 클래스마다 사슬 하나만 두고, 그 안에서 lock-free로 다툼을 흡수하는 길로 자리 잡는 일이 잦다. CUBRID는 후자다.
  2. 셀마다의 free 비트는 어디에 둘 것인가. 셀 안에 끼워 넣는 freelist는 포인터 한 번 읽기로 끝나 값싸지만, 풀어 낸 셀을 더럽히고 동시 환경에서는 ABA 위험을 짊어진다. 옆에 따로 둔 비트맵은 셀을 깨끗하게 두고, lock-free CAS로 굴리기 좋다. CUBRID는 비트맵(LF_BITMAP)을 고른다.
  3. 할당기가 비어 있는 슬랩을 어떻게 찾는가. 사슬을 처음부터 훑으면 O(n)이다. 스레드별 캐시는 그것을 O(1)로 줄여 주지만 스레드 사이의 불균형이라는 대가가 따른다. hint 포인터 하나만 두는 길이 가장 값싸다. CUBRID는 hint(area->hint_block)를 고른다.

엔진다운 엔진치고 핫 패스의 같은 크기 할당을 어떤 모양의 풀로든 감싸지 않는 곳은 없다. 이 어휘는 Bonwick의 원 논문에 나오지 않는다. 커널 슬랩과 SQL 엔진 사이에서 자라난 공학 어휘다.

클래스마다의 풀, 담는 내용으로 이름을 매단다

섹션 제목: “클래스마다의 풀, 담는 내용으로 이름을 매단다”

풀은 일반화된 도구가 아니다. PostgreSQL의 MemoryContext(AllocSetContext, SlabContext, GenerationContext), MySQL의 MEM_ROOT(THD가 들고 있고 구문 단위로 도는), CUBRID의 AREA * — 모두 name 필드를 두고, 자기 서브시스템의 전역 포인터(Value_area, tp_Domain_area, Set_Ref_Area, …)에 매달려 있다. 이름이 있어야 누수가 눈에 들어온다. 런타임 덤프에서 어느 풀이 부풀었는지가 곧장 드러난다.

트랜잭션이 아니라 “단계”에 묶이는 수명

섹션 제목: “트랜잭션이 아니라 “단계”에 묶이는 수명”

풀의 진짜 값은 한꺼번에 비우기 프리미티브에 있다. PostgreSQL은 구문 끝에서 MemoryContextReset을 부르고, MySQL은 free_root(MEM_ROOT*, MYF_FLAGS)를, CUBRID는 area_flush를 부른다. 셋 모두 모든 블록을 한 번 훑으며 풀어 낸다. 규율은 한 줄이다. 마음껏 잡아 두었다가 단계 경계에서 한꺼번에 비운다. 그 단계의 정의는 서브시스템이 짊어진다.

셀 추적 — 비트맵인가, 셀 안에 끼워 둔 freelist인가

섹션 제목: “셀 추적 — 비트맵인가, 셀 안에 끼워 둔 freelist인가”

두 길이 함께 산다. 셀 안에 끼워 둔 freelist는 풀어 낸 셀의 앞 8바이트에 다음 free 셀을 가리키는 포인터를 적는다(PostgreSQL AllocSet, MySQL MEM_ROOT, 거의 모든 malloc이 그렇다). 옆에 둔 비트맵은 워드 배열을 따로 두어 점유 여부를 적어 둔다. 셀이 깨끗하게 남고, 메모리 hop 한 번이 더 든다. CUBRID는 후자를 골랐다. 비트맵 연산은 32비트 워드에 대한 CAS로 lock-free로 돌리기 쉬운 반면, 셀 안 freelist는 ABA 문제를 안고 가야 한다.

lock-free 빠른 길과 뮤텍스로 막은 느린 길

섹션 제목: “lock-free 빠른 길과 뮤텍스로 막은 느린 길”

엔진다운 엔진은 거의 모두 비어 있는 셀 찾기의 빠른 길을 lock-free로, 새 슬랩 잡기의 느린 길을 굵직한 락으로 갈라 둔다. CUBRID에서는 그 경계가 분명하다. area->area_mutex는 새 AREA_BLOCK을 매다는 자리에서만 잡히고, 블록 안의 lock-free LF_BITMAP::get_entry가 핫 패스를 떠맡는다.

가변 크기 셀은 “size class별 풀”로

섹션 제목: “가변 크기 셀은 “size class별 풀”로”

CUBRID는 한 AREA 안에서 여러 크기를 함께 굴리지 않는다. AREA는 area_create 자리에서 셀 크기 한 가지로 못 박히고, 서브시스템은 자기에게 필요한 크기 종류만큼 따로 AREA를 만든다. Linux 커널 슬랩 캐시도 같은 길을 간다.

이론적 개념CUBRID 명칭
클래스별 풀AREA 구조체(area_alloc.h); 객체 클래스마다 전역 한 개(Value_area, tp_Domain_area, …)
같은 크기 셀의 슬랩AREA_BLOCK(area_alloc.h); block_size = element_size * alloc_count
셀 점유 추적AREA_BLOCK::bitmap(lockfree_bitmap.hppLF_BITMAP)
슬랩 사슬AREA::blockset_listAREA_BLOCKSET_LIST::items[]AREA_BLOCK *
느린 길의 락AREA::area_mutex(area_alloc의 새 블록 잡기 갈래와 area_insert_block 안에서만 잡힌다)
빠른 길의 hintAREA::hint_block(단일 AREA_BLOCK *)
한 번에 비우기 / 부수기area_flush, area_destroy
모든 풀의 전역 등록부static AREA *area_List + pthread_mutex_t area_List_lock
디버그용 double-free / use-after-free 잡기셀 prefix의 AREA_PREFIX_INITED(0) / AREA_PREFIX_FREED(0x01010101) 태그(!NDEBUG)
블록당 셀 수(설정)AREA::alloc_count(LF_BITMAP_COUNT_ALIGN이 32의 배수로 올림)
블록셋당 블록 수AREA_BLOCKSET_SIZE = 256(컴파일 타임 상수)

AREA 모듈은 헤더 한 개와 .c 한 개, 합쳐 약 1000줄짜리 단출한 모듈이다. 공개 함수는 일곱(area_init, area_final, area_create, area_destroy, area_alloc, area_free, area_flush)에 진단용 area_validate, area_dump까지 합쳐 아홉이다. 자료 구조는 셋(AREA, AREA_BLOCK, AREA_BLOCKSET_LIST)이다.

뚜렷한 선택은 여섯 가지다. (1) 모든 풀을 묶는 전역 단일 연결 리스트(area_List)를 두어 엔진 전체에서 수명 관리를 한 모양으로 잡는다. (2) 슬랩 사슬 자체도 청크화되어 있다. 256개의 블록 포인터를 안고 있는 블록셋이 그 단위다. 사슬이 수백 블록으로 자라도 lookup 구조가 캐시 친화적으로 머문다. (3) 블록마다 앞단에 lock-free 셀 할당을 떠맡는 LF_BITMAP이 자리한다. (4) 단일 hint_block 포인터가 흔한 경우를 비트맵 연산 한 번으로 끝낸다. (5) 뮤텍스는 사슬을 한 블록만큼 늘리는 자리에서만 잡힌다. alloc/free 핫 패스에서는 절대 잡히지 않는다. (6) 디버그 빌드는 셀마다 4바이트(또는 8바이트) prefix를 두어 double-free를 잡는다.

flowchart LR
  subgraph G["전역 등록부"]
    AL["area_List<br/>(static AREA *)"]
    ALK["area_List_lock<br/>(pthread_mutex_t)"]
  end
  subgraph A["AREA (객체 클래스 하나당 한 개)"]
    AH["name, element_size,<br/>alloc_count, block_size"]
    AHB["hint_block →"]
    AM["area_mutex<br/>(사슬을 늘릴 때만)"]
    ABL["blockset_list →"]
  end
  subgraph BS["AREA_BLOCKSET_LIST (256개 포인터씩의 사슬)"]
    BS1["BLOCKSET #0<br/>items[0..255]<br/>used_count"]
    BS2["BLOCKSET #1<br/>items[0..255]<br/>used_count"]
    BSn["…"]
  end
  subgraph B["AREA_BLOCK (슬랩 한 개)"]
    BBM["LF_BITMAP<br/>(셀별 free/used)"]
    BBD["data[]<br/>(element_size 셀이 alloc_count 개)"]
  end
  AL --> A
  AL -. next .-> A
  A --> ABL
  ABL --> BS1
  BS1 -. next .-> BS2
  BS2 -. next .-> BSn
  BS1 --> B
  BS2 --> B
  AHB --> B

이 그림에 그어진 경계는 셋이다. (전역과 AREA 단위) 프로세스 전체에 걸린 area_List가 풀들을 사슬로 묶는다. lookup은 코드 경로 기준이다. 서브시스템마다 자기 AREA * 전역을 들고 있고, 이름으로 찾아가는 식이 아니다. (AREA 단위와 블록셋 단위) 슬랩 사슬은 두 단이다. 블록셋 배열의 사슬이라는 모양 덕에, 비어 있는 셀을 찾으러 사슬을 훑을 때 set 단위 메타데이터가 닿는 캐시 라인이 최대 ⌈N / 256⌉ 줄에 그친다. 블록 하나하나로 건너뛰는 비용도 흩어진다. (블록 단위와 셀 단위) 한 블록 안에서 스레드가 다투는 자료 구조는 비트맵뿐이다. 셀은 인덱스로 닿는다.

// AREA — src/base/area_alloc.h
struct area
{
AREA *next; /* link in the global area_List */
char *name; /* diagnostic only (area_dump) */
size_t element_size; /* cell size, including !NDEBUG prefix */
size_t alloc_count; /* cells per block (32-aligned) */
size_t block_size; /* element_size * alloc_count */
AREA_BLOCKSET_LIST *blockset_list; /* head of slab-pointer chain */
AREA_BLOCK *hint_block; /* hint for the fast path */
pthread_mutex_t area_mutex; /* serialises adding a new block */
/* for dumping */
size_t n_allocs;
size_t n_frees;
void (*failure_function) (void); /* OOM hook (ws_abort_transaction in client) */
};

failure_function 필드는 풀을 들고 있는 서브시스템마다 OOM 회복 훅을 다르게 끼우게 해 준다. 클라이언트는 이 자리를 ws_abort_transaction에 묶어 둔다. 그 덕에 area_alloc_block 안의 malloc이 실패하면 호출 스택이 NULL을 떠받칠 채비도 안 된 상태에서 무너지는 대신 지금 트랜잭션이 abort된다. 서버는 이 자리를 NULL로 두고 에러 코드만 호출자 쪽으로 흘려 올린다.

area_create에 넘긴 element_size는 두 단계로 조용히 손이 가해진다. 디버그 빌드에서는 셀마다 AREA_PREFIX_SIZE(sizeof(double) = 8바이트) 헤더가 더해져 double-free 잡기에 쓰인다. 빌드와 무관하게 크기는 8의 배수로 올림된다. 그래서 호출자가 24바이트짜리 셀을 청해도 릴리스 빌드에서 32바이트, 디버그에서도 32바이트(24 + 8)가 잡힌다. 셀 페이로드의 시작은 디버그에서 data + i*element_size + AREA_PREFIX_SIZE, 릴리스에서 data + i*element_size다.

// AREA_BLOCK — src/base/area_alloc.h
struct area_block
{
LF_BITMAP bitmap;
char *data;
};

포인터 둘 분량의 메타데이터에 이어 block_size 바이트의 셀 자리가 따라온다. 블록 자체는 block_size + sizeof(AREA_BLOCK) 크기로 한 번에 malloc되며, data 포인터는 그 뒤에 이어지는 셀 영역을 가리키도록 맞춰진다. 블록을 풀어 내는 일은 bitmap.destroy()로 비트맵 자체 버퍼를 풀어 준 뒤 free_and_init 한 번이면 끝난다.

// area_alloc_block — src/base/area_alloc.c
static AREA_BLOCK *
area_alloc_block (AREA * area)
{
AREA_BLOCK *new_block;
size_t total = area->block_size + sizeof (AREA_BLOCK);
new_block = (AREA_BLOCK *) malloc (total);
if (new_block == NULL)
{
er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, total);
if (area->failure_function != NULL)
(*(area->failure_function)) ();
return NULL;
}
new_block->bitmap.init (LF_BITMAP_LIST_OF_CHUNKS,
(int) area->alloc_count, LF_AREA_BITMAP_USAGE_RATIO);
new_block->data = ((char *) new_block) + sizeof (AREA_BLOCK);
return new_block;
}

곧장 보이지 않는 두 가지가 있다. 첫째, 비트맵은 LF_BITMAP_LIST_OF_CHUNKS 모양으로 초기화된다. 안쪽으로 들어가 보면 32셀짜리 청크마다 자기 start_idx를 따로 두는 셈이라, 서로 다른 스레드의 동시 get_entry 호출이 라운드로빈으로 다른 청크에 떨어지면서 같은 32비트 CAS 워드 위에서 부딪히지 않는다. 둘째, 사용률 임계값(LF_AREA_BITMAP_USAGE_RATIO)이 서버와 클라이언트에서 다르다. SERVER_MODE에서는 LF_BITMAP_95PERCENTILE_USAGE_RATIO, 그 외에서는 LF_BITMAP_FULL_USAGE_RATIO다. 95퍼센트 임계값이라는 뜻은 서버 모드 블록은 95%가 차면 가득 찬 것으로 본다는 뜻이다. 남은 5%의 여유 자리는 마지막 몇 셀에서 일어날 CAS 다툼을 줄여 주고, 메모리 사용이 약간 늘어나는 대가를 치른다. 한 스레드만 도는 클라이언트는 그 부담이 없으니 100%까지 채우는 길을 고른다.

// AREA_BLOCKSET_LIST — src/base/area_alloc.h
#define AREA_BLOCKSET_SIZE 256
struct area_blockset_list
{
AREA_BLOCKSET_LIST *next;
AREA_BLOCK *items[AREA_BLOCKSET_SIZE];
int used_count;
};

블록셋 한 개에는 최대 256개의 블록 포인터가 들어가고, AREA_BLOCK * 값이 오름차순으로 정렬되어 있다(이 값은 그 블록의 셀 저장 영역 가운데 가장 낮은 주소이기도 하다. 상수 오프셋만 다르다). 두 단 구조를 고른 까닭은 다음과 같다.

  • 블록을 모두 단순 연결 리스트로 묶어 두면 area_find_block(area_validatearea_free가 부른다)이 셀 포인터의 주인을 찾으러 모든 블록을 O(N)으로 훑어야 한다.
  • 블록을 단일 배열에 두면 늘어날 때마다 realloc이 필요해진다. 배열을 들고 있던 동시 reader가 stale 포인터로 풀어진 메모리를 더듬는 위험이 따라온다.
  • 블록셋 사슬은 양쪽의 좋은 점만 가져온다. 블록셋마다는 고정 크기 배열이라 area_find_block에서 binary search가 가능하고, 자라는 일은 새 블록셋을 사슬 끝에 매다는 식이라 in-place realloc이 없다. reader는 늘 안전하다.

대가는 블록셋이 거의 비어 있어도 2 KB를 차지한다는 점이다. 일반적인 AREA 블록 256개 기준으로(블록당 기본 321024셀 × 셀당 8~256바이트 = 블록당 8 KB ~ 256 KB), 블록셋 오버헤드는 워킹셋의 1% 미만이다.

flowchart LR
  S["area_alloc(area)"] --> H["hint_block.bitmap.get_entry()"]
  H --> H1{"entry != -1?"}
  H1 -- "예 (빠른 길)" --> RET["pointer = data + idx*element_size<br/>(디버그면 + AREA_PREFIX_SIZE)"]
  H1 -- "아니오" --> SC["블록셋 사슬 훑기<br/>각 블록: bitmap.get_entry()"]
  SC --> SC1{"찾았는가?"}
  SC1 -- "예" --> CAS["기회적<br/>ATOMIC_CAS_ADDR hint = block"]
  CAS --> RET
  SC1 -- "아니오" --> LK["area_mutex 잠금"]
  LK --> NB["area_alloc_block (malloc)"]
  NB --> INS["area_insert_block (정렬 append)"]
  INS --> SETH["hint_block = new_block"]
  SETH --> UL["unlock"]
  UL --> RET

세 걸음으로 흘러간다. 1단계(lock-free) — hint 블록을 먼저 두드린다. LF_BITMAP::get_entry는 청크 워드 한 자리에 대한 CAS 한 번으로 끝나며, 셀 인덱스 하나를 돌려주거나 가득 찼다면 -1을 돌려준다. 2단계(lock-free) — 모든 블록셋을 VOLATILE_ACCESS 읽기로 훑어 본다. blockset->next->used_count를 그렇게 읽는다. area_insert_block에서 used_count에 거는 ATOMIC_INC_32가 그 직전의 items[] 저장에 대한 release barrier 역할을 하므로, 동시 append도 추가 동기화 없이 그대로 보인다. 3단계(뮤텍스 보유) — 새 블록을 malloc하는 일은 한 번에 한 스레드만 한다. 진 쪽은 다시 시도할 때 새 블록을 만나게 된다.

2단계 안에 가벼운 hint 갱신이 한 자리 숨어 있다. 어떤 할당기가 hint가 아닌 다른 블록에서 비어 있는 셀을 찾았고 마침 hint가 가득 찬 상태라면, ATOMIC_CAS_ADDR(&area->hint_block, hint_block, block)을 시도한다. 이 CAS가 빗나갈 수 있다(다른 스레드가 이미 갈아 둔 경우다). 그래도 어느 쪽도 진전을 잃지 않는다. 평소 부하의 회전 흐름 위에서 hint는 뮤텍스 없이도 비어 있는 블록 쪽으로 모아진다.

// area_alloc — src/base/area_alloc.c (condensed)
void *
area_alloc (AREA * area)
{
AREA_BLOCKSET_LIST *blockset;
AREA_BLOCK *block, *hint_block;
int used_count, i, entry_idx;
char *entry_ptr;
int rv;
/* Step 1: hint block fast path. */
hint_block = VOLATILE_ACCESS (area->hint_block, AREA_BLOCK *);
entry_idx = hint_block->bitmap.get_entry ();
if (entry_idx != -1)
{
block = hint_block;
goto found;
}
/* Step 2: scan the blockset chain. */
for (blockset = area->blockset_list; blockset != NULL;
blockset = VOLATILE_ACCESS (blockset->next, AREA_BLOCKSET_LIST *))
{
used_count = VOLATILE_ACCESS (blockset->used_count, int);
for (i = 0; i < used_count; i++)
{
block = VOLATILE_ACCESS (blockset->items[i], AREA_BLOCK *);
entry_idx = block->bitmap.get_entry ();
if (entry_idx != -1)
{
/* opportunistic hint upgrade */
hint_block = VOLATILE_ACCESS (area->hint_block, AREA_BLOCK *);
if (LF_BITMAP_IS_FULL (&hint_block->bitmap)
&& !LF_BITMAP_IS_FULL (&block->bitmap))
ATOMIC_CAS_ADDR (&area->hint_block, hint_block, block);
goto found;
}
}
}
/* Step 3: grow the chain by one block. */
rv = pthread_mutex_lock (&area->area_mutex);
if (area->hint_block != hint_block) /* re-check after acquiring lock */
{
block = area->hint_block;
entry_idx = block->bitmap.get_entry ();
if (entry_idx != -1)
{
pthread_mutex_unlock (&area->area_mutex);
goto found;
}
}
block = area_alloc_block (area);
/* ... condensed: error handling, area_insert_block, set hint ... */
area->hint_block = block;
pthread_mutex_unlock (&area->area_mutex);
found:
entry_ptr = block->data + area->element_size * entry_idx;
#if !defined (NDEBUG)
*(int *) entry_ptr = AREA_PREFIX_INITED;
entry_ptr += AREA_PREFIX_SIZE;
#endif
return ((void *) entry_ptr);
}

n_allocs / n_frees 카운터는 SERVER_MODE가 아닐 때만 갱신된다. 서버에서는 할당마다 atomic 증가를 걸지 않겠다는 의도가 주석에 명시되어 있다. 카운터는 클라이언트 쪽 area_dump 진단용일 뿐이다.

끼워 넣기 — 블록셋을 주소순으로 유지

섹션 제목: “끼워 넣기 — 블록셋을 주소순으로 유지”
// area_insert_block — src/base/area_alloc.c (condensed)
static int
area_insert_block (AREA * area, AREA_BLOCK * new_block)
{
AREA_BLOCKSET_LIST **last_blockset_p = &area->blockset_list;
AREA_BLOCKSET_LIST *blockset, *new_blockset;
int used_count;
for (blockset = area->blockset_list; blockset != NULL; blockset = blockset->next)
{
last_blockset_p = &blockset->next;
used_count = blockset->used_count;
if (used_count == AREA_BLOCKSET_SIZE)
continue; /* this set is full */
/* Append iff new_block has a higher address than the last
* block in this blockset — keeps items[] address-sorted. */
if (blockset->items[used_count - 1] < new_block)
{
blockset->items[used_count] = new_block;
ATOMIC_INC_32 (&blockset->used_count, 1); /* release barrier */
return NO_ERROR;
}
}
/* No room — allocate a fresh blockset and append. */
new_blockset = area_alloc_blockset (area);
/* ... condensed ... */
new_blockset->items[0] = new_block;
ATOMIC_INC_32 (&new_blockset->used_count, 1);
*last_blockset_p = new_blockset;
return NO_ERROR;
}

핵심은 셋이다. (a) 끼워 넣기는 마지막 블록 주소가 새 블록보다 작은 첫 번째 비-가득 블록셋의 끝에 append한다. malloc은 뒤이은 호출에 더 높은 주소를 돌려주는 경향이 있어, 같은 블록셋 안에서 새 블록도 주소 정렬을 거의 늘 지킨다. (b) 어느 블록셋에도 들어갈 자리가 없는 경우(예컨대 높은 주소 블록 다음에 낮은 주소 블록이 malloc에서 돌아오는 경우) 새 블록셋을 잡아 사슬 끝에 매단다. 이 새 블록셋의 항목들이 앞 블록셋들의 항목보다 반드시 크리라는 보장은 없다. (c) used_count에 거는 ATOMIC_INC_32가 release barrier 역할이고, 그 직전의 items[used_count] = new_block 저장은 그것보다 먼저 끝나야 한다. 동시에 읽는 쪽(area_alloc의 2단계, area_find_block)은 두 필드를 다시 읽으면서 (item, used_count)가 짝 맞는 모습을 보게 된다.

소스의 “no shifting / re-sort” 주석이 그 대가를 정직하게 시인한다. 블록셋은 거의 정렬되어 있을 뿐이다. area_find_block은 그 한계를 모든 블록셋을 시도하는 식으로 우회한다(포인터 범위에 들어가는 한 곳만이 아니라). 그 흐름은 곧이어 본다.

area_freearea_validate는 셀 포인터의 주인 블록을 찾아내야 한다. 구조 덕에 그 비용이 블록셋마다 O(log N)짜리 binary search에 사슬 길이만큼의 O(B)(B는 블록셋 개수) 걸음으로 끝난다.

// area_find_block — src/base/area_alloc.c (condensed)
static AREA_BLOCK *
area_find_block (AREA * area, const void *ptr)
{
AREA_BLOCKSET_LIST *blockset;
AREA_BLOCK *first_block, *last_block, *block;
int middle, left, right, pos, used_count;
for (blockset = area->blockset_list; blockset != NULL;
blockset = VOLATILE_ACCESS (blockset->next, AREA_BLOCKSET_LIST *))
{
used_count = VOLATILE_ACCESS (blockset->used_count, int);
first_block = blockset->items[0];
if ((char *) ptr < first_block->data)
continue; /* below this set's range */
last_block = VOLATILE_ACCESS (blockset->items[used_count - 1], AREA_BLOCK *);
if (last_block->data + area->block_size <= (char *) ptr)
continue; /* above this set's range */
/* binary search inside the blockset's address range */
left = 0; right = used_count - 1;
while (left <= right)
{
middle = (left + right) / 2;
block = VOLATILE_ACCESS (blockset->items[middle], AREA_BLOCK *);
if (block->data > (char *) ptr) right = middle - 1;
else left = middle + 1;
}
pos = right;
block = VOLATILE_ACCESS (blockset->items[pos], AREA_BLOCK *);
if ((block->data <= (char *) ptr) && ((char *) ptr < block->data + area->block_size))
return block;
}
return NULL;
}

첫 블록과 마지막 블록 검사가 값싼 범위 거름망이다. 포인터가 첫 블록의 data보다 낮거나 마지막 블록의 꼬리보다 높으면 그 블록셋은 통째로 건너뛴다. 포인터가 그 사이에 든다면 블록셋 안쪽에서 binary search로 dataptr 이하면서 가장 큰 블록을 찾고, 마지막에 한 번 더 포함 검사를 걸어 확정한다. 이 풀에 정말로 속한 포인터라면 늘 이 검사를 통과한다. 거의 정렬만으로도 충분한 까닭이 있다. 기존 범위보다 낮은 주소의 신선한 malloc 블록은 기존 블록셋에 끼지 않고 새 블록셋으로 떨어진다. 그래서 한 블록셋 안에서 보면 항목들은 만들어질 때 이미 주소순이다.

// area_free — src/base/area_alloc.c (condensed)
int
area_free (AREA * area, void *ptr)
{
AREA_BLOCK *block, *hint_block;
char *entry_ptr;
int entry_idx, offset;
if (ptr == NULL) { /* error */ }
#if !defined (NDEBUG)
entry_ptr = ((char *) ptr) - AREA_PREFIX_SIZE; /* strip debug prefix */
#else
entry_ptr = (char *) ptr;
#endif
block = area_find_block (area, (const void *) entry_ptr);
if (block == NULL) { /* ER_AREA_ILLEGAL_POINTER */ }
offset = (int) (entry_ptr - block->data);
if (offset < 0 || offset % area->element_size != 0)
{ /* ER_AREA_ILLEGAL_POINTER — pointer not at cell boundary */ }
#if !defined (NDEBUG)
if (*(int *) entry_ptr != AREA_PREFIX_INITED)
{ /* ER_AREA_FREE_TWICE */ }
*(int *) entry_ptr = AREA_PREFIX_FREED;
#endif
entry_idx = offset / (int) area->element_size;
block->bitmap.free_entry (entry_idx);
/* opportunistic hint downgrade — pull hint to a non-full block */
hint_block = VOLATILE_ACCESS (area->hint_block, AREA_BLOCK *);
if (LF_BITMAP_IS_FULL (&hint_block->bitmap) && !LF_BITMAP_IS_FULL (&block->bitmap))
ATOMIC_CAS_ADDR (&area->hint_block, hint_block, block);
return NO_ERROR;
}

검증은 네 단의 깔때기다. (1) 포인터가 NULL이 아닐 것. (2) area_find_block이 주인 블록을 찾아 줄 것. (3) 오프셋이 element_size의 양의 배수일 것(포인터가 셀 한가운데가 아니라 셀 경계에 자리할 것). (4) 디버그 빌드라면 prefix 태그가 AREA_PREFIX_INITED와 들어맞을 것. 어느 한 단이라도 빠지면 free하지 않고 거절한다. area_freefree와 달리 호출자 버그에 단단하게 막혀 있다.

area_allocupgrade 쪽에서 쓰는 그 hint-CAS가 여기서는 downgrade 쪽에서 돈다. 한 번의 free가 가득 차 있던 블록을 비어 있는 셀 하나 가진 블록으로 바꾸면, hint는 그 블록을 가리키는 쪽으로 기회 닿는 대로 옮겨 간다. 다음 할당이 곧장 그 블록의 빠른 길로 떨어지므로 워킹셋이 좁게 머문다.

수명 — init, create, flush, destroy, final

섹션 제목: “수명 — init, create, flush, destroy, final”
sequenceDiagram
  participant Boot as boot_sr / boot_cl
  participant Init as area_init
  participant Owner as 서브시스템 (예: object_primitive)
  participant Create as area_create
  participant Use as 런타임
  participant Final as area_final
  Boot->>Init: area_init() (area_List 0으로 초기화)
  Boot->>Owner: pr_area_init() / set_area_init() / ws_area_init()
  Owner->>Create: area_create("Value containers", sizeof(DB_VALUE), N)
  Create-->>Owner: AREA *
  Note over Owner: 받은 AREA *를 전역에 보관<br/>(Value_area, tp_Domain_area, …)
  Use->>Use: area_alloc / area_free 다수
  Boot->>Final: 종료 시 area_final()
  Final->>Final: area_List 순회 → 각각 area_flush → free

area_initboot_server_main / boot_client_main에서 부르며, 전역 area_List를 0으로 비우는 일만 한다. 그 다음 서브시스템마다의 *_area_init이 자기 size class로 area_create를 부른다. area_create는 AREA를 malloc하고, 첫 블록셋과 첫 블록을 미리 잡아 두어(빈 사슬을 만나는 일을 처음부터 막는다), area_List_lock을 잡고 새 AREA를 전역 리스트의 머리에 매단다.

// area_create — src/base/area_alloc.c (condensed)
AREA *
area_create (const char *name, size_t element_size, size_t alloc_count)
{
AREA *area = (AREA *) malloc (sizeof (AREA));
/* ... condensed: name strdup, NULL checks ... */
#if !defined (NDEBUG)
element_size += AREA_PREFIX_SIZE;
#endif
/* round up to 8-byte multiple */
if (element_size % 8) element_size += 8 - (element_size % 8);
area->element_size = element_size;
area->alloc_count = LF_BITMAP_COUNT_ALIGN (alloc_count); /* 32-aligned */
area->block_size = area->element_size * area->alloc_count;
area->n_allocs = area->n_frees = 0;
#if defined (SERVER_MODE)
area->failure_function = NULL;
#else
area->failure_function = ws_abort_transaction;
#endif
area->blockset_list = area_alloc_blockset (area);
area->hint_block = area_alloc_block (area);
area->blockset_list->items[0] = area->hint_block;
area->blockset_list->used_count++;
pthread_mutex_init (&area->area_mutex, NULL);
pthread_mutex_lock (&area_List_lock);
area->next = area_List;
area_List = area;
pthread_mutex_unlock (&area_List_lock);
return area;
}

가벼운 두 자리가 있다. 하나, LF_BITMAP_COUNT_ALIGNalloc_count를 32(비트맵 워드 한 개)의 배수로 올린다. 100개 셀을 청해도 실제로 잡히는 자리는 128개다. 둘, failure_function이 클라이언트 쪽에서는 ws_abort_transaction에 묶인다. 그래서 가령 pr_make_value 안의 할당이 실패하면 NULL이 호출 스택을 거꾸로 타고 올라가는 대신, 지금 트랜잭션이 롤백된다.

area_destroy는 짝이 되는 동작이다. area_List_lock 아래에서 리스트 연결을 풀어 내고, area_flush로 모든 블록을 풀어 낸 뒤, AREA 자체를 free한다. area_flushblockset_list를 훑으면서 블록마다 bitmap.destroyfree_and_init을 부르고, 블록셋 자체도 풀어 낸다. area_final(전체 area_List를 돌면서 모든 풀을 비운다)의 본체이기도 하다.

!NDEBUG 빌드에서는 셀마다 8바이트 prefix가 따라붙는다(정렬을 위해 sizeof(double)로 선언된다). prefix는 4바이트 태그를 안고 있다.

// debug prefix tags — src/base/area_alloc.c
#define AREA_PREFIX_SIZE sizeof(double)
enum {
AREA_PREFIX_INITED = 0,
AREA_PREFIX_FREED = 0x01010101
};

area_allocAREA_PREFIX_INITED(0)을 적은 뒤 prefix 다음 자리의 포인터를 돌려준다. area_free는 셀 포인터에서 8바이트를 빼서 prefix를 다시 읽고, AREA_PREFIX_INITED와 들어맞는지 본 뒤 AREA_PREFIX_FREED로 덮어쓴다. 두 번째 free는 AREA_PREFIX_FREED를 읽고 곧장 ER_AREA_FREE_TWICE를 띄운다. 두 값을 일부러 그렇게 골랐다. 풀어 낸 객체의 어느 정수 필드가 호출자에게 보일 때 0x01010101로 비치므로, 디버거에서 freed slab cell을 한눈에 알아보기 좋다.

prefix의 대가는 할당 비용이다. 24바이트 DB_VALUE는 32바이트(24 페이로드 + 8 prefix)가 되고, 56바이트 TP_DOMAIN은 64바이트가 된다. 프로덕션 빌드에서는 prefix가 사라지고 셀은 올림된 페이로드 크기 그대로다.

src/object/ 아래의 여덟 호출 자리에서 모듈 초기화 시점에 AREA가 만들어진다. 모두 클라이언트와 SA 모드의 핫 패스 같은 크기 객체들이다. 서버 모드의 lock manager나 catalog 같은 서브시스템은 AREA가 아니라 스레드별 arena의 db_private_alloc에서 자리를 받는다.

AREA 전역원소(Element)area_create 자리용도
Value_areaDB_VALUEpr_area_init(object_primitive.c)pr_make_value가 만드는 범용 값 그릇
tp_Domain_areaTP_DOMAINtp_init(object_domain.c)타입 도메인 디스크립터(선언된 타입마다 한 개)
Template_areaOBJ_TEMPLATEobt_area_init(object_template.c)객체 update / insert 템플릿
Assignment_areaOBJ_TEMPASSIGNobt_area_init(object_template.c)템플릿 안의 필드별 assignment 레코드
Template_areaSM_TEMPLATE(스키마)classobj_area_init(class_object.c)스키마 템플릿 편집(CREATE / ALTER class)
Set_Ref_AreaDB_COLLECTIONset_area_init(set_object.c)set / multiset / sequence를 가리키는 핸들 단위 reference
Set_Obj_AreaCOLset_area_init(set_object.c)set 본체(header + storage)
Objlist_areaDB_OBJLISTws_area_init(work_space.c)object id 목록의 단일 연결 노드

원본 분석의 “AREA structures used by CUBRID modules” 다이어그램도 같은 여덟 AREA를 같은 여덟 전역 변수 이름으로 늘어놓는다.

두 가지를 짚어 둔다. (a) 여덟 AREA가 모두 클라이언트 쪽 또는 standalone 모드 워크로드를 떠받친다. 파스 트리를 짜는 파서·옵티마이저, 더티 객체를 좇는 워크스페이스, 컬렉션 타입 컬럼을 받쳐 주는 set 본체가 그 자리들이다. 서버 본체(lock manager, 페이지 버퍼, 질의 실행기)는 핫 패스에서 AREA에 기대지 않는다. (b) 명명 규약은 <Subsystem>_area 또는 <subsystem>_<class>_area다. 이름 하나가 정확히 한 area_create 호출과 한 객체 클래스에 일대일로 짝지어진다. 그래서 같은 크기의 새 핫 할당을 엔진에 더하려면, 새 전역 AREA *를 선언하고 서브시스템 init에서 area_create를 부르고 alloc/free를 area_alloc / area_free로 흘리면 끝이다.

엔진의 LF_BITMAP(src/base/lockfree_bitmap.{hpp,cpp})이 AREA_BLOCK마다 앞단에 자리한다. 본 문서와 맞물리는 자리에서 둘만 짚는다.

  • 청크 모양. AREA가 쓰는 LIST_OF_CHUNKS는 비트맵을 32셀 단위 청크로 갈라 두고, 청크마다 자기 start_idx를 두어 라운드로빈으로 자리를 떼어 준다. ONE_CHUNK는 워드 한 개로 묶는다. AREA는 늘 LIST_OF_CHUNKS를 쓴다. 같은 블록에서 동시에 일어나는 할당이 서로 다른 청크에 떨어지도록 하기 위함이다.
  • 사용률 임계값. is_full()entry_count_in_use / entry_count >= usage_threshold일 때 true를 돌려준다. 임계값은 init 시점에 float으로 정한다. 클라이언트와 SA 모드에서는 FULL_USAGE_RATIO(1.0), 서버 모드에서는 NINTETYFIVE_PERCENTILE_USAGE_RATIO(0.95)다. 95% 쪽을 고른 까닭은 5%의 메모리 여유와 다툼 줄이기를 맞바꾸기 위해서다. 마지막 몇 셀이 모든 할당기를 같은 워드로 몰아넣지 못하게 막아 준다.

본 문서는 LF_BITMAP을 검은 상자로 두고 본다. 더 깊은 lock-free 공학(고경합 시 CAS 재시도 전략, ABA 막기, 청크별 start_idx 라운드로빈)은 따로 잡아 본다. 미해결 질문 §1을 참고하라.

닻은 심볼 이름이지 줄 번호가 아니다. 지금 자리는 git grep -n '<symbol>' src/base/area_alloc.{c,h}로 짚는다.

  • area_init, area_final — 모듈 켜기와 끄기. boot_server_mainboot_client_main에서 부른다.
  • area_create — AREA 잡기, 크기 다듬기, 첫 블록셋과 첫 블록을 미리 잡기, area_List에 매달기.
  • area_destroy — 연결 풀기, flush, free.
  • area_alloc — 세 걸음 할당(hint / scan / grow). 핫 패스다.
  • area_free — 주인 블록 찾기, 셀 경계와 디버그 prefix 검증, 비트 풀기.
  • area_validate — 포인터 범위 sanity 검사(헤더에 노출되어 있으나 in-tree 호출자 없음).
  • area_flush — 한 AREA의 모든 블록 풀기.
  • area_dump — 진단용. area_List를 돈다.
  • area_alloc_block — 슬랩 한 개 malloc, 비트맵 init.
  • area_alloc_blockset — 256슬롯 블록셋 한 개 malloc.
  • area_insert_block — 사슬에 정렬 append.
  • area_find_block — 사슬 순회 + 블록셋마다의 binary search.
  • area_info — 한 AREA에 대한 dump 본체.
  • struct area, struct area_block, struct area_blockset_list.
  • AREA_BLOCKSET_SIZE(area_alloc.h) — 256.
  • AREA_PREFIX_SIZE, AREA_PREFIX_INITED, AREA_PREFIX_FREED(area_alloc.c, !NDEBUG).
  • LF_AREA_BITMAP_USAGE_RATIOSERVER_MODE에서 0.95, 그 외 1.0.
  • LF_BITMAP_COUNT_ALIGN(lockfree_bitmap.hpp) — 32의 배수로 올림.
  • area_List(static AREA *), area_List_lock(pthread_mutex_t, 서버 전용).

외부 AREA 주인 (객체 클래스마다 전역 한 개)

섹션 제목: “외부 AREA 주인 (객체 클래스마다 전역 한 개)”
  • Value_area(object_primitive.c) — DB_VALUE.
  • tp_Domain_area(object_domain.c) — TP_DOMAIN.
  • Template_area(object_template.c) — OBJ_TEMPLATE.
  • Assignment_area(object_template.c) — OBJ_TEMPASSIGN.
  • Template_area(class_object.c) — 스키마 SM_TEMPLATE(객체 템플릿용과는 별개의 TU-static binding).
  • Set_Ref_Area, Set_Obj_Area(set_object.c) — DB_COLLECTION, COL.
  • Objlist_area(work_space.c) — DB_OBJLIST.

이 개정 시점의 위치 힌트 (2026-04-30)

섹션 제목: “이 개정 시점의 위치 힌트 (2026-04-30)”
심볼파일
AREA_BLOCKSET_SIZE 매크로base/area_alloc.h42
struct area_blockbase/area_alloc.h51
struct area_blockset_listbase/area_alloc.h63
struct areabase/area_alloc.h74
area_create 선언base/area_alloc.h104
area_alloc 선언base/area_alloc.h108
area_free 선언base/area_alloc.h110
AREA_PREFIX_SIZEbase/area_alloc.c60
AREA_PREFIX_INITED / _FREEDbase/area_alloc.c64
area_Listbase/area_alloc.c72
area_List_lockbase/area_alloc.c74
LF_AREA_BITMAP_USAGE_RATIObase/area_alloc.c78
area_initbase/area_alloc.c100
area_finalbase/area_alloc.c119
area_createbase/area_alloc.c146
area_destroybase/area_alloc.c245
area_alloc_blockbase/area_alloc.c287
area_alloc_blocksetbase/area_alloc.c323
area_allocbase/area_alloc.c356
area_validatebase/area_alloc.c482
area_freebase/area_alloc.c509
area_flushbase/area_alloc.c592
area_insert_blockbase/area_alloc.c636
area_find_blockbase/area_alloc.c703
area_infobase/area_alloc.c778
area_dumpbase/area_alloc.c848
LF_BITMAP_COUNT_ALIGNbase/lockfree_bitmap.hpp89
Value_area = area_create(...)object/object_primitive.c1774
Objlist_area = area_create(...)object/work_space.c3852
Template_area = area_create(...)(object)object/object_template.c158
Assignment_area = area_create(...)object/object_template.c164
tp_Domain_area = area_create(...)object/object_domain.c659
Template_area = area_create(...)(schema)object/class_object.c168
Set_Ref_Area = area_create(...)object/set_object.c158
Set_Obj_Area = area_create(...)object/set_object.c167

원본 분석(한글 PDF, v1.0)은 대체로 옳지만, 다섯 자리에서 지금 소스에 비해 늦거나 어긋난다.

  1. AREA는 프로세스마다 전역 한 개. 원본 분석은 AREA 자체를 단일 풀로 적어 둔다. 그러나 소스는 AREA의 리스트(area_List)를 들고 있다. 객체 클래스마다 한 개씩이며, 전역으로 잡히는 것은 그 리스트의 머리이지 단일 AREA가 아니다. 분석 자료의 modules using AREA 다이어그램 자체는 옳다.
  2. AREA_BLOCKSET이 lock-free로 할당을 다룬다. 비트맵 셀 단계에서는 옳다(LF_BITMAPget_entry / free_entry에 CAS를 쓴다). 그러나 블록을 더하는 단계에서는 옳지 않다. area_alloc의 느린 길은 area->area_mutex를 잡고, area_insert_block은 그 뮤텍스 아래에서만 돈다. lock-free 성질은 평소 부하의 핫 패스에 한정된다.
  3. Free list 어휘. 원본 분석은 AREA가 셀의 free list를 들고 있다는 식으로 적었다. 구현에는 리스트가 없다. 셀 점유는 비트맵이 짚는다. 가리키는 의미는 같다.
  4. Hint 블록. 원본 분석의 struct 그림에는 hint_block이 들어 있지만, 그 역할에 대한 풀이는 없다. hint는 성능에 가장 큰 무게를 두는 단일 구조다. 흔한 경우의 area_alloc 2단계를 비트맵 연산 한 번으로 줄여 두고, ATOMIC_CAS_ADDR로 hint를 위/아래로 옮기는 기회적 길이 회전 부하 위에서 사슬 훑기 비용이 자리를 차지하지 못하게 막는 핵심이다.
  5. 디버그 prefix. 원본 분석의 struct 그림은 릴리스 빌드의 셀 모양만 보여 준다. !NDEBUG 빌드에서는 셀 앞에 8바이트 prefix(AREA_PREFIX_INITED / AREA_PREFIX_FREED)가 더해져 double-free 잡기가 가능해진다. area_dump가 출력하는 element_size가 서브시스템이 청한 값과 다른 까닭이 여기에 있다.
  1. LF_BITMAP의 깊은 분석. 원본 분석은 이 자리를 후속 문서로 미루며 JIRA CBRD-21800(lock_free.h의 C++ 리팩토링)을 가리킨다. 고경합 환경에서의 비트맵 CAS 재시도 전략, 청크마다의 start_idx 라운드로빈, is_full 임계값의 의미는 lockfree_bitmap.cpp(276줄)에 대한 따로 떼어 둔 walk-through로 다룰 만하다.
  2. 느린 길의 락이 왜 AREA마다인가, 전역이 아닌가. 전역 뮤텍스 하나로도 충분해 보이는데(느린 길은 드물다), AREA마다 두는 area_mutex는 AREA마다 40바이트를 더 쓴다. 다툼 때문에 그렇게 둔 것인가(여덟 init이 동시에 도는 자리), 아니면 area_List_lock과의 짝 맞춤일 뿐인가.
  3. LF_AREA_BITMAP_USAGE_RATIO = 0.95라는 서버 모드의 trade-off. 5%의 여유가 블록 마지막 셀들의 CAS 다툼을 줄이고 메모리를 약간 더 쓴다. 0.9 / 0.99와 견주어 잰 적이 있는가.
  4. 서버에서는 왜 n_allocs / n_frees를 세지 않는가. SERVER_MODE에서는 카운터 증가가 주석으로 가려져 있다. area_dump는 그래도 그 값을 읽으니 0이 찍힐 텐데, dump 길이 일부러 클라이언트 전용으로 잡혀 있는가.
  5. jemalloc / tcmalloc 환경에서의 주소 정렬 약점. 블록셋의 거의 정렬 성질은 malloc이 평균적으로 더 높은 주소를 돌려준다는 가정에 기댄다. 스레드 캐시나 arena 분할이 강한 할당기는 그 가정을 깨뜨릴 수 있다. 폴백(주소가 거꾸로 오면 새 블록셋)은 옳지만 블록셋마다 블록 한 개로 떨어지는 손해가 따른다. 계측해 둘 만하다.
  6. area_validate의 호출자. 헤더에 노출되어 있지만 지금 소스에 외부 호출자가 없다. dead code인지, 클라이언트 (db_* API) 코드에서만 부르는지 확인해 둘 만하다.

원본 분석 (raw/code-analysis/cubrid/common/)

섹션 제목: “원본 분석 (raw/code-analysis/cubrid/common/)”
  • code_analysis_Common_module_AREA.pdf — 1차 자료(한글, v1.0, hgryoo). 절 구성: AREA 구조 소개, AREA의 내부 구조, AREA 관련 함수, AREA 구조를 사용하는 CUBRID의 모듈들, TODO. 4쪽, 그림 셋(struct 모양, BLOCKSET 모양, modules-using-AREA 그림).
  • _converted/common-module-area.pdf.md — 같은 자료의 markitdown 추출본.
  • knowledge/code-analysis/cubrid/cubrid-page-buffer-manager.md — AREA가 아니라 db_private_alloc / malloc을 쓰지만, AREA_BLOCK 안의 LF_BITMAP 인스턴스를 거친 클래스마다의 풀 + lock-free 비트맵이라는 같은 디자인 어휘를 함께 쓴다.
  • knowledge/code-analysis/cubrid/cubrid-heartbeat.md — 별개 모듈. 본 문서의 양식 본보기로 인용된다.
  • Database Internals(Petrov), 4장 Implementing B-Trees — B-tree 노드 캐시 자리에서 슬랩과 풀 할당을 가리킨다.
  • Database System Concepts(Silberschatz, Korth, Sudarshan, 6판), 13장 Storage and File Structure — 같은 크기 할당기가 자리 잡는 buffer manager 풍경.
  • Bonwick, The Slab Allocator: An Object-Caching Kernel Memory Allocator, USENIX Summer 1994 — 슬랩의 정전이 되는 논문. 클래스마다의 캐시와 constructor / destructor 패턴을 들여놓았고, AREA는 그 패턴을 단순하게 다시 짠 모양이다.

CUBRID 소스 (/data/hgryoo/references/cubrid/)

섹션 제목: “CUBRID 소스 (/data/hgryoo/references/cubrid/)”
  • src/base/area_alloc.h — 공개 타입과 API.
  • src/base/area_alloc.c — 구현.
  • src/base/lockfree_bitmap.{hpp,cpp} — 셀 단위의 lock-free 할당을 떠맡는 블록마다의 비트맵.
  • src/object/object_primitive.cValue_area 설치와 pr_make_value / pr_clear_value 호출 자리.
  • src/object/object_domain.ctp_Domain_area.
  • src/object/object_template.cTemplate_area(객체)와 Assignment_area.
  • src/object/class_object.cTemplate_area(스키마).
  • src/object/set_object.cSet_Ref_AreaSet_Obj_Area.
  • src/object/work_space.cObjlist_area.
  • src/transaction/boot_sr.c, src/transaction/boot_cl.c — 부팅 시 area_init, 종료 시 area_final 호출.
  • src/executables/unittests_area.c — 멀티스레드 area_alloc / area_free 스트레스 테스트.