freeeの開発情報ポータルサイト

デバッガでRedisのコードを読んでみよう

こんにちは、エンジニアの松崎 啓治(まつざき けいじ)です。
インターネット上ではこのIDで活動しています。 @futoase

先日、社内でエンジニア向けに「デバッガでRedisのコードを読んでみよう」というテーマの勉強会が開かれました。せっかくの機会なので、その内容をご紹介します。

勉強会スライドへのリンク

デバッガでRedisのコードを追いかけるメリットとしては以下のようなものがあります。

  • gdbを使ってRedisのコードをstep実行することで、どのタイミングでRedisのStorage(memory領域)からデータを取得できるのか体験から学べる
  • Redisだけではなく、nginxやMySQL、PostgreSQLなどgdbを利用してstep実行を行えるものであれば、今回の勉強会の手法を元に同じように体験から学ぶことができる

デバッガで追いかけるための準備

プレゼン資料で取り上げられている環境はLinux (ディストリビューションはUbuntu)ですが、公開されているDocker imageを利用することで、docker for Macやdocker for Windowsなど、非Linux環境でもRedisのデバッグ体験を行うことが可能です。

Docker imageの準備

プレゼン資料を作成した浅羽により、Redisのデバッグ環境を行えるDocker imageを作成するためのDockerfileをGithubにて公開しています。

github.com

また、Docker imageについてDocker Hub上に公開しています

docker コマンドを利用し、docker pullを行いましょう。

> docker pull futoase/redis-debug-4.0

docker imageの取得が終わったら、docker runコマンドでdocker containerを立ち上げましょう。

> docker run -it -p 9876:9876 \ 
  --privileged --cap-add=SYS_PTRACE \ 
  --security-opt seccomp=unconfined \
  futoase/redis-debug-4.0:latest /bin/bash
root@f76f0abef015:/# 

docker runに渡している各種オプションは、ptrace システムコールを呼び出すために必要なものとなっています。1

Redis Serverを立ち上げる

早速、先程立ち上げたcontainer内で、Redis Serverを立ち上げましょう。

root@c2407feddaa5:/# cd /root/redis-4.0.11/src
root@f76f0abef015:~/redis-4.0.11/src# 
root@01789e2f2a4e:~/redis-4.0.11/src# ./redis-server --port 9876 --protected-mode no --daemonize no
12:C 28 Sep 07:23:21.739 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
12:C 28 Sep 07:23:21.740 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=12, just started
12:C 28 Sep 07:23:21.740 # Configuration loaded
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 4.0.11 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 9876
 |    `-._   `._    /     _.-'    |     PID: 12
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

12:M 28 Sep 07:23:21.745 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
12:M 28 Sep 07:23:21.745 # Server initialized
12:M 28 Sep 07:23:21.747 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
12:M 28 Sep 07:23:21.748 * Ready to accept connections

gdbを利用しRedis Serverに対してattachをする

Redis Serverに対してattachを行います。今回、gdbではなく、cgdb2を利用します。 今度は、docker execコマンドを利用し、先程立ち上げたdocker containerでcgdbを立ち上げます

> docker ps | grep redis-debug
f76f0abef015        futoase/redis-debug-4.0:latest   "/bin/bash"              11 minutes ago      Up 11 minutes       0.0.0.0:9876->9876/tcp               peaceful_chebyshev
> docker exec --privileged -it f76f0abef015 /bin/bash
> cgdb -p 12 # 12はredis-serverのprocess id で、先程redis-serverを起動したときにterminalで表示されたもの

cgdbの起動が終わると以下のようになります。

cgdbが起動した様子のスクリーンショット

この状態で、プレゼンにあるsetCommandにbreak pointを貼ってみましょう。

(gdb) b setCommand
Breakpoint 1 at 0x558b0346b63b: file t_string.c, line 98.
(gdb) c
Continuing. 

合わせて、redis-clientを起動します。起動する対象は、docker containerを立ち上げているホストマシン、 例えばあなたがMacBookを利用して立ち上げているなら、そのMacBookのターミナル上で立ち上げます。

ターミナルにコマンドを打ち込み、docker container上のredis-serverに接続しましょう。

> redis-cli -p 9876
127.0.0.1:9876>

合わせて、SETコマンド 3を redis-clientから発行してみます。

127.0.0.1:9876> SET hoge 1234

この時、cgdb側で特定のbreakpointで処理が止まっている状態になります。

スクリーンショット:breakpointで処理が止まり、画面上部にはブレークポイント周辺のコードが表示されている

この時、next コマンドや、step コマンドを実行することで、next実行(関数レベル)、step実行(関数実行をネストして見る)ことが可能になります。

スクリーンショット:nextやstepコマンドを実行している様子のgifアニメ

cgdbを利用し、関数の実行処理を追いかけることで、ソースコードリーディングに対し C言語未経験者でも読みといていくことが可能となります。

p コマンドを利用することで、ランタイムで評価中の変数の値について確認することもできます。

スクリーンショット:pコマンドでretval変数の値を確認している

ミドルウェアのソースコードを読む体験

MySQL、nginx及び今回のRedisなど、ミドルウェアのソースコードをgdbを利用し、 読む体験を行うことでどの処理にボトルネックがあるのか、どのタイミングでwhile loopに割り込みが入るのか など順を追っていくことができるようになります。このような体験を得るために、今回のように環境そのものをDocker image化し、 ソフトウェアインストールなどの設定をしなくても済むようにすることで、気軽に体験できる点がよかったです。