故障分析 | 租戶 memstore 記憶體滿問題排查

愛可生雲資料庫發表於2023-04-30

作者:操盛春

技術專家,任職於愛可生,專注研究 MySQL、OceanBase 原始碼。

本文來源:原創投稿

*愛可生開源社群出品,原創內容未經授權不得隨意使用,轉載請聯絡小編並註明來源。


本文是對官方同名檔案部分內容的解釋,官方檔案連結:
https://www.oceanbase.com/docs/enterprise-oceanbase-database-...

一、檢查凍結情況

1.1 凍結功能是否正常?

以 tenant_id = 1001 租戶為例,查詢 __all_virtual_tenant_memstore_info 表:

-- 注意:where 條件中 tenant_id 需要修改成實際場景對應的值
select * from __all_virtual_tenant_memstore_info where tenant_id = 1001\G

*************************** 1. row ***************************
           tenant_id: 1001
              svr_ip: 10.186.17.106
            svr_port: 22882
active_memstore_used: 1295451600
 total_memstore_used: 1298137088
major_freeze_trigger: 8589934550
      memstore_limit: 17179869150
          freeze_cnt: 0

active_memstore_used 表示活躍狀態的 MemTable 佔用的記憶體,major_freeze_trigger 表示 memstore 佔用記憶體達到 major_freeze_trigger 之後會觸發轉儲(或合併)。

如果凍結功能正常,租戶 memstore 佔用記憶體到達 major_freeze_trigger 之後,就會先凍結、然後轉儲該租戶下的 MemTable,轉儲完成的 MemTable 佔用的記憶體會從 active_memstore_used 中減去。

凍結是轉儲或合併的前置操作,所以,先根據 active_memstore_used 和 major_freeze_trigger 的大小關係,判斷凍結功能是否正常:

  • active_memstore_used <= major_freeze_trigger,說明凍結功能正常,需要檢查轉儲情況,參照 2. 檢查轉儲情況小節。
  • active_memstore_used > major_freeze_trigger,說明凍結功能不正常,需要檢視凍結執行緒的情況,參照 1.2 凍結執行緒是否正常工作?小節。

1.2 凍結執行緒是否正常工作?

可以執行以下命令判斷負責凍結功能的執行緒是否在正常執行:

[admin@hostname log]$ grep "tenant manager timer task" observer.log

## 如果執行緒正常執行,會每 2s 輸出一次:====== tenant manager timer task ======
[2023-02-16 08:13:47.952516] INFO  [COMMON] ob_tenant_mgr.cpp:207 [21537][2][Y0-0000000000000000] [lt=10] [dc=0] ====== tenant manager timer task ======
[2023-02-16 08:13:49.953761] INFO  [COMMON] ob_tenant_mgr.cpp:207 [21537][2][Y0-0000000000000000] [lt=9] [dc=0] ====== tenant manager timer task ======
[2023-02-16 08:13:51.955373] INFO  [COMMON] ob_tenant_mgr.cpp:207 [21537][2][Y0-0000000000000000] [lt=52] [dc=0] ====== tenant manager timer task ======
[2023-02-16 08:13:54.022403] INFO  [COMMON] ob_tenant_mgr.cpp:207 [21537][2][Y0-0000000000000000] [lt=21] [dc=0] ====== tenant manager timer task ======
[2023-02-16 08:13:56.023441] INFO  [COMMON] ob_tenant_mgr.cpp:207 [21537][2][Y0-0000000000000000] [lt=8] [dc=0] ====== tenant manager timer task ======
[2023-02-16 08:13:58.024985] INFO  [COMMON] ob_tenant_mgr.cpp:207 [21537][2][Y0-0000000000000000] [lt=9] [dc=0] ====== tenant manager timer task ======
...

如果日誌中能夠每 2s 正常輸出資訊:====== tenant manager timer task ======,說明負責凍結功能的執行緒正常執行,那就意味著是某些 MemTable 無法凍結,導致 memstore 佔用記憶體超過 major_freeze_trigger。

這種情況下,需要檢視有哪些沒有凍結的 MemTable:

-- 注意:where 條件中 svr_ip、tenant_id 需要修改成實際場景對應的值
select a.table_name, b.table_id, b.partition_id, b.mt_base_version, b.mt_is_frozen, b.mt_protection_clock, b.mt_snapshot_version
from gv$table as a inner join __all_virtual_tenant_memstore_allocator_info as b
on a.table_id = b.table_id
where b.mt_is_frozen = 0 and b.svr_ip='10.186.17.106' and b.tenant_id = 1001
order by mt_protection_clock;

+------------+------------------+--------------+------------------+--------------+---------------------+---------------------+
| table_name | table_id         | partition_id | mt_base_version  | mt_is_frozen | mt_protection_clock | mt_snapshot_version |
+------------+------------------+--------------+------------------+--------------+---------------------+---------------------+
| t1         | 1100611139453777 |            0 | 1678500259180548 |            0 |                   0 | 9223372036854775807 |
| t4         | 1100611139453785 |            0 | 1678500010823930 |            0 |                   0 | 9223372036854775807 |
| t3         | 1100611139453781 |            0 | 1678945615601759 |            0 |           165599800 | 9223372036854775807 |
| t5         | 1100611139453787 |            0 | 1678500010823930 |            0 |           276698400 | 9223372036854775807 |
| t2         | 1100611139453778 |            0 | 1678500258966417 |            0 |           404566600 | 9223372036854775807 |
| t6         | 1100611139453789 |            0 | 1678945429338788 |            0 | 9223372036854775807 | 9223372036854775807 |
+------------+------------------+--------------+------------------+--------------+---------------------+---------------------+

mt_protection_clock 表示某個 MemTable 建立、轉儲、合併之後,進行增、刪、改等操作第一次分配記憶體時,該 MemTable 所屬租戶 memstore 已佔用記憶體。

按 mt_protection_clock 排序似乎沒有什麼特殊意義,可能只是為了方便檢視而已。

mt_protection_clock = 9223372036854775807,是個特殊值。

某個 MemTable 轉儲或合併之後,它的 mt_protection_clock 會修改為 9223372036854775807,然後一直保持不變,直到轉儲或合併之後第一次分配記憶體,mt_protection_clock 才會發生變化。

如果轉儲或合併之後,MemTable 沒有再分配過記憶體,mt_protection_clock 會一直保持為 9223372036854775807,重啟 OB 之後也還是 9223372036854775807,直到接下來第一次分配記憶體,mt_protection_clock 的值才會發生變化。

因為租戶 memstore 佔用記憶體達到 freeze_trigger_percentage 對應的記憶體上限之後,會觸發租戶級別的轉儲,也就是該租戶下的所有 MemTable 都會進行轉儲。基於這個前提,上面 SQL 語句查詢出來的 mt_is_frozen 等於 0,並且 mt_protection_clock 不等於 9223372036854775807 的 MemTable 就有可能是凍結異常的表,需要逐個排查確認是否凍結異常。

為什麼是有可能凍結異常的表?

因為有可能轉儲或合併之後,某些 MemTable 表又發生了 DML 操作,插入了新的資料,這種情況下,mt_is_frozen = 0、mt_protection_clock != 9223372036854775807 就是正常的了。

排除這種情況之後,剩下的 MemTable 就是凍結異常的表。

經過上面的一系列操作之後,如果找到了凍結異常的表,可以透過 table_id 查詢對應的錯誤日誌,以 table_id = 1100611139453778 為例:

## 進入 observer.log.wf 日誌檔案所在的目錄
## 如果 OB 的 enable_syslog_wf = false,需要把 observer.log.wf 替換為 observer.log
grep 1100611139453778 observer.log.wf | grep -E "WARN|ERROR"

## 如果上面的命令沒有找到錯誤日誌,也可試試以下命令
grep "fail to do minor freeze" observer.log.wf | grep 1100611139453778

二、檢查轉儲情況

2.1 轉儲功能是否正常?

以 tenant_id = 1001 租戶為例,查詢 __all_virtual_tenant_memstore_info 表:

檢查轉儲情況作為檢查凍結情況的下一個步驟,只有當凍結情況正常時,才要檢查轉儲情況。這裡所舉例子和 1. 檢查凍結情況中的例子是同一個,為了方便檢視,就複製過來了。
-- 注意:where 條件中 tenant_id 需要修改成實際場景對應的值
select * from __all_virtual_tenant_memstore_info where tenant_id = 1001\G

*************************** 1. row ***************************
           tenant_id: 1001
              svr_ip: 10.186.17.106
            svr_port: 22882
active_memstore_used: 1295451600
 total_memstore_used: 1298137088
major_freeze_trigger: 8589934550
      memstore_limit: 17179869150
          freeze_cnt: 0

1.檢查凍結情況小節介紹過,active_memstore_used <= major_freeze_trigger,說明凍結功能正常。

在此基礎上,再判斷 total_memstore_used 和 major_freeze_trigger 的關係:

  • 如果 total_memstore_used <= major_freeze_trigger,說明轉儲功能正常,那就說明一切正常,不需要排查了。
  • 如果 total_memstore_used > major_freeze_trigger,說明轉儲功能不正常,參照 2.2 及以後小節的內容。

2.2 是否存在活躍事務?

如果是 OB 2.2.x 版本,可以透過以下 SQL 查詢已凍結但未釋放記憶體的 MemTable,是否因為存在活躍事務,導致轉儲排程異常,記憶體無法釋放。

-- 注意:where 條件中 svr_ip、tenant_id 需要修改成實際場景對應的值
-- table_type = 0 表示 MEMTABLE
-- is_active = 0 表示 MEMTABLE 處於凍結狀態,還未轉儲結束
-- 關於 is_active,參照官方檔案:https://www.oceanbase.com/docs/enterprise-oceanbase-database-cn-10000000000364739
select * from __all_virtual_table_mgr as a
where a.table_type = 0 and a.is_active = 0 and a.trx_count > 0 and (a.table_id, a.partition_id) in (
  select table_id, partition_id from __all_virtual_tenant_memstore_allocator_info
  where svr_ip='10.186.17.106' and tenant_id=1001 and mt_is_frozen=1
)

如果上面 SQL 查詢到了 MemTable,說明這些查出來的表上因為存在活躍事務,導致轉儲排程異常。

可以透過 __all_virtual_trans_stat 表,檢視 MemTable 表的事務資訊,以確定事務長時間處於活躍狀態的原因。比如:大事務。

如果從 __all_virtual_trans_stat 表中沒有得到有效資訊,可以再從日誌檔案中檢視上面 MemTable 相關的事務日誌,需要根據哪些關鍵詞過濾事務日誌,官方檔案沒有寫,後續再補充吧(已列入遺留問題列表)。

2.3 從副本 clog 回放進度慢?

檢視已凍結的 MemTable,是否因為 MemTable 的弱一致性讀時間戳小於快照點(snapshot_version),導致 MemTable 轉儲排程異常,記憶體無法釋放。

關於弱一致性讀時間戳,參照官方檔案:弱一致性讀。(https://www.oceanbase.com/docs/enterprise-oceanbase-database-...)

為什麼 MemTable 的弱一致性讀時間戳小於快照點(snapshot_version)會導致該 MemTable 轉儲排程異常,我還沒有弄清楚,諮詢了官方還沒有答覆,後面搞清楚了再補充(已列入遺留問題列表)。

-- 注意:where 條件中 svr_ip、tenant_id 需要修改成實際場景對應的值
-- table_type = 0 表示 MEMTABLE
-- is_active = 0 表示 MEMTABLE 處於凍結狀態,還未轉儲結束
-- 關於 is_active,參照官方檔案:https://www.oceanbase.com/docs/enterprise-oceanbase-database-cn-10000000000364739
select 
  a.svr_ip, a.table_id, a.partition_id, a.is_active, a.table_type, a.snapshot_version,
  b.min_trans_service_ts, b.min_replay_engine_ts, b.min_log_service_ts
from __all_virtual_table_mgr as a inner join __all_virtual_partition_info as b
on a.table_id = b.table_id and a.partition_id = b.partition_idx and a.svr_ip = b.svr_ip
where a.table_type = 0 
and a.is_active = 0 
and a.snapshot_version > least(least(b.min_trans_service_ts, b.min_replay_engine_ts), b.min_log_service_ts)
and (a.table_id, a.partition_id) in (
  select table_id, partition_id from __all_virtual_tenant_memstore_allocator_info
  where svr_ip='10.186.17.106' and tenant_id=1001 and mt_is_frozen=1
);
least(least(min_trans_service_ts, min_replay_engine_ts), min_log_service_ts) 表示取 min_trans_service_ts, min_replay_engine_ts, min_log_service_ts 3 個欄位中的最小值。

如果上面 SQL 查詢到了 MemTable,說明這些表的弱一致性讀時間戳小於快照點(snapshot_version),接下來檢視是否因為是否因為 clog 日誌回放速度慢導致弱一致性讀時間戳落後比較多。

-- 用上面的 SQL(select 子句中只保留了 table_id、partition_id 欄位)作為 in 條件的子查詢
select * from __all_virtual_partition_replay_status
where (table_id, partition_idx) in (
  select a.table_id, a.partition_id
  from __all_virtual_table_mgr as a inner join __all_virtual_partition_info as b
  on a.table_id = b.table_id and a.partition_id = b.partition_idx and a.svr_ip = b.svr_ip
  where a.table_type = 0 
  and a.is_active = 0 
  and a.snapshot_version > least(least(b.min_trans_service_ts, b.min_replay_engine_ts), b.min_log_service_ts)
  and (a.table_id, a.partition_id) in (
    select table_id, partition_id from __all_virtual_tenant_memstore_allocator_info
    where svr_ip='10.186.17.106' and tenant_id=1001 and mt_is_frozen=1
  )
);

2.4 確認轉儲是否成功?

如果 2.2、2.3 小節都沒有查詢到轉儲排程異常的 MemTable,接下來根據已凍結但未釋放記憶體的 MemTable 的 pkey(table_id + partition_id) 到 observer.log 日誌檔案中檢視轉儲過程的日誌,以確認轉儲是否成功。

  • 先查出來已凍結但未釋放記憶體的 MemTable 的 pkey:
select table_id, partition_id from __all_virtual_tenant_memstore_allocator_info
where svr_ip='10.186.17.106' and tenant_id=1001 and mt_is_frozen=1
  • 根據 pkey(這裡實際上只用了 table_id) 到 observer.log 日誌檔案中檢視轉儲過程的日誌:
## 1100611139453778 是 table_id,需要替換成實際使用是的 table_id
grep "add dag success.*1100611139453778" observer.log
grep "task start process.*1100611139453778" observer.log
grep "task finish process.*1100611139453778" observer.log
grep "dag finish.*1100611139453778" observer.log

說明:官方檔案中這個步驟是根據 pkey 到 observer.log 中查詢是否有相應的轉儲日誌,但是實際上根本是查到不的,因為租戶 memstore 佔用記憶體達到 freeze_trigger_percentage 對應的記憶體上限時,是以租戶為維度進行轉儲的,關於轉儲過程的日誌,記錄的是租戶 ID,如下:

[403269][938][Y0-0000000000000000] [lt=28] [dc=0] add dag success(dag=0x7fffd6d0b5d0, start_time=1679040139626760, id=Y0-0000000000000000, *dag={this:0x7fffd6d0b5d0, type:7, name:"DAG_MAJOR_FINISH", id:Y0-0000000000000000, dag_ret:0, dag_status:1, start_time:1679040139626760, tenant_id:1}, dag->hash()=0, dag_cnt=1, dag_type_cnts=1)
[242340][416][Y59620ABA116A-0005F70FC566F959] [lt=40] [dc=0] task finish process(ret=0, start_time=1679023923679973, end_time=1679023923680479, runtime=506, *this={type:30, status:2, dag_:{this:0x7fffd6e7e800, type:7, name:"DAG_MAJOR_FINISH", id:Y59620ABA116A-0005F70FC566F959, dag_ret:0, dag_status:2, start_time:1679023923678832, tenant_id:1}})
[403017][456][Y59620ABA116A-0005F7134FC908DD] [lt=21] [dc=0] dag finished(*dag={this:0x7fffd6d0c020, type:7, name:"DAG_MAJOR_FINISH", id:Y59620ABA116A-0005F7134FC908DD, dag_ret:0, dag_status:3, start_time:1679040379627887, tenant_id:1}, runtime=1995, dag_cnt=0, dag_cnts_[dag->get_type()]=0)

所以,這一步實際上可以先跳過,暫且認為轉儲排程沒有異常,就說明會轉儲成功。

2.5 MemTable 引用計數是否正常?

如果確認了轉儲排程正常,轉儲過程也正常,但是已凍結的 MemTable 記憶體卻沒有釋放,那再確認下是否因為 MemTable 的引用計數異常,導致記憶體無法釋放。

-- 注意:where 條件中 svr_ip、tenant_id 需要修改成實際場景對應的值
-- table_type = 0 表示 MEMTABLE
-- is_active = 0 表示 MEMTABLE 處於凍結狀態,還未轉儲結束
-- 關於 is_active,參照官方檔案:https://www.oceanbase.com/docs/enterprise-oceanbase-database-cn-10000000000364739
select * from __all_virtual_table_mgr as a
where a.table_type = 0 and a.is_active = 0 and a.write_ref > 0 and (a.table_id, a.partition_id) in (
  select table_id, partition_id from __all_virtual_tenant_memstore_allocator_info
  where svr_ip='10.186.17.106' and tenant_id=1001 and mt_is_frozen=1
);

正常情況下,轉儲過程完成之後,MemTable 的引用計數(write_ref)應該變為 0。

如果上面 SQL 查詢到了 MemTable,說明已完成凍結、轉儲過程的 MemTable 中,還存在引用計數大於 0 的 MemTable,那就說明這些 MemTable 的引用計數異常,導致記憶體無法釋放。

三、遺留問題

  • 透過哪些關鍵字到 observer.log 檔案中檢視某個表的事務日誌?
  • 為什麼 MemTable 的弱一致性讀時間戳小於快照點(snapshot_version)會導致該 MemTable 轉儲排程異常?
  • OB 自動觸發轉儲是按租戶維度進行的,observer.log 中怎麼查詢單個表的轉儲過程日誌?

相關文章