(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가 기다릴 만한 자리
마다 깔아 둔다.
이 두 프레이밍이 모든 실제 엔진을 빚는 네 개의 구현 결정을 낳는다. 본 문서의 나머지 골격이기도 하다.
-
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에서 공짜로 받던 것을 흉내 내야 한다는 점이다.
-
큐가 하나인가, 여럿인가? 글로벌 큐 하나는 단순하고 정확하지만 경합이 심하다. 표준적인 정련은 분할된 큐 다. worker를 그룹 (core, partition, shard 어떻게 부르든) 으로 나누고 각 그룹에 자기 task 큐를 둔 뒤, task마다 hash나 round-robin이나 NUMA 근접도로 큐를 고른다. 비용은 부하 불균형 (한 shard가 굶주리는 동안 다른 shard에 작업이 쌓일 수 있음) 이고 이득은 각 shard 큐의 lock이 거의 경합되지 않는다는 점이다. CUBRID은 분할된 큐 + round-robin dispatch를 고른다.
-
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를 영원히 살려 둘 수 있게 해 준다. -
느린 경로를 무엇으로 보호하는가? 모든 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_SECTIONRW 원시 (csect), 그리고 hot 자료 구조용lockfree::hashmap과lockfree::circular_queue(base모듈 안에 있다).
이 네 결정이 명명되고 나면, 본 문서의 모든 CUBRID-specific 구조는 그 결정 중 하나를 구현하거나 그 구현을 더 빠르게 만든다는 관점으로 읽힌다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”동시 연결을 지원하는 모든 DBMS는 어떤 형태로든 thread 시스템을 출하한다. 그 모양은 한 줌의 패턴으로 수렴한다. CUBRID의 다음 절에서의 구체적 선택은 이 공유된 설계 공간 안의 다이얼 한 세트 로 읽는 것이 가장 좋다.
Thread-per-connection (PostgreSQL)
섹션 제목: “Thread-per-connection (PostgreSQL)”PostgreSQL의 코어는 thread pool 이 아니라 process-per-connection
모델을 쓴다. postmaster가 연결마다 backend를 fork한다. backend
는 연결이 닫힐 때까지 자기 MyProc 와 PGPROC 공유 메모리 슬롯
을 들고 있다. 백그라운드 프로세스 (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 집합을 가진 단일 멀티스레드 프로세스다.
Daemon 집합 (모든 엔진)
섹션 제목: “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은 이 중 하나에 들어 맞는다.
자체 RW-lock 과 tracker
섹션 제목: “자체 RW-lock 과 tracker”단일 공유 자원이 끊임없이 읽히고 드물게 쓰이는 (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-free 공유 구조”현대 엔진에서 일관되게 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의 구현
섹션 제목: “CUBRID의 구현”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.cppvoidmanager::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_RESUMED를thread_wakeup에 넘겨, 깨어난 waiter가 자기가 옳은 이유로 깨어났는지를 검증할 수 있게 하고 spurious signal과 구별 한다. - synchronization — entry별
pthread_mutex_tth_entry_lock더하기pthread_cond_twakeup_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.hppclass 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가 풀링되는 모든 이유이기도 하다.
Suspend / wake 프로토콜
섹션 제목: “Suspend / wake 프로토콜”서버의 모든 blocking 원시는 결국 entry의 mutex+condvar 쌍 위에 얹힌 두 C-style 함수로 환원된다. 이 둘을 한 번 읽으면 엔진의 나머지가 읽힌다.
// thread_suspend_wakeup_and_unlock_entry — thread/thread_entry.cppvoidthread_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.cppstatic voidthread_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이 되도록 이를 쓴다.
cubthread::manager — orchestrator
섹션 제목: “cubthread::manager — orchestrator”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.hppclass 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.cppvoidmanager::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_pool
이 NULL 을 반환한다.
// 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_entry 가 resource_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.cppentry *manager::claim_entry (void){ tl_Entry_p = m_entry_dispatcher->claim (); return tl_Entry_p;}
voidmanager::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_manager 는
on_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 가 보는 런타임
다형 인터페이스다.
Pool → Core → Worker → Task
섹션 제목: “Pool → Core → Worker → 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.hppvoidworker_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.hppstd::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 thread lifecycle
섹션 제목: “Worker thread lifecycle”worker는 풀 안에 영속적으로 사는 C++ 객체다. 오고 가는 것은 OS
thread다. assign_task 는 worker가 thread가 있으면
(m_has_thread) 그것에 알림을 보내고, 없으면 새로 spawn한다.
// worker_impl::assign_task — thread/thread_worker_pool_impl.hppvoidworker_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.hppvoidworker_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_run 이 m_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.hppboolworker_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이다.
Task wrapper, statistics
섹션 제목: “Task wrapper, statistics”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) 가 카운터를 컴파일 시에 버린다.
Task-cap (rate limiting)
섹션 제목: “Task-cap (rate limiting)”어떤 풀은 범람되어서는 안 된다 — 만약 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_pool
과 thread_create_stats_worker_pool 을 grep해서 찾을 수 있다.
| 풀 이름 | subsystem | 생성 위치 |
|---|---|---|
transaction | 연결 / 쿼리 worker (사용자 대면 풀) | src/connection/server_support.c |
vacuum | vacuum worker (블록별 로그 replay) | src/query/vacuum.c |
parallel-query | 쿼리 내부 병렬 scan/sort | src/query/parallel/px_worker_manager_global.cpp |
loaddb | bulk loader worker | src/loaddb/load_worker_manager.cpp |
online-index | 온라인 인덱스 빌더 | src/storage/btree_load.c |
backup-read | 병렬 read 백업 | src/transaction/log_page_buffer.c |
recovery-redo | 병렬 WAL redo | src/transaction/log_recovery_redo_parallel.cpp |
| (per-method) | Method/SP 임시 worker | execute_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.cppvoiddaemon::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다. wakeup 이
m_waiter.wakeup () 을 호출해서 외부 이벤트가 다음 wait를 단축
시킬 수 있게 한다 — page-flush daemon은 dirty 페이지 카운트가
임계치를 넘는 즉시 깨워지고, deadlock detector는 어떤 thread가
설정된 임계치보다 오래 lock wait queue에 있을 때 깨워진다.
Daemon FSM
섹션 제목: “Daemon FSM”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
Daemon 소비자
섹션 제목: “Daemon 소비자”create_daemon 을 grep해서 찾을 수 있다.
| Daemon 이름 | subsystem | looper 정책 | 생성 위치 |
|---|---|---|---|
log-checkpoint | Log manager | 고정 주기 (checkpoint_interval) | log_manager.c |
log-rm-archive | Log manager | 고정 주기 | log_manager.c |
log-clock | Log manager | 고정 (1s) | log_manager.c |
ha-delay-check | HA replication | 고정 주기 | log_manager.c |
log-flush | Log manager | INF (commit으로 깨워짐) | log_manager.c |
cdc-loginfo-producer | CDC | INF | log_manager.c |
deadlock-detect | Lock manager | 고정 (deadlock_detection_interval) | lock_manager.c |
dwb-flush-block | Double-write buffer | INF (블록 가득 차면 깨워짐) | double_write_buffer.cpp |
dwb-file-sync | Double-write buffer | INF | double_write_buffer.cpp |
pgbuf-maintain | Page buffer | 고정 | page_buffer.c |
pgbuf-page-flush | Page buffer | INF (dirty 임계 시 깨워짐) | page_buffer.c |
pgbuf-page-post-flush | Page buffer | INF | page_buffer.c |
pgbuf-flush-control | Page buffer | 고정 | page_buffer.c |
session-control | Session | 고정 | session.c |
vacuum-master | Vacuum | 고정 (master가 WAL 스캔) | vacuum.c |
pl-monitor | PL/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
cubthread::looper — wait 패턴
섹션 제목: “cubthread::looper — wait 패턴”looper는 작지만 중심에 있다 — daemon의 일하고 자고 루프가 반복 사이에 어떤 간격을 두는지를 결정하는 정책이다. 네 가지 패턴이 있다.
// looper::wait_type — thread/thread_looper.hppenum 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.cppvoidlooper::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.hpptemplate <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.htypedef 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.henum{ 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_statistics 와
csect_start_scan SHOW handler로 보인다.
Critical-section tracker
섹션 제목: “Critical-section tracker”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.cppvoidcritical_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_pool 을 transaction 이름으로
호출한다. 들어오는 요청마다 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 (thread별 컨텍스트)
섹션 제목: “cubthread::entry (thread별 컨텍스트)”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_transactions—tran_entries[THREAD_TS_*]를 lock-free transaction 시스템에 연결.cubthread::entry::register_id/get_id/unregister_id— OS thread id 저장 / 조회 / 삭제.cubthread::entry::lock/unlock—th_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 (orchestrator)
섹션 제목: “cubthread::manager (orchestrator)”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_entries—entry[m_max_threads]와 dispatcher 할당.cubthread::manager::init_entries— entry별 init, 선택적 lock-free 결선.cubthread::manager::init_lockfree_system—lockfree::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_config—count_registry<connection>::total()+count_registry<worker_pool>::total()+count_registry<daemon>::total()+ 1 을 읽음.cubthread::manager::claim_entry/retire_entry—tl_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_registryAPI).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 과 cubthread::looper
섹션 제목: “cubthread::daemon 과 cubthread::looper”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_wait—wait_duration<D>(infinite플래그) 를 처리하는 오버로드.
cubthread::task 와 entry-task 어댑터
섹션 제목: “cubthread::task 와 entry-task 어댑터”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_manager—create_context/retire_context/recycle_context/stop_execution.cubthread::daemon_entry_manager—on_daemon_create/on_daemon_retire를 가진 daemon 전용 특수화.cubthread::system_worker_entry_manager—entry::type과 시스템 TDES 컨텍스트를 세팅한 사전 제작 매니저.
Lock-free hash map
섹션 제목: “Lock-free hash map”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안에).
Critical section
섹션 제목: “Critical section”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.hpp | 111 |
cubthread::manager::push_task | src/thread/thread_manager.cpp | 157 |
cubthread::manager::create_daemon | src/thread/thread_manager.cpp | 126 |
cubthread::manager::create_worker_pool (template) | src/thread/thread_manager.hpp | 367 |
cubthread::manager::set_max_thread_count_from_config | src/thread/thread_manager.cpp | 266 |
cubthread::manager::claim_entry / retire_entry | src/thread/thread_manager.cpp | 234 / 242 |
cubthread::initialize | src/thread/thread_manager.cpp | 315 |
cubthread::initialize_thread_entries | src/thread/thread_manager.cpp | 378 |
REGISTER_CONNECTION/WORKERPOOL/DAEMON 매크로 | src/thread/thread_manager.hpp | 496–498 |
cubthread::entry (class) | src/thread/thread_entry.hpp | 195 |
cubthread::entry::status enum | src/thread/thread_entry.hpp | 202 |
THREAD_TS_* enum | src/thread/thread_entry.hpp | 81 |
thread_resume_suspend_status enum | src/thread/thread_entry.hpp | 139 |
thread_type enum | src/thread/thread_entry.hpp | 124 |
cubthread::entry ctor | src/thread/thread_entry.cpp | 78 |
cubthread::entry::request_lock_free_transactions | src/thread/thread_entry.cpp | 220 |
thread_suspend_wakeup_and_unlock_entry | src/thread/thread_entry.cpp | 497 |
thread_wakeup_internal | src/thread/thread_entry.cpp | 600 |
thread_suspend_with_other_mutex | src/thread/thread_entry.cpp | 688 |
cubthread::worker_pool (base) | src/thread/thread_worker_pool.hpp | 54 |
cubthread::worker_pool::core | src/thread/thread_worker_pool.hpp | 123 |
cubthread::worker_pool::core::worker | src/thread/thread_worker_pool.hpp | 178 |
cubthread::worker_pool_impl | src/thread/thread_worker_pool_impl.hpp | 105 |
worker_pool_impl::core_impl | src/thread/thread_worker_pool_impl.hpp | 263 |
core_impl::worker_impl | src/thread/thread_worker_pool_impl.hpp | 339 |
core_impl::execute_task | src/thread/thread_worker_pool_impl.hpp | 896 |
core_impl::get_task_or_become_available | src/thread/thread_worker_pool_impl.hpp | 1012 |
core_impl::execute_task_as_temp | src/thread/thread_worker_pool_impl.hpp | 1194 |
worker_impl::run | src/thread/thread_worker_pool_impl.hpp | 1387 |
worker_impl::init_run / finish_run | src/thread/thread_worker_pool_impl.hpp | 1430 / 1457 |
worker_impl::assign_task (with task) | src/thread/thread_worker_pool_impl.hpp | 1267 |
worker_impl::get_new_task | src/thread/thread_worker_pool_impl.hpp | 1521 |
worker_impl::stop_execution | src/thread/thread_worker_pool_impl.hpp | 1326 |
worker_pool_impl::stop_execution | src/thread/thread_worker_pool_impl.hpp | 594 |
worker_pool_task_capper | src/thread/thread_worker_pool_taskcap.hpp | 30 |
cubthread::daemon | src/thread/thread_daemon.hpp | 87 |
daemon::loop_with_context | src/thread/thread_daemon.cpp | 209 |
daemon::loop_without_context | src/thread/thread_daemon.cpp | 248 |
daemon::stop_execution | src/thread/thread_daemon.cpp | 90 |
cubthread::looper | src/thread/thread_looper.hpp | 81 |
looper::wait_type enum | src/thread/thread_looper.hpp | 132 |
looper::put_to_sleep | src/thread/thread_looper.cpp | 119 |
looper::setup_increasing_waits | src/thread/thread_looper.cpp | 208 |
cubthread::lockfree_hashmap | src/thread/thread_lockfree_hash_map.hpp | 35 |
lockfree_hashmap_forward_func 매크로 | src/thread/thread_lockfree_hash_map.hpp | 162 |
SYNC_CRITICAL_SECTION struct | src/thread/critical_section.h | 110 |
CSECT_* enum | src/thread/critical_section.h | 57 |
csect_Names[] | src/thread/critical_section.c | 76 |
csect_initialize_static_critical_sections | src/thread/critical_section.c | 243 |
csect_enter | src/thread/critical_section.c | 674 |
csect_enter_critical_section | src/thread/critical_section.c | 474 |
csect_enter_as_reader | src/thread/critical_section.c | 891 |
cubsync::critical_section_tracker | src/thread/critical_section_tracker.hpp | 32 |
critical_section_tracker::on_enter_as_reader | src/thread/critical_section_tracker.cpp | 60 |
critical_section_tracker::check_csect_interdependencies | src/thread/critical_section_tracker.cpp | 188 |
풀/daemon 소비자 호출 위치
섹션 제목: “풀/daemon 소비자 호출 위치”| 소비자 | 파일 | 라인 |
|---|---|---|
transaction worker pool | src/connection/server_support.c | 581 |
vacuum worker pool | src/query/vacuum.c | 1342 |
vacuum-master daemon | src/query/vacuum.c | 1352 |
parallel-query worker pool | src/query/parallel/px_worker_manager_global.cpp | 69 |
loaddb worker pool | src/loaddb/load_worker_manager.cpp | 124 |
online-index worker pool | src/storage/btree_load.c | 5315 |
backup-read worker pool | src/transaction/log_page_buffer.c | 7546 |
recovery-redo worker pool | src/transaction/log_recovery_redo_parallel.cpp | 662 |
log-checkpoint daemon | src/transaction/log_manager.c | 10415 |
log-rm-archive daemon | src/transaction/log_manager.c | 10440 |
log-clock daemon | src/transaction/log_manager.c | 10457 |
ha-delay-check daemon | src/transaction/log_manager.c | 10482 |
log-flush daemon | src/transaction/log_manager.c | 10500 |
cdc-loginfo-producer daemon | src/transaction/log_manager.c | 14046 |
deadlock-detect daemon | src/transaction/lock_manager.c | 5820 |
dwb-flush-block daemon | src/storage/double_write_buffer.cpp | 4078 |
dwb-file-sync daemon | src/storage/double_write_buffer.cpp | 4092 |
pgbuf-maintain daemon | src/storage/page_buffer.c | 16538 |
pgbuf-page-flush daemon | src/storage/page_buffer.c | 16556 |
pgbuf-page-post-flush daemon | src/storage/page_buffer.c | 16580 |
pgbuf-flush-control daemon | src/storage/page_buffer.c | 16604 |
session-control daemon | src/session/session.c | 588 |
pl-monitor daemon | src/sp/pl_sr.cpp | 266 |
소스 검증 (2026-04-30 기준)
섹션 제목: “소스 검증 (2026-04-30 기준)”이 문서는 여러 다른 문서들의 토대다. 다음과 함께 읽어야 한다.
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_manager가on_create를 override해서 붙인다). 블록별 작업 로직은 그곳 을 보라 — 본 문서에 있는 것은 그 토대다.cubrid-recovery-manager.md(parallel redo worker). recovery 의 parallel redo는log_recovery_redo_parallel.cpp에 등록된recovery-redoworker 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-detectdaemon은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.md와cubrid-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_TABLE↔CSECT_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 경로를 통째로 우회한다.loaddb와backup-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_TABLE↔CSECT_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_time은delta_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.hpp—cubthread::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.cpp—worker_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.cpp—cubthread::waiter(daemon별 mutex+condvar).src/thread/thread_task.hpp,src/thread/thread_entry_task.hpp,src/thread/thread_entry_task.cpp—task<Context>,entry_task,entry_manager와 친구들.src/thread/thread_lockfree_hash_map.hpp,src/thread/thread_lockfree_hash_map.cpp—cubthread::lockfree_hashmapfacade.src/thread/critical_section.h,src/thread/critical_section.c—SYNC_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혈통.