作者:操盛春
技術專家,任職於愛可生,專注研究 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 中怎麼查詢單個表的轉儲過程日誌?