콘텐츠로 이동

(KO) CUBRID 스레드와 워커 풀 — Worker, Daemon, lock-free 자료구조, critical section

목차

모든 데이터베이스 엔진은 가장 아래 계층에서 본질적으로 thread 시스템 이다. 들어오는 요청 스트림과 반복적으로 돌아야 하는 백그라운드 책무를 실제 운영체제 thread로 옮겨 주고, 각 thread에 자기 일을 하기에 충분한 상태를 — 호출 한 번 한 번마다 그 상태를 다시 만들지 않아도 되도록 — 들려 보내 주는 일이다. CUBRID 서버도 다르지 않다. lock manager 이전에, MVCC 이전에, recovery 이전에 이미 thread pool 이 있고, thread별 컨텍스트가 있고, 오래 살아 있는 백그라운드 daemon들이 있고, 그 위 계층들이 다 함께 쓰는 작은 동기화 원시 집합이 있다.

설계 공간을 두 권의 교재가 잡아 준다.

Database Internals (Petrov, 14장 Concurrency Control 과 스토리지 계층 챕터들) 가 기본 분리를 명명한다. 엔진에는 사용자 주도의 작업 단위 (연결, 쿼리, 스캔, 페이지 읽기) 를 돌리기 위해 존재하는 worker thread 가 있고, 엔진 주도의 반복 작업 (dirty 페이지 flush, WAL 잘라내기, deadlock 감지, dead 버전 vacuum) 을 돌리기 위해 존재하는 daemon thread 가 있다. 수명이 다르다 — worker는 한 작업을 위해 태어났다가 다음 작업으로 재활용되지만, daemon은 한 번 태어나서 종료까지 산다. 동기화 요건 도 다르다 — worker는 보통 큐로 도착한 요청에 의해 깨어나지만, daemon은 보통 시계나 다른 thread가 보내는 wakeup hint로 깨어 난다.

The Art of Multiprocessor Programming (Herlihy & Shavit) 은 worker 수가 손가락으로 세는 단위를 넘는 순간 thread 시스템이 풀어야 할 알고리즘적 문제를 명명한다 — 그 worker와 daemon이 공유하는 큐, free-list, hash table에 쓸 lock-free 자료구조 가 그것이다. task 큐나 lock 테이블에 단일 mutex 하나를 둔 순진한 설계는 그 mutex가 덮는 critical line 위에서 모든 thread 를 직렬화시킨다. 엔진은 거기서 확장을 멈춘다. 책이 표준적인 패턴들을 알려 준다 — Treiber stack (CAS 기반 push/pop), Michael–Scott queue (lock-free FIFO), Maged Michael / Fraser Robust Hash (FRH, split-ordered list hashmap 이라고도 불리는, JSR-166 이후 Java ConcurrentHashMap 의 토대). 확장을 표방하는 모든 현대 엔진은 이 중 일부를 골라서 thread가 기다릴 만한 자리 마다 깔아 둔다.

이 두 프레이밍이 모든 실제 엔진을 빚는 네 개의 구현 결정을 낳는다. 본 문서의 나머지 골격이기도 하다.

  1. thread-per-connection 인가, thread pool 인가? 고전적 접근 (PostgreSQL, 초기 C 시대 MySQL) 은 연결당 하나의 OS 프로세스 또는 thread, 풀 없음 — OS 스케줄러가 곧 작업 큐다. pool 접근 (MySQL의 thread-pool 플러그인, Oracle MTS, SQL Server의 SOS 스케줄러, CUBRID) 은 연결과 thread를 분리한다 — 연결이 큐에 도착하고 worker가 그것을 집어 든다. 절충은 분명하다 — 컨텍스트 생성 비용을 분산하고 동시성에 상한을 두는 대신, 자기만의 스케줄러와 thread별 컨텍스트로 thread-per-connection이 OS에서 공짜로 받던 것을 흉내 내야 한다는 점이다.

  2. 큐가 하나인가, 여럿인가? 글로벌 큐 하나는 단순하고 정확하지만 경합이 심하다. 표준적인 정련은 분할된 큐 다. worker를 그룹 (core, partition, shard 어떻게 부르든) 으로 나누고 각 그룹에 자기 task 큐를 둔 뒤, task마다 hash나 round-robin이나 NUMA 근접도로 큐를 고른다. 비용은 부하 불균형 (한 shard가 굶주리는 동안 다른 shard에 작업이 쌓일 수 있음) 이고 이득은 각 shard 큐의 lock이 거의 경합되지 않는다는 점이다. CUBRID은 분할된 큐 + round-robin dispatch를 고른다.

  3. thread를 풀링할 것인가, entry를 풀링할 것인가? worker가 task를 마치면 두 가지를 회수할 수 있다 — OS thread 자체와, 엔진 코드가 thread_p 로 읽고 쓰는 thread별 컨텍스트 다. 어떤 엔진은 둘 다 풀링한다 (항상 살아 있는 worker, 절대 파괴되지 않는 컨텍스트). 어떤 엔진은 컨텍스트만 풀링하고 OS thread는 필요할 때 오고 가게 한다. CUBRID은 기본값으로 후자를 한다 — 컨텍스트 (cubthread::entry) 는 고정 크기 풀에서 dispatch되고, OS thread는 worker에 task가 할당될 때 생성되었다가 새 task가 오지 않으면 idle timeout 후에 종료 된다. 튜닝 knob (pool_threads 와 perf-test override) 이 필요할 때 thread를 영원히 살려 둘 수 있게 해 준다.

  4. 느린 경로를 무엇으로 보호하는가? 모든 thread 시스템에는 적어도 세 계층의 동기화 원시가 있다. 가장 아래에는 pthread_mutex_t 가 단일 critical line (큐 머리, free list) 을 보호한다. 한 계층 위에는 reader/writer 원시가 많은 reader를 들이면서 writer를 배제한다 (교과서적인 RW-lock). 가장 위에는 lock-free 구조가 hot path 위에서 lock 자체를 대체한다. CUBRID은 셋 다 가진다 — 곳곳의 pthread_mutex_t, transaction table 같은 무거운 게이트를 위한 자체 SYNC_CRITICAL_SECTION RW 원시 (csect), 그리고 hot 자료 구조용 lockfree::hashmaplockfree::circular_queue (base 모듈 안에 있다).

이 네 결정이 명명되고 나면, 본 문서의 모든 CUBRID-specific 구조는 그 결정 중 하나를 구현하거나 그 구현을 더 빠르게 만든다는 관점으로 읽힌다.

동시 연결을 지원하는 모든 DBMS는 어떤 형태로든 thread 시스템을 출하한다. 그 모양은 한 줌의 패턴으로 수렴한다. CUBRID의 다음 절에서의 구체적 선택은 이 공유된 설계 공간 안의 다이얼 한 세트 로 읽는 것이 가장 좋다.

PostgreSQL의 코어는 thread pool 이 아니라 process-per-connection 모델을 쓴다. postmaster가 연결마다 backend를 fork한다. backend 는 연결이 닫힐 때까지 자기 MyProcPGPROC 공유 메모리 슬롯 을 들고 있다. 백그라운드 프로세스 (autovacuum launcher + workers, wal-writer, bgwriter, checkpointer, archiver, walsender / walreceiver, logical replication apply workers) 도 있지만, 이들도 모두 풀의 thread가 아니라 postmaster가 등록하고 시작하는 온전한 프로세스다. 결과는 훌륭한 격리 (오염된 backend가 다른 backend를 오염시킬 수 없다) 이지만, 연결당 메모리와 IPC 오버헤드를 댓가로 지불한다. 연결 풀링은 외부 미들웨어 (PgBouncer, pgpool) 에 위임된다.

풀링된 thread (MySQL, MariaDB, Oracle MTS, SQL Server)

섹션 제목: “풀링된 thread (MySQL, MariaDB, Oracle MTS, SQL Server)”

상용과 fan-out이 큰 DBMS들은 OS가 수만 개의 연결을 thread로 경제 적으로 dispatch할 수 없다는 분명한 이유로 thread를 풀링한다.

  • MySQL Enterprise / MariaDB thread pool 은 연결을 thread group 에 배정한다. 각 group은 자기 큐와 waiter, listener thread를 가진다. group 수는 대략 CPU 수, 각 group은 한두 개의 active thread를 돌린다.
  • Oracle Multi-Threaded Server (MTS / shared-server architecture) 는 사용자 프로세스를 dispatcher 로 더 작은 server process 풀 위로 다중화한다. dedicated-server 모드는 풀을 거치지 않는다.
  • SQL Server SOS (SQL OS) 는 worker, scheduler (논리 CPU당 하나), task, task 큐를 가진 자체 user-space 스케줄러다. OS thread 위에 협조적으로 스케줄링되는 fiber를 얹는다.

CUBRID은 이 진영에 그대로 들어간다 — CUBRID 서버는 thread-entry 풀, 분할된 worker pool, daemon 집합을 가진 단일 멀티스레드 프로세스다.

worker 모델과 독립적으로, 모든 엔진은 이름 붙은 채 오래 사는 백그라운드 thread의 작은 집합을 출하한다. 이름들이 수렴한다.

  • page-flush / bgwriter / lazy writer — buffer pool에 항상 깨끗한 victim이 있도록 dirty 페이지를 골라 쓴다.
  • log-flush / wal-writer — 가장 최근 commit된 LSN까지 WAL 을 fsync하여 WAL 규칙을 강제한다.
  • checkpoint — 주기적으로 buffer pool을 flush하고 checkpoint 로그 레코드를 쓴다.
  • archive / log-mover — active 범위를 벗어난 옛 WAL을 옮긴다.
  • vacuum / purge / clean-up — 죽은 MVCC 버전을 회수한다 (PostgreSQL autovacuum, InnoDB purge, CUBRID vacuum).
  • deadlock detector — wait-for graph를 주기적으로 걷는다.
  • stats collector — activity counter를 주기적으로 표본 추출한다.

§풀 소비자 에 나열된 모든 CUBRID daemon은 이 중 하나에 들어 맞는다.

단일 공유 자원이 끊임없이 읽히고 드물게 쓰이는 (transaction table, schema cache, disk allocation map) 상황에서는 교과서적 답이 pthread_rwlock_t 다. 실제로는 모든 진지한 엔진이 자기 RW 원시를 출하하고 거기에 다음을 더한다 — writer queue (reader 부하 아래에서 writer가 굶주리지 않게), promotion (reader가 먼저 풀지 않고 writer로 승격), demotion (writer가 reader로 강등), section별 identity (deadlock cycle 감지가 section 이름을 댈 수 있도록), 그리고 debug 빌드에서 thread별로 어떤 section을 들고 있는지 기록하고 일관되지 않은 재진입에 불평하는 tracker. 이는 정확히 CUBRID의 SYNC_CRITICAL_SECTION 더하기 critical_section_tracker 다.

현대 엔진에서 일관되게 lock-free인 두 구조는 다음과 같다.

  • lock 테이블, 페이지 테이블, schema cache, lock-free allocator bucket을 위한 lock-free hash map. 표준 알고리즘 은 Maged Michael의 split-ordered hashmap (Java ConcurrentHashMap 의 혈통) 이며, CUBRID의 lockfree::hashmap 이 그것이다.
  • worker와 단일 consumer (flusher, vacuumer) 사이의 핸드오프를 위한 lock-free 원형 버퍼 / MPSC queue. CUBRID의 lockfree::circular_queue (src/base/) 가 page buffer 안에 서 직접 victim 핸드오프에 쓰인다.

CUBRID은 lock-free hashmap을 thread-aware 래퍼 (cubthread::lockfree_hashmap) 로 노출한다. 이 래퍼는 hashmap 트랜잭션을 cubthread::entry 안에 들어 있는 thread별 descriptor에 묶는다.

CUBRID은 src/thread/ 의 동일한 소스에서 전처리기 가드로 세 종류의 바이너리를 컴파일한다 — SERVER_MODE (cub_server 프로세스), SA_MODE (standalone, 클라이언트와 서버가 한 바이너 리에 링크), CS_MODE (클라이언트 측 라이브러리). thread 기계 는 SERVER_MODE 에서만 완전히 활성화된다. SA_MODE 에서는 cubthread::manager 가 만들어지지만 worker pool과 daemon은 만들 어지지 않는다. 대신 push_task 가 호출 thread 위에서 task를 동기적으로 실행한다. 이 점이 본 문서 나머지에 들고 갈 첫 디테일 이라는 것이다 — 이후에 보게 될 모든 worker pool 경로는 standalone에서는 풀 dispatch를 건너뛰는 동기 경로이기도 하다.

// manager::push_task — thread/thread_manager.cpp
void
manager::push_task (worker_pool *worker_pool_arg, entry_task *exec_p)
{
if (worker_pool_arg == NULL)
{
// execute on this thread
exec_p->execute (get_entry ());
exec_p->retire ();
}
else
{
#if defined (SERVER_MODE)
check_not_single_thread ();
worker_pool_arg->execute (exec_p);
#else
// ... fall back to immediate execute ...
#endif
}
}

이 절은 위에서 아래로 아키텍처를 걷는다 — thread별 컨텍스트가 먼저 (cubthread::entry), 그 다음 컨텍스트 풀을 소유한 manager, 그 다음 그 컨텍스트로 매개변수화된 worker pool 템플릿, 그 다음 같은 컨텍스트 풀을 쓰지만 다른 루프 패턴을 갖는 daemon 클래스, 그 다음 그 thread들이 호출하는 lock-free hashmap, 마지막으로 느린 공유 자원을 위해 pthread_mutex_t 위에 얹은 critical section 원시 순서다.

cubthread::entry — thread별 컨텍스트

섹션 제목: “cubthread::entry — thread별 컨텍스트”

cubthread::entry 는 서버에서 가장 중요한 단일 struct다. 누가 나를 부르고 있고 그가 어떤 상태에 있는가 를 알아야 하는 모든 엔진 루틴은 첫 인자로 THREAD_ENTRY *thread_p (cubthread::entry * 의 typedef alias) 를 받는다. entry는 thread-per-connection 엔진에서 OS thread의 TLS에 묵시적으로 들어 있을 모든 것을 들고 다닌다.

  • identity — index (manager 배열에서의 슬롯, 1-based), type (worker, daemon, vacuum master, recovery, …), m_id (std::thread::id), client_id (이 thread가 어느 세션을 서비스하는지), tran_index (어느 transaction descriptor — TDES — 가 묶여 있는지), private_lru_index (private quota 가 켜진 page buffer에서 이 thread가 소유한 LRU 리스트).
  • status & wait reason — TS_DEAD, TS_FREE, TS_RUN, TS_WAIT, TS_CHECK 의 FSM에, TS_WAIT가 들어왔는지를 나열하는 resume_status 가 더해진다 (THREAD_LOCK_SUSPENDED, THREAD_PGBUF_SUSPENDED, THREAD_LOGWR_SUSPENDED, …). 이 reason은 thread를 깨울 누군가와 맺는 프로토콜이다 — lock manager가 lock을 부여할 때 THREAD_LOCK_RESUMEDthread_wakeup 에 넘겨, 깨어난 waiter가 자기가 옳은 이유로 깨어났는지를 검증할 수 있게 하고 spurious signal과 구별 한다.
  • synchronization — entry별 pthread_mutex_t th_entry_lock 더하기 pthread_cond_t wakeup_cond. 서버 의 모든 blocking 원시 (latch, lock, csect, log writer) 는 결국 thread_suspend_wakeup_and_unlock_entry 로 귀결되며, 이 함수가 그 mutex+condvar 쌍 위에서 pthread_cond_wait 을 호출한다. futex 스타일 fast path는 없다 — 모든 wait가 entry 의 condition variable을 통과한다.
  • allocator cache — private_heap_id (thread-private heap의 HL_HEAPID), log_zip_undo / log_zip_redo / log_data_ptr (로그 append 사이에 재사용되는 thread별 압축 버퍼), tran_entries[THREAD_TS_*] (공유 lock-free 테이블당 하나의 lock-free transaction descriptor 배열 — sessions, catalog, lock-res, lock-ent, xcache, fpcache, hfid table, dwb-slots, … — lock-free hashmap의 epoch / hazard-pointer 스킴과 협응하기 위함).
  • tracker — m_alloc_tracker, m_pgbuf_tracker, m_csect_tracker. ENABLE_TRACKERS = !NDEBUG && SERVER_MODE 인 debug 빌드에서만 활성. 모든 malloc, 모든 page fix, 모든 csect enter를 기록해서 transaction이 끝날 때 thread가 자기 가 잡은 것을 모두 풀었는지 엔진이 단언할 수 있게 한다.
  • subsystem별 상태 — vacuum_worker (이 entry가 vacuum thread에 묶여 있다면 vacuum worker 정보 포인터), xasl_unpack_info_ptr (현재 실행 중 query의 풀린 XASL), lockwait / lockwait_msecs / lockwait_state (deadlock detector가 읽는 객체 X 위에서 최대 N ms 기다리고 있다 튜플), event_stats (slow query timing), 그리고 interrupted, shutdown (atomic), check_interrupt, no_logging, is_cdc_daemon, trigger_involved 등의 플래그 한 무더기.
// cubthread::entry — thread/thread_entry.hpp
class entry
{
public:
enum class status { TS_DEAD, TS_FREE, TS_RUN, TS_WAIT, TS_CHECK };
int index; // thread entry index
thread_type type; // worker, daemon, vacuum, recovery, …
int client_id; // client whose request this thread runs
int tran_index; // bound transaction descriptor
int private_lru_index;
pthread_mutex_t th_entry_lock;
pthread_cond_t wakeup_cond;
status m_status;
thread_resume_suspend_status resume_status;
HL_HEAPID private_heap_id; // private allocator cache
css_conn_entry *conn_entry; // network connection
xasl_unpack_info *xasl_unpack_info_ptr;
vacuum_worker *vacuum_worker;
lf_tran_entry *tran_entries[THREAD_TS_COUNT]; // lock-free txn descriptors
pgbuf_holder_anchor *m_holder_anchor; // page-fix list head
std::atomic_bool shutdown;
bool interrupted;
bool check_interrupt;
bool wait_for_latch_promote;
// ... condensed ...
private:
cuberr::context m_error;
cubbase::alloc_tracker &m_alloc_tracker;
cubbase::pgbuf_tracker &m_pgbuf_tracker;
cubsync::critical_section_tracker &m_csect_tracker;
log_system_tdes *m_systdes;
lockfree::tran::index m_lf_tran_index;
};

헤더의 “private members will gradually replace public” 코멘트는 진심이다. 프로젝트는 C 시대의 THREAD_ENTRY 공용 필드 struct로 부터 멀어지는 refactor 중간에 있고, 위의 public 필드들은 의도된 부채다. entry 상태를 읽거나 써야 하는 새 코드는 직접 필드 접근 보다 accessor 메서드 (get_error_context, get_lf_tran_index, get_pgbuf_tracker, …) 를 선호해야 한다는 점이다.

생성자가 private heap 하나, pthread mutex 두 개, condition variable 하나, tracker 세 개를 할당한다 — 이것이 manager가 많은 task에 걸쳐 분산하고자 하는 비용이며, entry가 풀링되는 모든 이유이기도 하다.

서버의 모든 blocking 원시는 결국 entry의 mutex+condvar 쌍 위에 얹힌 두 C-style 함수로 환원된다. 이 둘을 한 번 읽으면 엔진의 나머지가 읽힌다.

// thread_suspend_wakeup_and_unlock_entry — thread/thread_entry.cpp
void
thread_suspend_wakeup_and_unlock_entry (cubthread::entry *thread_p,
thread_resume_suspend_status suspended_reason)
{
// entry's th_entry_lock must be held by caller
thread_p->m_status = cubthread::entry::status::TS_WAIT;
thread_p->resume_status = suspended_reason;
// ... slow-query timing prelude ...
pthread_cond_wait (&thread_p->wakeup_cond, &thread_p->th_entry_lock);
thread_p->m_status = old_status;
pthread_mutex_unlock (&thread_p->th_entry_lock);
}
// thread_wakeup_internal — thread/thread_entry.cpp
static void
thread_wakeup_internal (cubthread::entry *thread_p,
thread_resume_suspend_status resume_reason, bool had_mutex)
{
if (!had_mutex) thread_lock_entry (thread_p);
pthread_cond_signal (&thread_p->wakeup_cond);
thread_p->resume_status = resume_reason;
if (!had_mutex) thread_unlock_entry (thread_p);
}

계약은 이렇다 — 자기 entry 위에서 잠들고 싶은 thread가 먼저 th_entry_lock 을 잡고, resume_status 를 suspend reason으로 세팅하고, cond_wait 을 부른다. 깨우는 쪽은 — 보통 다른 mutex (lock table mutex, page buffer mutex) 를 들고 있는 채 — 일치하는 resume reason으로 thread_wakeup 을 호출한다. 깨어난 thread는 resume_status 를 자기가 기대했던 suspend reason과 비교해서 진행하거나 spurious wake로 처리한다. _already_had_mutex 변종은 호출자가 이미 lock을 들고 있을 때 다시 잡는 것을 건너뛴다 (suspend와 wake가 더 위 계층의 mutex 아래에서 한 단위로 협응되는 전형적 경우다).

timeout 변종도 같은 식이다. thread_suspend_with_other_mutex 는 외부 mutex를 받아서 호출자가 entry의 condvar 위에서 외부 mutex 아래 기다릴 수 있게 한다 — lock manager가 “lock table mutex 잡은 채 내 entry 위에서 suspend, lock table mutex 풀기” 가 suspend에 걸쳐 atomic이 되도록 이를 쓴다.

manager는 싱글턴이다 (Manager static, cubthread::get_manager () 로 접근). entry 풀, worker pool 리스트, daemon 리스트, lock-free transaction 시스템을 소유한다. cubthread::initialize 가 할당하고 initialize_thread_entries 가 셋업하며 cubthread::finalize 가 해체한다.

// cubthread::manager — thread/thread_manager.hpp
class manager
{
// entries
entry *m_all_entries;
entry_dispatcher *m_entry_dispatcher; // resource_shared_pool<entry>
std::size_t m_max_threads;
std::size_t m_available_entries_count;
// pools and daemons
std::vector<worker_pool *> m_worker_pools;
std::vector<daemon *> m_daemons;
std::vector<daemon *> m_daemons_without_entries;
entry_manager m_entry_manager;
daemon_entry_manager m_daemon_entry_manager;
// lock-free transaction system
lockfree::tran::system *m_lf_tran_sys;
// ...
template <typename Res, typename ... CtArgs>
Res *create_worker_pool (std::size_t pool_size, std::size_t core_count,
CtArgs &&... args);
daemon *create_daemon (const looper &, entry_task *, const char *name,
entry_manager * = NULL);
daemon *create_daemon_without_entry (const looper &,
task_without_context *, const char *);
entry *claim_entry (void);
void retire_entry (entry &);
};

entry 풀의 크기는 knob이 아니다 — 등록된 소비자의 카운트로부터 count_registry 글로벌로 계산된다.

// manager::set_max_thread_count_from_config — thread/thread_manager.cpp
void
manager::set_max_thread_count_from_config (void)
{
m_max_threads = cubbase::count_registry<connection>::total ()
+ cubbase::count_registry<worker_pool>::total ()
+ cubbase::count_registry<daemon>::total ()
+ 1 /* PAD */;
}

count_registry<T>::total() 은 각 subsystem이 file scope에서 호출하는 REGISTER_CONNECTION, REGISTER_WORKERPOOL, REGISTER_DAEMON 매크로의 등록을 합산한다. 매크로는 thread의 수량 을 등록하는 게 아니라 이름 붙은 호출자의 카운트 를 등록한다. 그래서 새 daemon이나 pool을 추가하려면 파일 머리 가까이에 REGISTER_* 매크로가 있어야 하고, 그래야 manager가 entry 풀의 크기를 잡을 수 있다. 그렇지 않으면 N entries 예약 이 m_available_entries_count 를 넘는 순간 create_worker_poolNULL 을 반환한다.

// macros — thread/thread_manager.hpp
#define REGISTER_CONNECTION(name, getter) static cubthread::manager::connection_registry_t _gl_reg_conn_##name (#name, getter)
#define REGISTER_WORKERPOOL(name, getter) static cubthread::manager::workerpool_registry_t _gl_reg_wp_##name (#name, getter)
#define REGISTER_DAEMON(name) static cubthread::manager::daemon_registry_t _gl_reg_daemon_##name (#name, 1)

entry가 일단 존재하면 OS thread에 on-demand로 dispatch된다. claim_entryresource_shared_pool 에서 entry를 pop해서 thread-local 포인터 tl_Entry_p 에 저장한다. retire_entry 는 도로 넣는다. 모든 서버 thread는 claim_entry 로 시작 (보통 worker pool의 init_run 안 또는 daemon의 loop_with_context 안에서) 해서 자기 entry & 를 받아 들고 그 다음 task를 돈다.

// manager::claim_entry / retire_entry — thread/thread_manager.cpp
entry *
manager::claim_entry (void)
{
tl_Entry_p = m_entry_dispatcher->claim ();
return tl_Entry_p;
}
void
manager::retire_entry (entry &entry_p)
{
assert (tl_Entry_p == &entry_p);
tl_Entry_p = NULL;
m_entry_dispatcher->retire (entry_p);
}

entry_manager (와 그 파생 daemon_entry_manager, system_worker_entry_manager) 는 worker pool이 매번 호출하는 create_context / retire_context / recycle_context / stop_execution 각각을 호출되는 정책 객체다. 각 subsystem 이 자기 thread별 상태를 일반 worker에 붙이는 customisation hook 이다. 예를 들어 vacuum의 vacuum_Worker_entry_manageron_create 를 override해서 vacuum_worker * 를 entry에 붙이고, pl/sp의 m_monitor_helper_daemon 은 사용자 정의 entry manager로 JNI bridge의 class loader를 연결한다.

cubthread::worker_pool — core별 큐를 가진 분할 풀

섹션 제목: “cubthread::worker_pool — core별 큐를 가진 분할 풀”

worker pool은 task당 thread를 spawn하지 않고도 임의의 (그러나 유한한) 수의 entry_task 인스턴스를 병렬로 돌리는 엔진의 유일한 방법이다. 통계를 필요로 하지 않는 풀 (대부분) 과 필요로 하는 풀 (글로벌 transaction별 worker pool) 이 런타임 오버헤드 없이 다른 코드로 컴파일되도록 worker_pool_impl<bool Stats> 템플릿이다. base class worker_pool 이 manager와 push_task 가 보는 런타임 다형 인터페이스다.

풀은 core 로 분할 되어 있고 (저volume 풀은 보통 1개, 고volume 풀은 여러 개), 각 core는 풀의 worker를 고정 슬라이스 와 task 큐 하나를 소유한다.

worker_pool
/ | \
core core core (m_cores)
/ | | \
worker worker worker (core::m_workers)
(core::m_available_workers)
(core::m_task_queue)

execute_on_core 가 hash로 (기본은 round-robin, 인자로 사용자 정의) core를 골라 core_impl::execute_task 를 부른다. hot path 는 짧다 — m_workers_mutex 를 잡고, idle worker가 있으면 pop 하고, task를 그 worker에 배정한다. 없으면 큐에 넣는다.

// core_impl::execute_task — thread/thread_worker_pool_impl.hpp
void
worker_pool_impl<Stats>::core_impl::execute_task (task_type *task_p, bool is_temp)
{
if (!m_parent_pool->is_running ()) { task_p->retire (); return; }
wrapped_task task_ref (task_p);
std::unique_lock<std::mutex> ulock (m_workers_mutex);
if (!m_available_workers.empty ())
{
worker_impl *refp = static_cast<worker_impl *> (m_available_workers.back ());
m_available_workers.pop_back ();
ulock.unlock ();
refp->assign_task (std::move (task_ref)); // wake or spawn
}
else if (is_temp)
{
ulock.unlock ();
execute_task_as_temp (std::move (task_ref)); // spawn an ad-hoc temp worker
}
else
{
m_task_queue.push (std::move (task_ref)); // enqueue
}
}

worker는 현재 task를 마친 뒤 get_task_or_become_available 을 부른다 — 한 번의 critical section이 큐에 쌓인 task를 pop하거나 worker를 available로 등록하거나 한다.

// core_impl::get_task_or_become_available — thread/thread_worker_pool_impl.hpp
std::optional<wrapped_task>
worker_pool_impl<Stats>::core_impl::get_task_or_become_available (worker &w)
{
std::unique_lock<std::mutex> ulock (m_workers_mutex);
if (!m_task_queue.empty ())
{
wrapped_task qt = std::move (m_task_queue.front ());
m_task_queue.pop ();
return std::optional<wrapped_task> (std::in_place, std::move (qt));
}
m_available_workers.push_back (&w);
return std::nullopt;
}

그래서 worker는 정확히 둘 중 하나의 상태에 있다 — task를 들고 돌고 있거나, 배정을 기다리며 m_available_workers 안에 있거나. mutex가 두 리스트를 모두 보호하면서 assign 대 available 경합을 직렬화한다. 별도의 idle 리스트는 없다. m_available_workers 가 곧 idle 리스트다.

worker는 풀 안에 영속적으로 사는 C++ 객체다. 오고 가는 것은 OS thread다. assign_task 는 worker가 thread가 있으면 (m_has_thread) 그것에 알림을 보내고, 없으면 새로 spawn한다.

// worker_impl::assign_task — thread/thread_worker_pool_impl.hpp
void
worker_pool_impl<Stats>::core_impl::worker_impl::assign_task (wrapped_task &&task_ref)
{
std::unique_lock<std::mutex> ulock (m_task_mutex);
m_wrapped_task.emplace (std::move (task_ref));
if (m_is_temp) { m_has_thread = true; start_thread (); return; }
if (m_has_thread) { ulock.unlock (); m_task_cv.notify_one (); }
else
{
m_has_thread = true;
ulock.unlock ();
start_thread (); // std::thread (&worker_impl::run, this).detach ()
}
}

detached thread는 worker_impl::run 을 돈다.

// worker_impl::run — thread/thread_worker_pool_impl.hpp
void
worker_pool_impl<Stats>::core_impl::worker_impl::run (void)
{
os::resources::cpu::clearaffinity ();
pthread_setname_np (pthread_self (), m_parent_core->get_parent_pool ()->get_name ().c_str ());
init_run (); // claim entry (context)
if (m_is_temp) { execute_current_task (); finish_run (); return; }
// loop: execute current task, then try to grab another
if (!m_wrapped_task.has_value ())
{
if (get_new_task ()) { /* got one */ }
}
if (m_wrapped_task.has_value ())
{
do { execute_current_task (); } while (get_new_task ());
}
}

init_runm_parent_core->get_entry_manager().create_context() 를 호출하고, 내부에서 manager의 dispatcher로부터 entry를 claim한 뒤 subsystem-specific on_create hook을 돌린다. finish_run 은 거울 — retire_context 가 entry를 도로 풀어 준다. 그 사이에 thread는 task들을 돈다. 각 task는 같은 entry * 를 컨텍스트로 받기 때문에, 오래 사는 풀은 사실상 자기가 claim한 entry를 핀으로 박아 둔 셈이 된다.

get_new_task 가 OS thread를 살려 둘지 결정하는 worker의 부분 이다.

// worker_impl::get_new_task — thread/thread_worker_pool_impl.hpp
bool
worker_pool_impl<Stats>::core_impl::worker_impl::get_new_task (void)
{
std::unique_lock<std::mutex> ulock (m_task_mutex, std::defer_lock);
if (!m_stop)
{
// 1. queued task? pop it.
auto qt = static_cast<core_impl *> (m_parent_core)->get_task_or_become_available (*this);
if (qt.has_value ()) { m_wrapped_task.emplace (std::move (*qt)); return true; }
// 2. no queued task; we just got added to m_available_workers.
// wait on m_task_cv until either a task arrives or idle_timeout fires.
ulock.lock ();
if (!m_wrapped_task.has_value () && !m_stop)
{
condvar_wait (m_task_cv, ulock,
m_parent_core->get_parent_pool ()->get_idle_timeout (),
[this] () -> bool { return m_wrapped_task.has_value () || m_stop; });
}
}
// ...
if (!m_wrapped_task.has_value ())
{
// timed out; thread will exit. next assign_task spawns fresh.
m_has_thread = false;
finish_run (); // retire context now
return false;
}
// got a task, keep going
return true;
}

idle timeout 기본값은 5초다 (manager의 thread_create_worker_pool 기본값). pool_threads=true 가 넘어오면 (또는 perf-test 모드가 wp_set_force_thread_always_alive () 로 강제하면) 무한대다. worker의 thread가 timeout되면 worker 는 m_has_thread = false 인 채로 풀에 남고, 다음 assign_task 가 새 OS thread를 시작한다. 이것이 “낮은 부하: thread 회수, 높은 부하: 살려 두기” knob이다.

wrapped_task<Stats> 는 task 포인터를 선택적 timing과 함께 감싼다. Stats=true 이면 생성 시점의 cubperf::time_point 를 기록하고 worker가 stage별 timing을 추적한다 (start_thread, create_context, execute_task, retire_task, found_in_queue, wakeup_with_task, recycle_context, retire_context). Stats=false 이면 wrapper는 사실상 task* 이고 if constexpr (Stats) 가 카운터를 컴파일 시에 버린다.

어떤 풀은 범람되어서는 안 된다 — 만약 page-flush daemon이 worker가 flush할 수 있는 속도보다 빠르게 enqueue하면 큐가 무한히 커지면서 backpressure가 깨진다. cubthread::worker_pool_task_capper (thread_worker_pool_taskcap.hpp) 가 풀을 고정된 token 예산으로 감싼다. try_task 는 예산이 떨어지면 false를 돌려준다. push_task 는 condition variable 위에서 end_task (capped_task::execute 의 epilogue에서 호출됨) 가 빈 슬롯을 신호할 때까지 block한다. recovery의 parallel redo와 page-flush dispatcher가 무한 적재를 피하는 방법이다.

코드베이스에는 여덟 개의 풀이 산다. thread_create_worker_poolthread_create_stats_worker_pool 을 grep해서 찾을 수 있다.

풀 이름subsystem생성 위치
transaction연결 / 쿼리 worker (사용자 대면 풀)src/connection/server_support.c
vacuumvacuum worker (블록별 로그 replay)src/query/vacuum.c
parallel-query쿼리 내부 병렬 scan/sortsrc/query/parallel/px_worker_manager_global.cpp
loaddbbulk loader workersrc/loaddb/load_worker_manager.cpp
online-index온라인 인덱스 빌더src/storage/btree_load.c
backup-read병렬 read 백업src/transaction/log_page_buffer.c
recovery-redo병렬 WAL redosrc/transaction/log_recovery_redo_parallel.cpp
(per-method)Method/SP 임시 workerexecute_on_core (..., is_temp=true) 로 묵시적

transaction 풀이 SELECT와 DML을 돌리는 풀이다 — “내 SQL을 실제 로 누가 실행하는가 를 물으면 답은 transaction 풀의 worker thread” 다.

flowchart LR
  subgraph WP["worker_pool"]
    direction TB
    Q1["core[0].queue"]
    Q2["core[1].queue"]
    Q3["core[2].queue"]
    A1["available[0]"]
    A2["available[1]"]
    A3["available[2]"]
  end

  Push["push_task"] -->|round-robin| RR{"get_next_core"}
  RR -->|hash%N| Q1 & Q2 & Q3
  Q1 --> Wk1["worker_impl"]
  Q2 --> Wk2["worker_impl"]
  Q3 --> Wk3["worker_impl"]
  Wk1 --> Ex1["execute_current_task"]
  Wk2 --> Ex2["execute_current_task"]
  Wk3 --> Ex3["execute_current_task"]
  Ex1 -->|다음 루프| Get1["get_task_or_become_available"]
  Get1 -->|큐 있음| Wk1
  Get1 -->|큐 없음| A1
  A1 -.idle_timeout.-> Exit1["thread 종료, m_has_thread=false"]

cubthread::daemon — 단일 thread looper

섹션 제목: “cubthread::daemon — 단일 thread looper”

daemon은 OS thread 하나, task 하나, looper 하나로 구성된다. daemon 은 풀을 공유하지 않는다 — 각 daemon이 자기 thread, 자기 waiter, 자기 컨텍스트를 가진다. thread는 daemon 생성자에서 std::thread (daemon::loop_with_context, this, ...) 로 시작되어 stop_execution 에서 (~daemon 이 호출함) join된다.

// daemon::loop_with_context — thread/thread_daemon.cpp
void
daemon::loop_with_context (daemon *daemon_arg, entry_manager *entry_manager_arg,
entry_task *exec_arg, const char *name)
{
pthread_setname_np (pthread_self (),
name[0] ? name : "unnamed-daemon");
entry &context = entry_manager_arg->create_context (); // entry claim, on_daemon_create 실행
daemon_arg->register_stat_start ();
while (!daemon_arg->m_looper.is_stopped ())
{
exec_arg->execute (context); // 주기적 작업 수행
daemon_arg->register_stat_execute ();
daemon_arg->pause (); // looper 정책에 따라 sleep
daemon_arg->register_stat_pause ();
}
entry_manager_arg->stop_execution (context);
entry_manager_arg->retire_context (context);
exec_arg->retire ();
}

looper 가 daemon이 반복 사이에 어떻게 기다릴지를 결정한다 (다음 하위 절 참고). waiter는 daemon별 condvar/mutex다. wakeupm_waiter.wakeup () 을 호출해서 외부 이벤트가 다음 wait를 단축 시킬 수 있게 한다 — page-flush daemon은 dirty 페이지 카운트가 임계치를 넘는 즉시 깨워지고, deadlock detector는 어떤 thread가 설정된 임계치보다 오래 lock wait queue에 있을 때 깨워진다.

stateDiagram-v2
  [*] --> CreateContext
  CreateContext --> ExecuteTask: entry_manager.create_context()
  ExecuteTask --> Pause: exec->execute(ctx)
  Pause --> CheckStopped: looper.put_to_sleep(waiter)
  CheckStopped --> ExecuteTask: !looper.is_stopped()
  CheckStopped --> RetireContext: looper.is_stopped()
  RetireContext --> [*]: entry_manager.retire_context(ctx); exec->retire()

  note right of Pause
    looper가 결정하는 wait 패턴:
    - INF_WAITS: wakeup까지 cond_wait
    - FIXED_WAITS: 고정 주기로 cond_timedwait
    - INCREASING_WAITS: 점증 주기, wakeup 시 리셋
    - CUSTOM_WAITS: 사용자 정의 period_function
  end note

create_daemon 을 grep해서 찾을 수 있다.

Daemon 이름subsystemlooper 정책생성 위치
log-checkpointLog manager고정 주기 (checkpoint_interval)log_manager.c
log-rm-archiveLog manager고정 주기log_manager.c
log-clockLog manager고정 (1s)log_manager.c
ha-delay-checkHA replication고정 주기log_manager.c
log-flushLog managerINF (commit으로 깨워짐)log_manager.c
cdc-loginfo-producerCDCINFlog_manager.c
deadlock-detectLock manager고정 (deadlock_detection_interval)lock_manager.c
dwb-flush-blockDouble-write bufferINF (블록 가득 차면 깨워짐)double_write_buffer.cpp
dwb-file-syncDouble-write bufferINFdouble_write_buffer.cpp
pgbuf-maintainPage buffer고정page_buffer.c
pgbuf-page-flushPage bufferINF (dirty 임계 시 깨워짐)page_buffer.c
pgbuf-page-post-flushPage bufferINFpage_buffer.c
pgbuf-flush-controlPage buffer고정page_buffer.c
session-controlSession고정session.c
vacuum-masterVacuum고정 (master가 WAL 스캔)vacuum.c
pl-monitorPL/Java server고정pl_sr.cpp

이게 daemon 전체 집합이다. cubrid-vacuum.md (vacuum master + worker), cubrid-recovery-manager.md (recovery-redo 풀, daemon 없음), cubrid-page-buffer-manager.md (네 개의 pgbuf daemon + dwb daemon) 와 교차 참조하면 된다.

graph TD
  subgraph "Worker pool (소비자)"
    TXN["transaction (server_support)"]
    VWP["vacuum (vacuum.c)"]
    PXQ["parallel-query (parallel/)"]
    LDDB["loaddb"]
    OIB["online-index (btree_load)"]
    BR["backup-read"]
    RR["recovery-redo"]
  end
  subgraph "Daemon (소비자)"
    LF["log-flush"]
    LCK["log-checkpoint"]
    LRA["log-rm-archive"]
    DD["deadlock-detect"]
    PGM["pgbuf-maintain"]
    PGF["pgbuf-page-flush"]
    PGPF["pgbuf-page-post-flush"]
    PGFC["pgbuf-flush-control"]
    DWB1["dwb-flush-block"]
    DWB2["dwb-file-sync"]
    SC["session-control"]
    VM["vacuum-master"]
    PLM["pl-monitor"]
    HAD["ha-delay-check"]
    CDC["cdc-loginfo-producer"]
    LCK1["log-clock"]
  end
  TM["cubthread::manager"]
  TM ---|추적| TXN
  TM ---|추적| VWP
  TM ---|추적| PXQ
  TM ---|추적| LDDB
  TM ---|추적| OIB
  TM ---|추적| BR
  TM ---|추적| RR
  TM ---|추적| LF
  TM ---|추적| LCK
  TM ---|추적| DD
  TM ---|추적| PGF
  TM ---|추적| DWB1
  TM ---|추적| VM

looper는 작지만 중심에 있다 — daemon의 일하고 자고 루프가 반복 사이에 어떤 간격을 두는지를 결정하는 정책이다. 네 가지 패턴이 있다.

// looper::wait_type — thread/thread_looper.hpp
enum wait_type
{
INF_WAITS, // wakeup까지 무한정 sleep
FIXED_WAITS, // 고정 시간 sleep, 반복
INCREASING_WAITS, // timeout 시 점증 sleep, wakeup 시 리셋
CUSTOM_WAITS, // 임의의 period_function
};

daemon이 호출하는 메서드는 put_to_sleep 단 하나다.

// looper::put_to_sleep — thread/thread_looper.cpp
void
looper::put_to_sleep (waiter &waiter_arg)
{
if (is_stopped ()) return;
bool is_timed_wait = true;
delta_time period = delta_time (0);
m_setup_period (is_timed_wait, period); // 생성 시 바인딩된 정책
if (is_timed_wait)
{
// task 실행 시간을 원하는 주기에서 빼서 daemon이 "execute + period"
// 가 아니라 일정한 wall-clock 속도로 tick하도록 한다
delta_time wait_time = delta_time (0);
delta_time exec = std::chrono::system_clock::now () - m_start_execution_time;
if (period > exec) wait_time = period - exec;
m_was_woken_up = waiter_arg.wait_for (wait_time);
}
else
{
waiter_arg.wait_inf ();
m_was_woken_up = true;
}
m_start_execution_time = std::chrono::system_clock::now ();
}

실행 시간을 빼라 라는 디테일이 중요하다 — 1초 주기로 설정된 daemon이 자기 일을 800ms 만에 끝내면 1000ms이 아니라 200ms를 sleep한다. 그래야 wall-clock 주기가 보존된다.

INCREASING_WAITS 는 back-off 정책이다 — 할 일이 없을 때 일이 빨리 끝나지만 그렇다고 비싸게 polling해서는 안 되는 daemon들에 쓰인다. vacuum master가 이를 쓴다 — WAL을 스캔하다가 작업이 없으면 점증 간격으로 sleep (예: 10ms → 100ms → 1s) 하고, 첫 외부 wakeup hint 가 들어오면 인덱스를 0으로 리셋한다.

cubthread::lockfree_hashmap — 공유 hash map facade

섹션 제목: “cubthread::lockfree_hashmap — 공유 hash map facade”

CUBRID에는 두 개의 lock-free hash map 구현이 나란히 살고 있다 — 구식 lf_hash_table_cpp 와 신식 lockfree::hashmap (split-ordered 방식). thread 계층은 PRM_ID_ENABLE_NEW_LFHASH 에 따라 둘 중 하나를 고르는 단일 템플릿 wrapper를 제공한다.

// cubthread::lockfree_hashmap — thread/thread_lockfree_hash_map.hpp
template <class Key, class T>
class lockfree_hashmap
{
enum type { OLD, NEW, UNKNOWN };
lf_hash_table_cpp<Key, T> m_old_hash;
lockfree::hashmap<Key, T> m_new_hash;
type m_type;
int m_entry_idx; // entry::tran_entries[] 내 인덱스
// ...
};
#define lockfree_hashmap_forward_func(f_, tp_, ...) \
is_old_type () \
? m_old_hash.f_ (get_tran_entry (tp_), __VA_ARGS__) \
: m_new_hash.f_ ((tp_)->get_lf_tran_index (), __VA_ARGS__)

모든 연산은 entry::tran_entries[m_entry_idx] (구식 hash) 또는 entry::get_lf_tran_index() (신식 hash) 로 forward된다 — hash map의 epoch / hazard 스킴이 쓰는 thread별 테이블 descriptor 다. m_entry_idx 슬롯 집합은 THREAD_TS_* enum에 고정돼 있다 — lock-free hashmap의 모든 소비자가 cubthread::entry 안에 슬롯을 미리 잡아 두어야 한다. 현재 슬롯 — THREAD_TS_SPAGE_SAVING, THREAD_TS_OBJ_LOCK_RES, THREAD_TS_OBJ_LOCK_ENT, THREAD_TS_CATALOG, THREAD_TS_SESSIONS, THREAD_TS_FREE_SORT_LIST, THREAD_TS_GLOBAL_UNIQUE_STATS, THREAD_TS_HFID_TABLE, THREAD_TS_XCACHE, THREAD_TS_FPCACHE, THREAD_TS_DWB_SLOTS. 새 lock-free hashmap을 추가하려면 enum에 슬롯을 더하고 entry::request_lock_free_transactions 에서 entry 슬롯을 요청한 뒤 hashmap을 만들어야 한다. cubthread::entry 를 건드리지 않고 확장할 수 있는 지점은 없다.

SYNC_CRITICAL_SECTION (csect) — 무거운 RW gate

섹션 제목: “SYNC_CRITICAL_SECTION (csect) — 무거운 RW gate”

csect는 엔진의 가장 거친 동기화 원시이며, (a) read가 write를 자릿수 단위로 압도하고 (b) wait가 관찰 가능해야 하는 결정적 제어 경로에서 건드려지는 공유 자원에 한정해서 쓰인다.

// SYNC_CRITICAL_SECTION — thread/critical_section.h
typedef struct sync_critical_section
{
const char *name;
int cs_index; // 식별자, csect_Names[] 안의
pthread_mutex_t lock; // monitor lock
int rwlock; // >0 = reader 수, <0 = writer, 0 = 없음
unsigned int waiting_readers;
unsigned int waiting_writers;
pthread_cond_t readers_ok; // reader 깨우기
THREAD_ENTRY *waiting_writers_queue; // 대기 중인 writer FIFO
THREAD_ENTRY *waiting_promoters_queue; // demoted 후 promoting FIFO
thread_id_t owner; // 현재 writer
int tran_index; // 진단/덤프용
SYNC_STATS *stats;
} SYNC_CRITICAL_SECTION;

csect 인덱스 공간은 고정되어 있고 작다 (현재 소스 기준 CSECT_LAST ≈ 18).

// csect/CSECT_* — thread/critical_section.h
enum
{
CSECT_WFG = 0, // wait-for-graph
CSECT_LOG, // log manager
CSECT_LOCATOR_SR_CLASSNAME_TABLE,
CSECT_QPROC_QUERY_TABLE,
CSECT_QPROC_LIST_CACHE,
CSECT_DISK_CHECK,
CSECT_CNV_FMT_LEXER,
CSECT_HEAP_CHNGUESS,
CSECT_TRAN_TABLE,
CSECT_CT_OID_TABLE,
CSECT_HA_SERVER_STATE,
CSECT_COMPACTDB_ONE_INSTANCE,
CSECT_ACL,
CSECT_PARTITION_CACHE,
CSECT_EVENT_LOG_FILE,
CSECT_TRACE_LOG_FILE,
CSECT_LOG_ARCHIVE,
CSECT_ACCESS_STATUS,
CSECT_LAST
};

각 csect는 이름 하나, rwlock 상태를 보호하는 pthread_mutex_t, reader 깨우기용 단일 pthread_cond_t readers_ok, 그리고 두 개의 intrusive FIFO (waiting_writers_queue, waiting_promoters_queue) 의 THREAD_ENTRY * 들을 가진다. 흥미로운 기능은 promotion 과 demotion 이다 — reader로 들어간 thread는 csect_promote 를 호출해서 (먼저 풀지 않고) writer로 in-place 승격할 수 있고, writer 는 csect_demote 로 reader로 강등할 수 있다. lock manager와 schema manager가 read scan 중에 write가 필요한 무언가를 관찰 했을 때 요구하는 패턴이 정확히 이것이다.

각 csect는 stats 포인터도 가진다 — 총 enter, 총 wait, 총 re-enter, 누적 wait 시간, 최대 wait 시간 — csect_dump_statisticscsect_start_scan SHOW handler로 보인다.

debug 빌드 (ENABLE_TRACKERS = !NDEBUG && SERVER_MODE) 에서 각 thread는 cubsync::critical_section_tracker 를 들고 다닌다. 이 tracker는 thread가 각 csect에 몇 번 들어갔는지, 현재 writer인지 (아마도 demoted된) reader인지를 기록하고, 하드코딩된 의존 규칙을 강제한다.

// critical_section_tracker::check_csect_interdependencies — thread/critical_section_tracker.cpp
void
critical_section_tracker::check_csect_interdependencies (int cs_index)
{
if (cs_index == CSECT_LOCATOR_SR_CLASSNAME_TABLE)
{
cstrack_assert (m_cstrack_array[CSECT_CT_OID_TABLE].m_enter_count == 0);
}
}

의도는 이렇다 — CT_OID_TABLE 을 이미 들고 있는 채로 LOCATOR_SR_CLASSNAME_TABLE 을 절대 잡지 마라. 이는 하드코딩된 순서 규칙이다 — 프로젝트가 과거 deadlock 조사로 특정한 두-section 사이클 하나가 금지된다는 것을 알게 됐고, tracker가 이를 강제한다. 새 csect를 tracker를 건드리지 않고 추가할 수 있지만, 새 순서 규칙은 여기에 직접 추가해야 한다.

tracker는 또한 re-enter 카운트를 강제한다 — section당 최대 8회 re-enter (MAX_REENTERS), reader re-enter는 thread가 이미 writer 이거나 demoted 상태일 때만, writer re-enter는 thread가 이미 writer 일 때만. 그 외에는 모두 cstrack_assert (false) 에 걸린다. task 종료 시 entry::end_resource_tracks 가 모든 csect가 풀렸는지, alloc tracker가 균형 맞는지, page-buffer fix 리스트가 비었는지를 확인 한다 — 세 개의 독립적 잡은 건 다 풀어라 assertion이다.

SQL 요청이 어떻게 worker task가 되는가

섹션 제목: “SQL 요청이 어떻게 worker task가 되는가”

조각들을 모아 보자. CSQL 요청이 broker에 도착하고, broker가 그것을 연결 프로토콜로 cub_server 로 forward한다. src/connection /server_support.c 안의 서버 측 connection-state machine이 startup 에 thread_create_stats_worker_pooltransaction 이름으로 호출한다. 들어오는 요청마다 entry_task 하나를 구성하고 thread_get_manager()->push_task 를 호출한다. 풀이 round-robin 으로 core에 dispatch하고, core가 task를 가용 worker에 넘기거나 (또는 큐잉) 한다. worker의 OS thread (필요할 때 spawn) 가 worker_impl::run 을 호출하고, 이는 init_run (entry claim, on_create), 그 다음 execute_current_task (이게 task::execute (entry &) 를 호출한다 — 실제 SQL 경로), 그 다음 recycle_context, 그 다음 get_new_task (다음 요청 또는 become_available) 을 부른다. thread는 idle_timeout 이 다 될 때까지 돌다가 종료되고, 다음 요청 이 새로 spawn한다.

sequenceDiagram
  participant CAS as broker (CAS)
  participant SS as server_support<br/>(연결 thread)
  participant WP as transaction worker_pool
  participant W as worker_impl
  participant T as entry_task<br/>(쿼리 실행자)

  CAS->>SS: 요청 바이트
  SS->>WP: push_task (entry_task)
  WP->>WP: round-robin core
  WP->>W: assign_task (notify 또는 spawn)
  W->>W: init_run -> claim_entry, on_create
  W->>T: execute (entry &)
  T-->>W: 반환
  W->>W: retire_current_task; recycle_context
  W->>WP: get_task_or_become_available
  alt 큐에 task 있음
    WP-->>W: 큐의 task 반환
  else task 없음
    WP-->>W: available로 등록, m_task_cv 위에서 wait
  end

vacuum block 작업, parallel-query exchange, recovery-redo chunk 도 같은 모양을 따른다 — 다른 점은 어느 풀의 push_task 가 호출되고 어떤 entry_task 서브클래스가 push되는지뿐이다. 그 균일성이 worker pool 템플릿의 존재 이유 그 자체다.

심볼을 subsystem별로 묶었다.

  • cubthread::entry — 클래스.
  • cubthread::entry::status — TS_DEAD/FREE/RUN/WAIT/CHECK enum.
  • thread_resume_suspend_status — 프로토콜 enum (THREAD_LOCK_SUSPENDED, THREAD_PGBUF_SUSPENDED, …).
  • cubthread::entry::request_lock_free_transactionstran_entries[THREAD_TS_*] 를 lock-free transaction 시스템에 연결.
  • cubthread::entry::register_id / get_id / unregister_id — OS thread id 저장 / 조회 / 삭제.
  • cubthread::entry::lock / unlockth_entry_lock 위 pthread_mutex.
  • cubthread::entry::end_resource_tracks / push_resource_tracks / pop_resource_tracks — debug 전용 alloc/pgbuf/csect tracker hook.
  • cubthread::entry::claim_system_worker / retire_system_worker — 시스템 TDES가 필요한 daemon용.
  • cubthread::entry::assign_lf_tran_index / pull_lf_tran_index / get_lf_tran_index — 신식 lock-free hashmap descriptor.
  • THREAD_TS_* enum 값 — 테이블별 lock-free transaction descriptor 슬롯 식별자.
  • thread_suspend_wakeup_and_unlock_entry, thread_suspend_timeout_wakeup_and_unlock_entry, thread_wakeup, thread_wakeup_already_had_mutex, thread_check_suspend_reason_and_wakeup, thread_suspend_with_other_mutex — C 호출 가능한 suspend/wake API.
  • thread_type_to_string, thread_status_to_string, thread_resume_status_to_string — pretty-printer.
  • cubthread::manager — 싱글턴 클래스.
  • cubthread::initialize / cubthread::finalize — 라이프사이클.
  • cubthread::initialize_thread_entries — entry 풀 사이징과 lock-free transaction 시스템 bring-up.
  • cubthread::get_manager, get_max_thread_count, get_entry, is_single_thread, check_not_single_thread — 글로벌 accessor.
  • cubthread::manager::alloc_entriesentry[m_max_threads] 와 dispatcher 할당.
  • cubthread::manager::init_entries — entry별 init, 선택적 lock-free 결선.
  • cubthread::manager::init_lockfree_systemlockfree::tran::system 생성.
  • cubthread::manager::create_worker_pool<Res> — 템플릿 (hpp 참고).
  • cubthread::manager::destroy_worker_pool — 풀 정지 + entry 반환.
  • cubthread::manager::push_task / cubthread::manager::push_task_on_core — dispatch.
  • cubthread::manager::create_daemon / cubthread::manager::create_daemon_without_entry / cubthread::manager::destroy_daemon / cubthread::manager::destroy_daemon_without_entry.
  • cubthread::manager::set_max_thread_count_from_configcount_registry<connection>::total() + count_registry<worker_pool>::total() + count_registry<daemon>::total() + 1 을 읽음.
  • cubthread::manager::claim_entry / retire_entrytl_Entry_p 를 통한 thread-local entry 배관.
  • cubthread::manager::find_by_tid — entry 조회.
  • cubthread::manager::map_entries — SHOW handler가 쓰는 iteration helper.
  • REGISTER_CONNECTION / REGISTER_WORKERPOOL / REGISTER_DAEMON 매크로 (count_registry API).
  • cubthread::is_logging_configured_er_log_debug 의 런타임 gate.

cubthread::worker_pool (worker pool 템플릿)

섹션 제목: “cubthread::worker_pool (worker pool 템플릿)”
  • cubthread::worker_pool — 추상 base.
  • cubthread::worker_pool::core — 파티션의 추상 base.
  • cubthread::worker_pool::core::worker — 개별 worker의 추상 base.
  • cubthread::worker_pool_impl<bool Stats> — 템플릿 구현. worker_pool_type (stats 없음) 과 stats_worker_pool_type (stats 있음) 이 두 개의 alias.
  • cubthread::worker_pool_impl::initialize, cubthread::worker_pool_impl::execute, cubthread::worker_pool_impl::execute_on_core, cubthread::worker_pool_impl::warmup, cubthread::worker_pool_impl::stop_execution, cubthread::worker_pool_impl::is_running, cubthread::worker_pool_impl::get_worker_count, cubthread::worker_pool_impl::get_core_count — worker-pool API.
  • cubthread::worker_pool_impl::map_running_contexts / map_cores — 실행 중 thread / core 위 debug iteration.
  • cubthread::worker_pool_impl::core_impl — 구체 파티션, m_workers, m_available_workers, m_task_queue, m_temp_workers 소유.
  • cubthread::worker_pool_impl::core_impl::execute_task — assign 또는 enqueue.
  • cubthread::worker_pool_impl::core_impl::get_task_or_become_available — worker 재진입.
  • cubthread::worker_pool_impl::core_impl::execute_task_as_temp — 풀 슬롯을 쓰지 않는 ad-hoc method/SP worker.
  • cubthread::worker_pool_impl::core_impl::worker_impl — 구체 worker. m_context_p, m_wrapped_task, m_task_cv, m_task_mutex, m_stop, m_has_thread, m_is_temp 소유.
  • cubthread::worker_pool_impl::core_impl::worker_impl::run — thread main.
  • cubthread::worker_pool_impl::core_impl::worker_impl::init_run / finish_run — 컨텍스트 생성 / 반환.
  • cubthread::worker_pool_impl::core_impl::worker_impl::assign_task / start_thread / get_new_task / execute_current_task / retire_current_task / stop_execution — worker 제어 흐름.
  • cubthread::worker_pool_impl::wrapped_task — task 포인터 + 선택적 timing.
  • cubthread::worker_pool_impl::stats — worker별 카운터 정의 (start_thread, create_context, execute_task, retire_task, found_in_queue, wakeup_with_task, recycle_context, retire_context).
  • cubthread::worker_pool_task_capper — bounded-queue wrapper.
  • cubthread::system_core_count, cubthread::wp_handle_system_error, cubthread::wp_set_force_thread_always_alive, cubthread::wp_is_thread_always_alive_forced — 유틸리티.
  • cubthread::daemon — 단일 thread looper 클래스.
  • cubthread::daemon::loop_with_context, cubthread::daemon::loop_without_context — thread main.
  • cubthread::daemon::wakeup, cubthread::daemon::stop_execution, cubthread::daemon::pause, cubthread::daemon::was_woken_up, cubthread::daemon::reset_looper, cubthread::daemon::is_running — 제어.
  • cubthread::daemon::get_stats, cubthread::daemon::get_stats_value_count, cubthread::daemon::get_stat_name — stats facade.
  • cubthread::looper — wait 패턴.
  • cubthread::looper::wait_type — INF / FIXED / INCREASING / CUSTOM.
  • cubthread::looper::put_to_sleep — 호출되는 메서드.
  • cubthread::looper::reset — INCREASING_WAITS가 wakeup에서 사용.
  • cubthread::looper::stop, cubthread::looper::is_stopped — shutdown gate.
  • cubthread::looper::setup_fixed_waits, cubthread::looper::setup_infinite_wait, cubthread::looper::setup_increasing_waits — 생성 시 바인딩되는 내부 정책 함수.
  • cubthread::waiter — looper가 쓰는 daemon별 mutex+condvar.
  • cubthread::condvar_waitwait_duration<D> (infinite 플래그) 를 처리하는 오버로드.
  • cubthread::task<Context>execute(Context &) + retire() 를 갖는 모든 task의 추상 base.
  • cubthread::callable_task<Context>std::function 기반 task.
  • cubthread::task<void> / task_without_context / callable_task<void> — 컨텍스트 없는 특수화.
  • cubthread::entry_task = task<entry> — 표준 어댑터.
  • cubthread::entry_callable_task = callable_task<entry>.
  • cubthread::entry_managercreate_context / retire_context / recycle_context / stop_execution.
  • cubthread::daemon_entry_manageron_daemon_create / on_daemon_retire 를 가진 daemon 전용 특수화.
  • cubthread::system_worker_entry_managerentry::type 과 시스템 TDES 컨텍스트를 세팅한 사전 제작 매니저.
  • cubthread::lockfree_hashmap<Key, T> — old/new 구현 facade.
  • cubthread::lockfree_hashmap::iterator.
  • cubthread::lockfree_hashmap::init, init_as_old, init_as_new, destroy.
  • cubthread::lockfree_hashmap::find, find_or_insert, insert, insert_given, erase, erase_locked, unlock, clear, freelist_claim, freelist_retire, start_tran, end_tran.
  • cubthread::lockfree_hashmap::is_old_type, cubthread::lockfree_hashmap::get_tran_entry — 내부 forwarding glue. 매크로 lockfree_hashmap_forward_func / lockfree_hashmap_forward_func_noarg 가 old 또는 new로 라우팅.
  • cubthread::get_thread_entry_lftransys — 신식 lock-free transaction 시스템의 accessor.
  • cubthread::entry::tran_entries 안의 THREAD_TS_* 슬롯 — 테이블별 descriptor 식별자 (thread_entry.hpp 안에).
  • SYNC_CRITICAL_SECTION (struct) — RW gate 상태.
  • SYNC_RWLOCK, SYNC_RMUTEX (struct) — 형제 원시.
  • SYNC_STATS, SYNC_PRIMITIVE_TYPE, SYNC_TYPE_CSECT/RWLOCK/RMUTEX/MUTEX — 공통 stats 배관.
  • csect_initialize_static_critical_sections, csect_finalize_static_critical_sections — bring-up.
  • csect_initialize_critical_section, csect_finalize_critical_section — 단일 csect 라이프사이클.
  • csect_enter, csect_enter_as_reader, csect_enter_critical_section, csect_enter_critical_section_as_reader, csect_demote, csect_promote, csect_exit, csect_exit_critical_section — 주 API.
  • csect_check_own — assertion helper.
  • csect_dump_statistics, csect_start_scan, csect_name_at — 진단.
  • rwlock_initialize, rwlock_finalize, rwlock_read_lock, rwlock_read_unlock, rwlock_write_lock, rwlock_write_unlock — 비재진입 RW lock 변종.
  • rmutex_initialize, rmutex_finalize, rmutex_lock, rmutex_unlock — 재귀 mutex 변종.
  • sync_initialize_sync_stats, sync_finalize_sync_stats, sync_dump_statistics — 공통 stats.
  • cubsync::critical_section_tracker — thread별 debug tracker. start, stop, clear_all, on_enter_as_reader, on_enter_as_writer, on_promote, on_demote, on_exit, check_csect_interdependencies — debug 빌드에서 쓰이는 tracker API.
심볼파일라인
cubthread::manager (class)src/thread/thread_manager.hpp111
cubthread::manager::push_tasksrc/thread/thread_manager.cpp157
cubthread::manager::create_daemonsrc/thread/thread_manager.cpp126
cubthread::manager::create_worker_pool (template)src/thread/thread_manager.hpp367
cubthread::manager::set_max_thread_count_from_configsrc/thread/thread_manager.cpp266
cubthread::manager::claim_entry / retire_entrysrc/thread/thread_manager.cpp234 / 242
cubthread::initializesrc/thread/thread_manager.cpp315
cubthread::initialize_thread_entriessrc/thread/thread_manager.cpp378
REGISTER_CONNECTION/WORKERPOOL/DAEMON 매크로src/thread/thread_manager.hpp496–498
cubthread::entry (class)src/thread/thread_entry.hpp195
cubthread::entry::status enumsrc/thread/thread_entry.hpp202
THREAD_TS_* enumsrc/thread/thread_entry.hpp81
thread_resume_suspend_status enumsrc/thread/thread_entry.hpp139
thread_type enumsrc/thread/thread_entry.hpp124
cubthread::entry ctorsrc/thread/thread_entry.cpp78
cubthread::entry::request_lock_free_transactionssrc/thread/thread_entry.cpp220
thread_suspend_wakeup_and_unlock_entrysrc/thread/thread_entry.cpp497
thread_wakeup_internalsrc/thread/thread_entry.cpp600
thread_suspend_with_other_mutexsrc/thread/thread_entry.cpp688
cubthread::worker_pool (base)src/thread/thread_worker_pool.hpp54
cubthread::worker_pool::coresrc/thread/thread_worker_pool.hpp123
cubthread::worker_pool::core::workersrc/thread/thread_worker_pool.hpp178
cubthread::worker_pool_implsrc/thread/thread_worker_pool_impl.hpp105
worker_pool_impl::core_implsrc/thread/thread_worker_pool_impl.hpp263
core_impl::worker_implsrc/thread/thread_worker_pool_impl.hpp339
core_impl::execute_tasksrc/thread/thread_worker_pool_impl.hpp896
core_impl::get_task_or_become_availablesrc/thread/thread_worker_pool_impl.hpp1012
core_impl::execute_task_as_tempsrc/thread/thread_worker_pool_impl.hpp1194
worker_impl::runsrc/thread/thread_worker_pool_impl.hpp1387
worker_impl::init_run / finish_runsrc/thread/thread_worker_pool_impl.hpp1430 / 1457
worker_impl::assign_task (with task)src/thread/thread_worker_pool_impl.hpp1267
worker_impl::get_new_tasksrc/thread/thread_worker_pool_impl.hpp1521
worker_impl::stop_executionsrc/thread/thread_worker_pool_impl.hpp1326
worker_pool_impl::stop_executionsrc/thread/thread_worker_pool_impl.hpp594
worker_pool_task_cappersrc/thread/thread_worker_pool_taskcap.hpp30
cubthread::daemonsrc/thread/thread_daemon.hpp87
daemon::loop_with_contextsrc/thread/thread_daemon.cpp209
daemon::loop_without_contextsrc/thread/thread_daemon.cpp248
daemon::stop_executionsrc/thread/thread_daemon.cpp90
cubthread::loopersrc/thread/thread_looper.hpp81
looper::wait_type enumsrc/thread/thread_looper.hpp132
looper::put_to_sleepsrc/thread/thread_looper.cpp119
looper::setup_increasing_waitssrc/thread/thread_looper.cpp208
cubthread::lockfree_hashmapsrc/thread/thread_lockfree_hash_map.hpp35
lockfree_hashmap_forward_func 매크로src/thread/thread_lockfree_hash_map.hpp162
SYNC_CRITICAL_SECTION structsrc/thread/critical_section.h110
CSECT_* enumsrc/thread/critical_section.h57
csect_Names[]src/thread/critical_section.c76
csect_initialize_static_critical_sectionssrc/thread/critical_section.c243
csect_entersrc/thread/critical_section.c674
csect_enter_critical_sectionsrc/thread/critical_section.c474
csect_enter_as_readersrc/thread/critical_section.c891
cubsync::critical_section_trackersrc/thread/critical_section_tracker.hpp32
critical_section_tracker::on_enter_as_readersrc/thread/critical_section_tracker.cpp60
critical_section_tracker::check_csect_interdependenciessrc/thread/critical_section_tracker.cpp188
소비자파일라인
transaction worker poolsrc/connection/server_support.c581
vacuum worker poolsrc/query/vacuum.c1342
vacuum-master daemonsrc/query/vacuum.c1352
parallel-query worker poolsrc/query/parallel/px_worker_manager_global.cpp69
loaddb worker poolsrc/loaddb/load_worker_manager.cpp124
online-index worker poolsrc/storage/btree_load.c5315
backup-read worker poolsrc/transaction/log_page_buffer.c7546
recovery-redo worker poolsrc/transaction/log_recovery_redo_parallel.cpp662
log-checkpoint daemonsrc/transaction/log_manager.c10415
log-rm-archive daemonsrc/transaction/log_manager.c10440
log-clock daemonsrc/transaction/log_manager.c10457
ha-delay-check daemonsrc/transaction/log_manager.c10482
log-flush daemonsrc/transaction/log_manager.c10500
cdc-loginfo-producer daemonsrc/transaction/log_manager.c14046
deadlock-detect daemonsrc/transaction/lock_manager.c5820
dwb-flush-block daemonsrc/storage/double_write_buffer.cpp4078
dwb-file-sync daemonsrc/storage/double_write_buffer.cpp4092
pgbuf-maintain daemonsrc/storage/page_buffer.c16538
pgbuf-page-flush daemonsrc/storage/page_buffer.c16556
pgbuf-page-post-flush daemonsrc/storage/page_buffer.c16580
pgbuf-flush-control daemonsrc/storage/page_buffer.c16604
session-control daemonsrc/session/session.c588
pl-monitor daemonsrc/sp/pl_sr.cpp266

이 문서는 여러 다른 문서들의 토대다. 다음과 함께 읽어야 한다.

  • cubrid-vacuum.md (master + worker). vacuum의 master는 위 daemon 목록 중 하나이고, vacuum의 worker는 worker pool 중 하나 다. master는 고정 looper를 가진 cubthread::daemon 으로, 매 tick 마다 WAL을 앞으로 스캔하며 vacuum 풀 위에서 thread_get_manager()->push_task 를 호출해 블록별 작업을 dispatch한다. 모든 vacuum worker thread는 자기 cubthread::entry 위에 vacuum_worker * 를 들고 있다 (vacuum_Worker_entry_manageron_create 를 override해서 붙인다). 블록별 작업 로직은 그곳 을 보라 — 본 문서에 있는 것은 그 토대다.
  • cubrid-recovery-manager.md (parallel redo worker). recovery 의 parallel redo는 log_recovery_redo_parallel.cpp 에 등록된 recovery-redo worker pool을 쓴다. 이 worker pool은 server startup이 아니라 recovery 흐름 안 에서 만들어지고, recovery 끝 에 파괴된다 — manager가 호스트하는 유일한 단명 풀이다. 그쪽 worker는 redo 작업 stream에 대한 backpressure를 위해 worker_pool_task_capper 로 핀한다.
  • cubrid-page-buffer-manager.md (flush daemon, post-flush daemon, flush-control, maintain). page-buffer 모듈에는 네 개의 daemon이 살고, 모두 pgbuf_initialize 안에서 고정 주기 또는 무한 looper로 만들어진다. 그 wakeup hint는 page-buffer hot path 에서 온다 — dirty 카운트가 pgbuf_flush_threshold 를 넘으면 buffer manager가 pgbuf_Page_flush_daemon->wakeup() 을 호출한다. flush-control 피드백 산식은 그곳을 봐야 하고, 나머지 (daemon 메커니즘) 가 본 문서에 있다.
  • cubrid-lock-manager.md (deadlock detector). deadlock-detect daemon은 src/transaction/lock_manager.c 안에 있고 looper 주기는 prm_get_integer_value (PRM_ID_DEADLOCK_DETECTION_INTERVAL_SECS) 로 고정돼 있다. 그 task는 lock 테이블에 유지되는 wait-for graph를 스캔한다 — 그 자체가 LK_RES/LK_ENT 슬롯 (THREAD_TS_OBJ_LOCK_RES/THREAD_TS_OBJ_LOCK_ENT) 위의 cubthread::lockfree_hashmap 이다. 관련된 csect는 CSECT_WFG 다.
  • cubrid-log-manager.md (log flush daemon, archive removal, CDC). log 모듈에는 다섯 daemon이 산다 — log-flush, log-checkpoint, log-rm-archive, log-clock, ha-delay-check, 그리고 cdc-loginfo-producer. 모두 REGISTER_DAEMON(log_*) 매크로로 함께 등록된다.
  • cubrid-2pc.mdcubrid-ha-replication.md. 이들은 src/thread/ 본체 바깥에 사는 daemon들로 교차 참조된다 (master 프로세스의 heartbeat thread는 src/executables/master_heartbeat.c 안에서 다른 패턴을 쓴다 — 그건 cubrid-heartbeat.md 를 보라). 본 문서는 엄밀히 in-server thread 시스템에 관한 것이다.

다음은 짚어 둘 만한 drift 지점이다.

  • entry 클래스 헤더는 자기 자신이 public 필드에서 멀어지는 refactor 중간에 있다고 명시한다. src/thread/thread_entry.hpp 안의 TODO (“make member variable private, remove content that does not belong here, migrate here thread entry related functionality from thread.c/h”) 는 한 번에 끝나지 않는다 — 한동안 은 새 코드가 accessor 호출과 직접 필드 접근을 섞어 쓸 것이라고 예상해야 한다.
  • lock-free hashmap facade에는 개의 살아 있는 구현 (OLD, NEW) 이 있고 PRM_ID_ENABLE_NEW_LFHASH 로 선택된다. 의도는 분명히 구식 lf_hash_table_cpp 를 나중에 폐기하는 것이고, 그 때까지는 lockfree_hashmap_forward_func 매크로가 모든 호출 뒤 에 어느 쪽이 쓰이고 있는지를 숨긴다. 매크로에서 벗어나는 새 코드는 OLD 전용 경로만 보게 된다.
  • csect 집합은 고정 크기다 (CSECT_LAST ≈ 18). 새 csect는 enum과 csect_Names[] 에 추가해야 하고, 새 cstrack_entry 슬롯은 thread별 tracker에 자동으로 들어간다. check_csect_interdependencies 안의 의존 규칙은 CSECT_LOCATOR_SR_CLASSNAME_TABLECSECT_CT_OID_TABLE 에 하드코딩돼 있다. 새로운 부분 순서가 필요하면 그곳에 항목을 추가해야 한다.
  • count_registry 기반 사이징은 daemon이나 풀이 file scope에 REGISTER_* 매크로 없이 추가되면 create_* 시점에 조용히 실패한다 (entry 풀이 너무 작아서 NULL 반환). 오류는 복구 가능하지만 놓치기 쉽다 — 등록이 곧 계약이다.
  • round-robin 카운터가 왜 m_cores.size() 가 아니라 m_max_workers 모듈로 증가하는가? get_round_robin_core_hash 의 코멘트는 core 크기에 비례하는 배정을 보존한다 고 말하지만 modulus는 worker 수이지 core 수가 아니다. 균등하게 분할된 core를 는 산술이 맞지만, 불균등한 core (worker_count가 core_count로 나누어 떨어지지 않을 때) 에 대해서는 dispatch가 앞쪽 (remainder) core들로 편향된다.
  • production에서는 무엇이 pool_threads=true 를 세팅하는가? 이 플래그는 idle-timeout/exit 경로를 통째로 우회한다. loaddbbackup-read 에는 명시적으로 결선돼 있고 transaction 풀은 기본값을 쓴다. 성능 테스트 override (wp_set_force_thread_always_alive) 는 PRM_ID_PERF_TEST_MODE 가 켜면 활성화된다. 내가 추적하지 못한 추가적인 묵시적 활성화가 있을 수 있다.
  • worker 컨텍스트가 매우 다른 task들에 걸쳐 살아남을 수 있는가? task 사이에 recycle_context 가 호출되어 entry의 tracker와 TDES는 리셋되지만 private_heap_id 는 유지된다. task가 private heap에 할당하고 retire에서만 해제한다면 오래 사는 재활용 entry는 fragmentation을 누적할 수 있다. 실제로 tracker가 debug 빌드에서 이를 assert하지만 release-mode 동작 은 조용하다.
  • csect tracker는 production에 영향을 주는가? ENABLE_TRACKERS!NDEBUG && SERVER_MODE 다. release 경로는 정적으로 죽어 있다. 의존 규칙 검사도 debug 전용이다. 즉 CSECT_LOCATOR_SR_CLASSNAME_TABLECSECT_CT_OID_TABLE 역전 에 대한 production 보호는 코드베이스의 코딩 규율이지 런타임 assertion이 아니다.
  • 중간 m_temp_workers 리스트와 register_free_temp_list 는 thread는 필요하지만 풀 슬롯을 소비해서는 안 되는 method/SP 실행을 위해 설계됐다. 부하가 걸렸을 때 단일 풀이 spawn할 수 있는 temp worker 수에 강한 상한이 있는가? 범람되면 무한정 OS thread를 spawn할 수 있다. taskcap wrapper는 도움이 되지만 큐잉 된 task에만 적용되지 is_temp=true 경로에는 적용되지 않는다.
  • looper::put_to_sleep 안의 주기에서 실행 시간을 빼라 디테일은 m_start_execution_time 이 monotonic이라고 가정한다. 이는 std::chrono::system_clock 을 쓰는데, Linux에서는 wall-clock이며 NTP 조정 시 뒤로 갈 수 있다. 음수가 된 period - execution_timedelta_time(0) 이 되므로 daemon이 sleep하지 않는다 — 괜찮다. 그러나 앞으로 점프하면 daemon이 한 tick을 건너 뛸 수 있다. 초 단위 granularity에서는 아마 중요하지 않지만 짚어 둘 만하다.
  • src/thread/thread_manager.cpp, src/thread/thread_manager.hpp — manager 싱글턴과 그 할당, 등록, 라이프사이클.
  • src/thread/thread_entry.cpp, src/thread/thread_entry.hppcubthread::entry 와 C 호출 가능한 suspend/wake API.
  • src/thread/thread_worker_pool.hpp — 추상 base 클래스 (worker_pool, core, worker).
  • src/thread/thread_worker_pool_impl.hpp, src/thread/thread_worker_pool_impl.cpp — 템플릿화된 worker_pool_impl<bool Stats> 와 그 core/worker 구현.
  • src/thread/thread_worker_pool_taskcap.hpp, src/thread/thread_worker_pool_taskcap.cppworker_pool_task_capper, bounded-queue wrapper.
  • src/thread/thread_daemon.hpp, src/thread/thread_daemon.cpp — 단일 thread looper.
  • src/thread/thread_looper.hpp, src/thread/thread_looper.cpp — wait 정책.
  • src/thread/thread_waiter.hpp, src/thread/thread_waiter.cppcubthread::waiter (daemon별 mutex+condvar).
  • src/thread/thread_task.hpp, src/thread/thread_entry_task.hpp, src/thread/thread_entry_task.cpptask<Context>, entry_task, entry_manager 와 친구들.
  • src/thread/thread_lockfree_hash_map.hpp, src/thread/thread_lockfree_hash_map.cppcubthread::lockfree_hashmap facade.
  • src/thread/critical_section.h, src/thread/critical_section.cSYNC_CRITICAL_SECTION 과 csect API.
  • src/thread/critical_section_tracker.hpp, src/thread/critical_section_tracker.cpp — thread별 debug tracker.
  • grep -nE 'create_worker_pool|thread_create_worker_pool|thread_create_stats_worker_pool|create_daemon' 으로 찾은 교차 참조:
    • src/connection/server_support.c (transaction 풀)
    • src/query/vacuum.c (vacuum 풀 + master daemon)
    • src/query/parallel/px_worker_manager_global.cpp (parallel-query 풀)
    • src/loaddb/load_worker_manager.cpp (loaddb 풀)
    • src/storage/btree_load.c (online-index 풀)
    • src/transaction/log_page_buffer.c (backup-read 풀)
    • src/transaction/log_recovery_redo_parallel.cpp (recovery-redo 풀)
    • src/transaction/log_manager.c (여섯 개의 log/HA/CDC daemon)
    • src/transaction/lock_manager.c (deadlock-detect daemon)
    • src/storage/double_write_buffer.cpp (두 개의 DWB daemon)
    • src/storage/page_buffer.c (네 개의 pgbuf daemon)
    • src/session/session.c (session-control daemon)
    • src/sp/pl_sr.cpp (pl-monitor daemon)
  • 이론: Petrov, Database Internals, Ch. 14 “Concurrency Control”; Herlihy & Shavit, The Art of Multiprocessor Programming (Treiber stack, Michael–Scott queue, split-ordered hashmap); Maged M. Michael, High Performance Dynamic Lock-Free Hash Tables and List-Based Sets (SPAA 2002); 신식 lock-free hashmap 모양의 Java ConcurrentHashMap 혈통.