본문 바로가기

Database/MongoDB

MongoDB Debug Mode로 빌드하기

MongoDB를 사용하고 있는가. 그렇다면 MongoDB에 대해서 얼마나 알고 있는가. 
MongoDB를 알 수 있는 가장 좋은 방법은 결국 직접 실행해보는 것이다. 
이번에는 MongoDB를 Debug Mode로 빌드하고 gdb를 사용해서 디버깅해보는 방법을 알아보자. 

Overview

gdb를 사용해서 MongoDB를 디버그 모드로 실행해볼 수 있다. 

Debug Mode로 빌드하기 

Prerequisite

Server

MongoDB를 빌드, 실행할 수 있는 서버가 필요하다. 

각자 알아서 서버를 잘 준비해보도록하자. 스토리지는 100GB 정도면 적절하다. ( 나는 그랬다. ) 
mongod를 빌드하기 위해서 필요한 최소한의 용량은 13GB라고 하지만, 실제로 수행했을 때에는 약 34GB 정도의 공간을 차지하는 것을 볼 수 있었다. ( 빌드에 필요한 도구 등을 전부 포함한 스토리지 ) 

OS는 Rocky 8.10 버전을 사용한다. 

Dev tools 

MongoDB를 빌드하기 위한 도구들을 다운로드 해야한다. 

MongoDB 6.0 기준으로 진행한다. MongoDB 6.0을 기준으로 빌드하는 방법은 다음 문서에서도 확인할 수 있다. 

https://github.com/mongodb/mongo/blob/v6.0/docs/building.md

 

mongo/docs/building.md at v6.0 · mongodb/mongo

The MongoDB Database. Contribute to mongodb/mongo development by creating an account on GitHub.

github.com

 

MongoDB 버전에 따라서 최소로 필요한 조건들이 달라지는데, 리눅스 환경에서 MongoDB를 빌드하기 위해서 주로 필요한 것들은 다음과 같다. 

Program Version
GCC 8.2 or newer
libcurl-devel -
Python 3.7.x and Pip modules

 

다음 명령을 통해서 시원~하게 설치할 수 있다.

yum install gcc-toolset-10
dnf install libcurl-devel
dnf install python39

 

Python을 다운로드 받은 뒤에는 python, python3 명령어의 기본 바이너리 환경을 변경해주어야한다. 

다운로드 받은 Python3.9를 사용하도록 변경해주자. 

update-alternatives --config python
 
# check version
python3 --version

Build MongoDB with debug symbol

MongoDB를 debug symbol을 포함하도록 빌드해야한다. 

Install source code 

소스코드를 다운로드 받자. 

https://github.com/mongodb/mongo 에서 코드를 다운로드 받는다.

git clone https://github.com/mongodb/mongo

 

6.0 버전을 기준으로 빌드하기 위해서 branch를 checkout한다. 

git branch -a | grep 6.0
git checkout remotes/origin/v6.0

 

Build MongoDB

빌드는 기본적으로 https://github.com/mongodb/mongo/blob/v6.0/docs/building.md 문서를 따라가면 되는데, 단지 Scons을 통해 빌드할 때 디버그 심볼들을 포함하도록 --dbg=on 플래그를 추가해준다.

# install requirements
python3 -m pip install -r etc/pip/compile-requirements.txt
 
# build with debug symbol
python3 buildscripts/scons.py install-mongod --dbg=on

 

빌드는 굉장히 시간이 오래 걸린다. 12Core 서버에서 약 1시간 정도 걸린 듯 하다. 

빌드가 완료되면 다음과 같이 mongod가 생성되어있는 것을 볼 수 있다. 

[bin]$ pwd
/xxx/mongo/build/install/bin
[bin]$ ls -al
total 2546968
drwxrwxr-x 2 xxx xxx         38 Dec 21 23:17 .
drwxrwxr-x 3 xxx xxx        100 Dec 21 23:13 ..
-rwxrwxr-x 2 xxx xxx 2608090960 Dec 21 23:17 mongod
-rwxrw-r-- 2 xxx xxx       1782 Dec 21 23:13 resmoke.py

Execute mongod

기본적으로 mongod를 실행시킬 수 있는 환경들을 전부 갖춰준다. 
그리고 mongosh로 접속할 수 있도록 mongosh도 다운로드를 받아준다. 
이후 mongod 바이너리를 실행시켜준 뒤 replica set initiate, 계정 생성등을 진행한다.

 

실행하는 환경에 따라서 다르므로, 알잘딱 설정한 뒤 mongod를 실행해준다. 

Debug with gdb 

gdb

What is gdb? 

https://sourceware.org/gdb/

Chatgpt의 힘을 빌려보자. 

gdbGNU Debugger의 약자로, C, C++, Fortran 등 다양한 언어로 작성된 프로그램을 디버깅하기 위한 도구입니다. gdb는 개발자가 프로그램 실행 중 발생하는 문제를 분석하고 해결하는 데 매우 유용합니다.

 

암튼 c, c++ 로 개발된 프로그램을 디버깅하기 위한 도구라는 것 같다. 

Install gdb

보통 RHEL의 GCC Toolset을 다운로드 받으면 gdb가 포함되어 다운로드된다. 
https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/developing_c_and_cpp_applications_in_rhel_8/additional-toolsets-for-development_developing-applications#specifics-of-binutils-in-gcc-toolset-9_gcc-toolset-9

sudo yum install gcc-toolset-10

Execute gdb

gdb의 기본적인 사용법은 아직 잘 모르지만, mongod에 붙어 디버깅할 수 있는 방법을 소개한다. 

우선 debug symbol 과 함께 빌드된 mongod 프로세스를 실행중인지 확인하고 이를 gdb로 debug mode로 붙는다.

ps -ef | grep mongod
gdb -p `pidof mongod`

 

gdb에서 mongod를 전부 확인하는데 시간이 조금 걸리는 것 같다. 
다음 명령어가 뜨면서 console이 뜨면 성공이다.

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00007f85ec89e47c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
Missing separate debuginfos, use: yum debuginfo-install brotli-1.0.6-3.el8.x86_64 cyrus-sasl-lib-2.1.27-6.el8_5.x86_64 glibc-2.28-251.el8_10.5.x86_64 keyutils-libs-1.5.10-9.el8.x86_64 krb5-nbpj-libs-1.17-120.el8.x86_64 libcom_err-1.45.6-5.el8.x86_64 libcurl-7.61.1-34.el8_10.2.x86_64 libgcc-8.5.0-22.el8_10.x86_64 libidn2-2.2.0-1.el8.x86_64 libnghttp2-1.33.0-5.el8_8.x86_64 libpsl-0.20.2-6.el8.x86_64 libselinux-2.9-8.el8.x86_64 libssh-0.9.6-14.el8.x86_64 libstdc++-8.5.0-22.el8_10.x86_64 libunistring-0.9.9-3.el8.x86_64 libxcrypt-4.1.1-6.el8.x86_64 openldap-2.4.46-18.el8.x86_64 openssl-libs-1.1.1k-14.el8_10.x86_64 pcre2-10.32-3.el8_6.x86_64 xz-libs-5.2.4-4.el8_6.x86_64
(gdb)

 

다음은 breakpoint를 생성한다. 
breakpoint를 생성하고 mongod 실행 중 해당 함수로 들어오게 된다면, hit 이라는 이벤트와 함께 breakpoint에서부터 순차적으로 함수 혹은 코드를 실행시켜볼 수 있다. 
이번에는 예시이기 때문에 Watchdog Thread의 DirectoryCheck::run 함수에 breakpoint를 걸어보자.

DirectoryCheck::run 함수는 다음 코드에서 확인할 수 있다. 
https://github.com/mongodb/mongo/blob/dc676b71192abba530fb7087f637a7d87bb18f89/src/mongo/watchdog/watchdog.cpp#L660-L684

(gdb) b DirectoryCheck::run
Breakpoint 1 at 0x55ad0f796890: file src/mongo/watchdog/watchdog.cpp, line 660.

 

breakpoint를 생성한 뒤에는 info break 명령어를 통해서 breakpoint가 잘 생성되었는지 확인할 수 있다.

(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000055ad0f796890 in mongo::DirectoryCheck::run(mongo::OperationContext*) at src/mongo/watchdog/watchdog.cpp:660

 

breakpoint가 잘 생성되었다면 process를 실행시켜준다. 
gdb가 attach 되면 기본적으로 process가 interrupt가 된 상태가 된다. 즉, mongod가 기동하고 있지 않다. 
mongod를 재기동 시켜주기 위해서 continue 명령어를 수행해준다.

(gdb) continue

 

Watchdog Thread는 60초에 1번씩 실행이 되므로, 60초 안에 hit가 발생한다.

[New Thread 0x7f85bcf0b700 (LWP 119472)]
[Switching to Thread 0x7f85d8ef4700 (LWP 117890)]
 
Thread 20 "watchdogCheck" hit Breakpoint 1, mongo::DirectoryCheck::run (this=0x7f85e300c2c0, opCtx=0x7f85df396460) at src/mongo/watchdog/watchdog.cpp:660
660     void DirectoryCheck::run(OperationContext* opCtx) {

 

이때부터 디버깅을 할 수 있도록 콘솔이 활성화된다. 
where 명령을 통해서 함수의 callstack을 확인할 수 있다.

(gdb) where
#0  mongo::DirectoryCheck::run (this=0x7f85e300c2c0, opCtx=0x7f85df396460) at src/mongo/watchdog/watchdog.cpp:660
...
...

 

next 명령을 수행하면 실제 코드를 한줄씩 실행시켜볼 수 있다.

(gdb) next
662         boost::filesystem::path file = _directory;

 

이런 식으로 debug mode로 실행한 뒤 mognod를 실행시켜서 동작을 확인할 수 있다.