メモリリークの動的診断
GV$OB_MEMORYビューは、OBServerノード全体でObMallocによって割り当てられたメモリの状況を表示します。各メモリブロックには、labelまたはmod_idと呼ばれるラベルが付けられており、modの統計情報を確認することで、システムメモリの使用状況を分析し、メモリリークが疑われるモジュールを初期段階で特定することができます。
メモリリーク問題の調査を容易にするため、OceanBaseデータベースではメモリリークの動的診断メカニズムが実装されています。その仕組みとしては、追跡モジュールがメモリを割り当てるたびに、申請したメモリのアドレスと対応するコールスタックを記録し、割り当てられたメモリが解放されると、その記録をクリアします。このように、通常のメモリの申請や解放に伴うコールスタックはすぐにクリアされますが、メモリリークが発生した場合は、そのコールスタックの記録が残り続けます。最終的に、コールスタックごとにグループ化し、各異なるコールスタックに現在存在する申請記録を累積して、__all_virtual_mem_leak_checker_infoに表示します。言い換えれば、累積回数が多いコールスタックはメモリリークが発生している可能性が高いです(もちろん、さまざまなキャッシュによるものである可能性もありますが、これは具体的な問題に応じて個別に分析する必要があります)。
memory_leakを開く
obclient> alter system set leak_mod_to_check='OB_COMMON_ARRAY';
このように、トレースモジュールをOB_COMMON_ARRAYと指定し、設定が完了したら__all_virtual_mem_leak_checker_infoテーブルの情報を監視し始めることができます。これにより、問題が発生する可能性のあるコールスタックを確認できます。
クエリ
obclient> select * from __all_virtual_mem_leak_checker_info order by alloc_count desc;
一般的に、漏洩したコールスタックのカウントは非常に大きく(しかもますます増加しています)、このようなコールスタックをaddr2lineを使用して出力します。
obclient> addr2line -pCfe bin/observer 0xe77df5 0xedf5a4 0xee14b0 0xee0923 1 0xeea558 0x4a05c3 0x1485cd9 0x1485223 0x1483c4b 0x1526f2a 0x1401359 0x1403075 0x140325a 0x1406416 0x14a51bb 0x14a5130 0x140x48f3aa7db8 0x14a4cc8 0x14a50f4 0x14a5cb1 0x130166e 0xd08bc9 0xd069d2 0xd01e97 0xeff033 0xefe6
$oceanbase_root/src/lib/utility/utility.cpp:58
$oceanbase_root/src/lib/../../src/lib/allocator/ob_mem_leak_checker.h:125
$oceanbase_root/src/lib/allocator/ob_tc_malloc.cpp:399
$oceanbase_root/src/lib/allocator/ob_tc_malloc.cpp:218
$oceanbase_root/src/observer/../../src/lib/allocator/ob_malloc.h:38
$oceanbase_root/src/lib/allocator/ob_malloc.cpp:121
$oceanbase_root/src/lib/../../src/lib/allocator/ob_malloc.h:116
$oceanbase_root/src/sql/../../src/lib/container/ob_array.h:295
$oceanbase_root/src/sql/../../src/lib/container/ob_array.h:291
$oceanbase_root/src/sql/../../src/lib/container/ob_array.h:468
$oceanbase_root/src/sql/optimizer/ob_log_table_scan.cpp:85
$oceanbase_root/src/sql/optimizer/ob_log_plan.cpp:1183
$oceanbase_root/src/sql/optimizer/ob_log_plan.cpp:1484
$oceanbase_root/src/sql/optimizer/ob_log_plan.cpp:1557
$oceanbase_root/src/sql/optimizer/ob_log_plan.cpp:2092
$oceanbase_root/src/sql/optimizer/ob_select_log_plan.cpp:467
$oceanbase_root/src/sql/optimizer/ob_select_log_plan.cpp:455
$oceanbase_root/src/sql/optimizer/ob_select_log_plan.cpp:890
$oceanbase_root/src/sql/optimizer/ob_select_log_plan.cpp:378
$oceanbase_root/src/sql/optimizer/ob_select_log_plan.cpp:450
$oceanbase_root/src/sql/optimizer/ob_select_log_plan.cpp:568
$oceanbase_root/src/sql/optimizer/ob_optimizer.cpp:23
$oceanbase_root/src/sql/ob_sql.cpp:1160
$oceanbase_root/src/sql/ob_sql.cpp:887
$oceanbase_root/src/sql/ob_sql.cpp:152
$oceanbase_root/src/observer/mysql/obmp_query.cpp:263
memory_leakを無効にする
チェッカーを有効にすると、実行パフォーマンスが著しく低下します。問題の特定後は、速やかにチェッカーを無効にしてください。
obclient> alter system set leak_mod_to_check='';
memory_contextによるメモリ動的リークチェック
長いライフサイクルを持つメモリ管理に対しては、memory_leakが優れた診断機能を提供し、呼び出しスタックでの問題特定を容易にします。しかし、短いライフサイクルを持つモデルでは、メモリは常にすぐに解放されるため、memory_leakはしばしば無力となります。この問題を解決するためには、新しい機能が必要です。
memory_contextは、OceanBaseデータベースがライフサイクルに基づいてメモリを管理するためのインターフェースです。memory_context自体がメモリのライフサイクルを表しているため、memory_contextの機能を拡張して、短いライフサイクルのメモリリークの診断を実現できます。注意すべき点として、memory_contextはリークが発生した場合でも、メモリが完全に解放されることを保証します。したがって、正確に言えば、「短いライフサイクルのメモリの動的リーク」をサポートしているということです。
static_idの特定
ログに「HAS UNFREE」というメッセージが出力されたら、そのログ内のstatic_idを見つけます。
[2020-10-22 15:30:57.923806] ERROR has_unfree_callback (object_set.cpp:27) [67779][462][Y40DC64589025-0005B23D7165EB9F] [lt=24] [dc=0] HAS UNFREE PTR!!!label: mytest1,static_id: 12,static_info: {filename_:"obmp_query.cpp", line_:475, function_:"process_single_stmt"}, dynamic_info: {tid_:67779, cid_:462, create_time_:1603351857840041}
memory_leakの開始
obclient> alter system set leak_mod_to_check='mytest1@12';
これは、static_idが12のmemory_contextの下でmytest1のメモリ申請をトレースすることを意味します。もちろんワイルドカードもサポートされていますが、本番環境ではパフォーマンスとスペースコストの観点から推奨されません。可能な限り範囲を絞ることが合理的です。
obclient> alter system set leak_mod_to_check='*@12';
この書き方は、static_idが12のmemory_contextの下でのすべてのメモリ申請をトレースすることを意味します。
再現を待機
次回「HAS UNFREE」が出力されたとき、呼び出しスタックが表示されます。
[2020-10-22 15:41:02.530881] INFO object_set.cpp:523 [67784][472][Y40DC64589025-0005B23D7135EBC2] [lt=15] [dc=0] CONTEXT MEMORY LEAK. ptr: 0x7f8f8a4145b0, size: 200, label: mytest1, lbt: 0xb979d8b 0xb72d239 0xb71ad7a 0x3a88b97 0x7fd735c 0x7fcee89 0x7fcd41d 0xbac5fad 0x8044c7a 0x80443b5 0x804001a 0x803f2ff 0x32e2cd7 0x32e2b7c 0x2d49d6d 0xb76f163 0xb76cd74 0xb76bdae
同様に、addr2lineを使用してさらに分析を続けることもできます。
memory_leakの停止
チェッカーを有効にするとパフォーマンスが著しく低下するため、問題の特定後は速やかにチェッカーを無効にします。
obclient> alter system set leak_mod_to_check='';
メモリメタデータのダンプ
すべてのメモリのメタデータをディスクに書き出すことができます。
操作手順
OceanBaseデータベースのデプロイメントディレクトリを開きます。
etc/dump.configファイルを新規作成し、ターゲットコマンド(具体的なコマンドは以下を参照)を記述します。kill -62 $pidを実行し、log/memory_metaファイルを確認します。
コマンドの概要
すべてのメモリ情報を出力する
dump chunk all指定されたテナント+ctxのメモリ情報を出力する
dump chunk tenant_id=$tenant_id,$ctx_id=ctx_id(純粋な数字であることに注意してください)
glibcインターフェースが申請したメモリの確認
mallocなどのglibcによるメモリ申請は、OceanBaseデータベースのメモリ管理インターフェースに透過的に送信されます。mod_idはglibc_mallocであり、以下の方法で確認できます。
obclient> select *from GV$OB_MEMORY where mod_name = 'glibc_malloc' and hold > 0;
+-----------+----------------+----------+--------+--------------+----------+----------+--------+--------------+------+-----------+-----------+-------+-------------+------------+
| tenant_id | svr_ip | svr_port | ctx_id | label | ctx_name | mod_type | mod_id | mod_name | zone | hold | used | count | alloc_count | free_count |
+-----------+----------------+----------+--------+--------------+----------+----------+--------+--------------+------+-----------+-----------+-------+-------------+------------+
| 500 | xx.xx.xx.xx | 46824 | 26 | glibc_malloc | GLIBC | user | 0 | glibc_malloc | z1 | 249800208 | 219156754 | 41247 | 0 | 0 |
+-----------+----------------+----------+--------+--------------+----------+----------+--------+--------------+------+-----------+-----------+-------+-------------+------------+