【GaussDB】內存資源告急:深度診斷一起“memory temporarily unavailable“故障

【GaussDB】診斷一起內存臨時不可用的問題


📋 背景

在客戶測試環境中(GaussDB 506.0 SPC0100 集中式),一個重度使用存儲過程的系統,頻繁出現內存臨時不可用的問題(ERROR: memory is temporarily unavailable)。令人困惑的是,這個環境配置的內存大小已經數十倍于遷移前的Oracle。

🔍 GaussDB提供的觀測方法

GaussDB官方產品文檔中有一個《內存過載》章節,詳細描述了在出現內存過載情況下如何定位,但似乎這個章節并沒有在官方網站上公開。不過GaussDB松鼠會倒是把這個章節的內容發成一篇文章了:《GaussDB內存過載分析》。

下面按照本次分析過程進行描述。

📊 內存監控視圖

GaussDB提供了幾個重要的內存視圖:

gs_total_memory_detail

顯示當前數據庫節點的內存使用情況,單位為MB。當GUC參數enable_memory_limit的值為off時,本視圖不可用。

gs_session_memory_detail

顯示會話的內存使用情況,以MemoryContext節點來統計。當開啟線程池(enable_thread_pool = on)時,該視圖包含所有線程和會話的內存使用情況。當GUC參數enable_memory_limit的值為off時,本視圖不可用。

gs_shared_memory_detail

查詢當前節點所有已產生的共享內存上下文的使用信息。

📈 內存使用情況示例

SELECT * FROM gs_total_memory_detail;
nodenamememorytypememorymbytes
primarymax_process_memory24576
primaryprocess_used_memory7847
primarymax_dynamic_memory16651
primarydynamic_used_memory1843
primarydynamic_peak_memory2942
primarydynamic_used_shrctx1386
primarydynamic_peak_shrctx1387
primarymax_backend_memory1360
primarybackend_used_memory1
primaryhigh_backend_thread_memory20
primaryhigh_backend_thread_max_memory768
primarymedium_backend_thread_memory113
primarymedium_backend_thread_max_memory1536
primarylow_backend_thread_memory0
primarylow_backend_thread_max_memory768
primarymax_shared_memory6548
primaryshared_used_memory5402
primarymax_cstore_memory16
primarycstore_used_memory0
primarymax_sctpcomm_memory0
primarysctpcomm_used_memory0
primarysctpcomm_peak_memory0
primaryother_used_memory527
primarygpu_max_dynamic_memory0
primarygpu_dynamic_used_memory0
primarygpu_dynamic_peak_memory0
primarypooler_conn_memory0
primarypooler_freeconn_memory0
primarystorage_compress_memory0
primaryudf_reserved_memory0
primaryllvm_used_memory0
primarymax_htap_memory0
primaryhtap_used_memory0

🧭 內存類型理解指南

要真實理解gs_total_memory_detail里這些內存類型的關系,并且理解gs_session_memory_detailgs_shared_memory_detail記錄的內存對應到gs_total_memory_detail的映射關系,需要明確以下幾點:

📏 內存指標含義
  • max 表示最大可以為多少,超過就會報內存不足
  • used 表示當前已經申請了多少
  • peak 表示本次數據庫啟動以來的歷史used峰值
🔗 內存分配關系
  • max_process_memory 是通過參數設置的
  • max_shared_memory 主要為shared_buffers,但是這個值的大小可能會比shared_buffers要大
  • max_dynamic_memorymax_process_memory - max_shared_memory - max_cstore_memory - max_backend_memory - ... 得到的

dynamic_used_memory 包括了:

  • 動態會話內存(gs_total_memory_detail里沒列出來)
  • dynamic_used_shrctx
  • high_backend_thread_memory
  • medium_backend_thread_memory
  • low_backend_thread_memory

💡 注意:后三個high/medium/low暫時不用關心,這是新版本中引入的特性,為后臺線程專門分出來的內存。

🗂? GaussDB內存區域劃分

在GaussDB中,主要的內存區域分為:

📁 共享內存(主要為shared_buffers)
📁 動態內存(dynamic_memory)├── 📄 動態會話內存 (gs_session_memory_detail)├── 📄 動態共享內存 (dynamic_used_shrctx) (gs_shared_memory_detail)└── 📄 動態后臺內存

?? 重要提醒:這里出現兩個"共享內存",非常容易產生理解上的歧義。比如gs_shared_memory_detail這個視圖,查的就不是"共享內存",而是"動態共享內存"。

可以這么理解:

  • shared_buffers主要是data buffer,即表和索引的數據
  • GaussDB實現的global plan cache/global plsql cache/unique sql hash table等全局緩存,并不屬于shared_buffers,而是在dynamic_used_shrctx

📝 內存追蹤配置

內存不足的報錯經常是一個瞬間沖高,回頭再來看這些視圖時,已經找不到對應的內存上下文,因此GaussDB在出現內存不足的報錯時,會自動把當時的內存上下文情況生成到單獨的日志文件中。

memory_trace_level 參數

參數說明:動態內存使用超過最大動態內存的90%后,記錄內存申請信息的管控等級。該參數僅在GUC參數use_workload_managerenable_memory_limit打開時生效。

參數類型:枚舉類型
參數單位:無

取值范圍

級別說明
none表示不記錄內存申請信息
level1動態內存使用超過最大動態內存的90%后,會記錄以下信息,并將記錄的內存信息保存在$GAUSSLOG/mem_log目錄下:
? 全局內存概況
? instance,session,thread三種類型的所有內存上下文中內存占用前20的內存上下文的內存使用情況
? 每個內存上下文的totalsize、freesize字段
level2在level1的基礎上,還會記錄每個內存上下文上所有內存申請的詳細信息,包含申請內存所在的文件,行號和大小

默認值:level1

?? 設置建議:該參數設置為level2后,會記錄每個內存上下文的內存申請詳情(file,line,size字段),會對性能影響較大,需慎重設置。

相關功能

  • 記錄的內存快照信息可以通過系統函數gs_get_history_memory_detail(cstring)查詢
  • 記錄的內存上下文是經過將同一類型所有重名的內存上下文進行匯總之后得到的

🔬 現場分析

🕵? 初步發現

由于默認是level1,沒有記錄申請內存的源碼文件和行號,因此后面現場調成了level2。

當時的mem_log中有記錄兩個比較可疑的內存上下文:

位置上下文名稱數量大小
shared_memoryunique sql hash table1個~8G
session_memorySRF multi-call context1個~8G

然后回頭查gs_shared_memory_detailgs_session_memory_detail這兩個視圖,發現的確可以查到unique sql hash table占了8G,但是查不到SRF multi-call context

📊 監控腳本

客戶DBA寫了個監控腳本,每隔20秒采集一下各個內存視圖的數據。

但幾天后,仍然沒有采集到SRF multi-call context,而數據庫日志中仍然會時不時報錯內存臨時不可用。

💡 關鍵發現

在一次偶然的連續查詢gs_total_memory_detail時,發現已使用的動態內存上下波動幅度高達8GB

于是猜想可能是一個非常短的時間產生了SRF multi-call context這個上下文,執行完后就釋放了,所以間隔20秒采一次能采到的概率非常低。

🔍 深入分析

數據庫大廠的內核研發往往都只負責各自一小塊領域,華為內核研發遠程查看日志,并沒有發現什么可疑的點,而且還拉了好幾個不同領域的內核研發,都沒有找到原因。

但是在遠程會議中,客戶快速一頁一頁翻日志時,我發現一個現象:

在出現內存臨時不可用報錯的前面一段,會頻繁打印相同格式的日志,而且中間有個數字一直在漲,從1XXXXXXXXX一直漲到7XXXXXXXXX,然后就沒有這個重復日志了。

直覺告訴我,很可能與這個有關。于是讓客戶回到這段日志前面,然后發現了類似下面的這么一行:

2025-07-25 10:00:01.523 primary RdsAdmin postgres xxx.xxx.xxx.xxx 140216907527936 12[0:0#0] 0 gsql 00000 31806672368306287 [BACKEND] LOG:  00000: unique_sql_id=3740540431, Current SQL is consuming about 1099 MB, allocating 102921000 bytes, memctx:SRF multi-call context, appname:, user_id: 16616, current query is: select usename,count(*) from dbe_perf.statement group by usename

🎯 問題串聯

很明顯這里就出現了SRF multi-call context,然后這段日志下面就是不斷地在申請內存,直到申請了8G,后面很快就有一個業務調用存儲過程報錯內存不足了。

也就是說,在這個環境中,查詢一次dbe_perf.statement這個視圖,就會申請8G內存。這個查詢,是TPOPS端定時采集的SQL。

dbe_perf.statement這個視圖里實際上是一個查詢特定內存數據的返回多行的函數,查詢的就是unique sql hash table里的內容。

📝 問題鏈條分析

分析到這里,感覺大部分疑點都串起來了:

  1. ? 由于某種原因,歸一化SQL的內存達到了8G
  2. 🔄 然后由于TPOPS定時查詢dbe_perf.statement,導致內存瞬間又消耗了8GB
  3. 📈 光這兩個動態內存上下文就占了16GB
  4. 💾 由于這是個重度存儲過程的系統,GLOBAL PLSQL CACHE又占了幾個GB
  5. ? 動態內存全用完了,導致業務會話報錯

? 進一步探究

歸一化內存為什么會達到8GB?

我在很早就問過華為內核研發:track_activity_query_size=40960這么設置,如果SQL沒達到這個長度,占用的內存是SQL實際的長度還是40960?

我一直記得在openGauss系數據庫中,這個內存一定是按設置的這個大小申請,不會根據SQL實際長度變化,但是華為內核研發說在GaussDB中內存占用只會按實際SQL長度來,除非SQL長度達到或者超過這個數,才會申請這么多內存。

由于GaussDB并沒有開源,所以當時我只能相信華為說的,可能GaussDB做了優化。

🧮 巧合的乘積

可是客戶現場出現這個現象時,我直接計算了下:

40,960 × 200,000 = 8,192,000,000

剛好差不多8G,這么巧合?

然后客戶也查了,實際在dbe_perf.statement里query的匯總字節數,也就只有一百多MB。

但是華為內核研發說他看了那段代碼,說的確是按實際長度來的,但是他又無法解釋這8GB到哪里去了,因為算上其他屬性的長度,每行最多就再加幾百KB。


🤖 問問萬能的AI

GaussDB沒開源,但是openGauss是開源的,本來想用Cursor來分析一下,奈何被斷供了,只能先用vscode的Copilot了。

打開openGauss源碼目錄,切換Copilot到agent模式,開問:

第一輪提問

Dark-Athena: 看下SRF multi-call context 這個上下文在什么時候會產生

GitHub Copilot :我來幫你分析 “SRF multi-call context” 這個上下文在什么時候會產生。讓我先搜索相關代碼來了解這個上下文的創建和使用場景。 現在讓我搜索更多關于 SRF(Set-Returning Functions)和多次調用上下文的信息: 讓我查看一些使用這些函數的實際例子: 現在讓我查看一個具體的SRF函數實現例子來理解上下文的產生時機: 現在讓我查看頭文件中的宏定義和更多的注釋說明: 現在讓我查看一下具體的內存上下文創建代碼: 讓我再查看一些具體的SRF函數使用例子: 讓我查找更多的SRF函數實現例子: 讓我查找一些實際的SRF函數實現: 現在讓我查看一個具體的pg_stat函數來理解SRF上下文的產生: 根據對代碼的分析,我現在可以詳細回答你的問題:

SRF multi-call context 產生的時機和原因

“SRF multi-call context” 是 Set-Returning Functions(集合返回函數)的多次調用上下文,它在以下情況下產生:

1. 什么是SRF?

SRF(Set-Returning Functions)是能夠返回多行數據的函數,比如:

  • pg_stat_get_activity() - 返回所有活動會話信息
  • unnest() - 展開數組為多行
  • generate_series() - 生成數字序列
  • 各種系統統計函數

2. 上下文產生的具體時機

SRF multi-call context 在以下時機被創建:

  1. 首次調用時:當SRF函數第一次被調用時,通過 SRF_FIRSTCALL_INIT() 宏(實際調用 init_MultiFuncCall())創建:
if (SRF_IS_FIRSTCALL()) {funcctx = SRF_FIRSTCALL_INIT();  // 這里創建 "SRF multi-call context"// 初始化操作...
}
  1. 內存上下文創建位置:在 init_MultiFuncCall() 函數中:
multi_call_ctx = AllocSetContextCreate(fcinfo->flinfo->fn_mcxt,"SRF multi-call context",    // 這就是你看到的上下文名稱ALLOCSET_SMALL_MINSIZE,ALLOCSET_SMALL_INITSIZE,ALLOCSET_SMALL_MAXSIZE);

3. 為什么需要這個上下文?

  1. 跨調用數據保存:SRF函數需要在多次調用之間保持狀態信息
  2. 內存管理:為跨調用的數據分配長期有效的內存
  3. 性能優化:避免每次調用都重新計算所有結果

4. 使用場景舉例

pg_stat_get_activity() 為例:

  • 第1次調用:創建上下文,獲取所有會話信息,返回第1行
  • 第2次調用:使用已有上下文,返回第2行
  • 第N次調用:返回第N行
  • 最后一次:清理上下文,結束返回

5. 生命周期

// 第一次調用 - 創建上下文
funcctx = SRF_FIRSTCALL_INIT();// 后續調用 - 使用現有上下文  
funcctx = SRF_PERCALL_SETUP();// 最后 - 清理上下文
SRF_RETURN_DONE(funcctx);  // 調用 end_MultiFuncCall()

6. 典型的SRF函數模式

Datum my_srf_function(PG_FUNCTION_ARGS) {FuncCallContext *funcctx;if (SRF_IS_FIRSTCALL()) {// 創建 "SRF multi-call context"funcctx = SRF_FIRSTCALL_INIT();// 在這個上下文中分配跨調用的數據MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);// ... 初始化操作MemoryContextSwitchTo(oldcontext);}funcctx = SRF_PERCALL_SETUP();if (funcctx->call_cntr < funcctx->max_calls) {// 返回下一行數據SRF_RETURN_NEXT(funcctx, result);} else {// 清理上下文并結束SRF_RETURN_DONE(funcctx);}
}

總結:SRF multi-call context 在任何集合返回函數第一次被調用時產生,用于在函數的多次調用之間保持狀態和數據,直到函數返回完所有結果后被清理。

第二輪提問

Dark-Athena: 分析下執行get_instr_unique_sql這個函數時,申請的"SRF multi-call context"占用大小與什么有關

GitHub Copilot: 我來分析 get_instr_unique_sql 函數執行時 “SRF multi-call context” 內存占用的情況。首先讓我搜索這個函數的實現。 讓我搜索這個函數的具體實現: 現在讓我找到這個函數的具體實現: 讓我搜索這個函數的具體實現文件: 現在讓我查看這個函數的具體實現: 現在讓我查看 GetUniqueSQLStat 函數,這是關鍵的數據獲取函數: 現在讓我查看內存分配的關鍵部分和數據結構定義: 現在讓我查找相關的宏定義和常量: 現在讓我查看 UniqueSQL 數據結構定義: 讓我查找 UniqueSQL 結構體的定義: 基于對代碼的深入分析,我現在可以詳細回答你關于 get_instr_unique_sql 函數執行時 “SRF multi-call context” 內存占用與什么相關的問題:

SRF multi-call context 在 get_instr_unique_sql 中的內存占用分析

1. 內存占用的核心影響因素

SRF multi-call context 的內存占用主要與以下幾個因素密切相關:

1.1 Unique SQL 條目數量
*num = hash_get_num_entries(g_instance.stat_cxt.UniqueSQLHashtbl);
  • 哈希表中存儲的 unique SQL 條目總數是最主要的影響因素
  • 條目數量越多,需要分配的內存越大
1.2 每個條目的大小
int malloc_size = (int)MAX_MEM_UNIQUE_SQL_ENTRY_COUNT * (sizeof(UniqueSQL) + unique_sql_str_len);

單個條目大小包括:

  • UniqueSQL 結構體:約 200+ 字節(包含統計信息、時間信息、網絡信息等)
  • SQL 字符串長度UNIQUE_SQL_MAX_LEN = g_instance.attr.attr_common.pgstat_track_activity_query_size + 1
1.3 節點類型和配置
int unique_sql_str_len = (IS_PGXC_COORDINATOR || IS_SINGLE_NODE) ? UNIQUE_SQL_MAX_LEN : 0;
  • CN 節點或單機:需要存儲完整的 SQL 字符串,內存占用更大
  • DN 節點:不存儲 SQL 字符串,內存占用相對較小

2. 內存分配策略

2.1 批次分配機制
#define MAX_MEM_UNIQUE_SQL_ENTRY_COUNT 1000.0for (int j = 0; j < ceil(*num / MAX_MEM_UNIQUE_SQL_ENTRY_COUNT); j++) {int malloc_size = (int)MAX_MEM_UNIQUE_SQL_ENTRY_COUNT * (sizeof(UniqueSQL) + unique_sql_str_len);unique_sql_array = (UniqueSQL*)palloc0_noexcept(malloc_size);
}
  • 分批處理:每批最多處理 1000 個條目
  • 避免大塊內存分配:防止因條目過多導致的內存分配失敗
2.2 內存布局
/* memory format: [entry_1] [entry_2] [entry_3] ... | [sql_1] [sql_2] [sql_3] ... */
  • 結構體數組 + SQL 字符串數組的連續內存布局
  • 提高內存訪問效率

3. 具體內存計算公式

3.1 CN 節點或單機環境
總內存 = 批次數 × 每批內存大小
每批內存大小 = 1000 × (sizeof(UniqueSQL) + pgstat_track_activity_query_size + 1)
3.2 DN 節點環境
總內存 = 批次數 × 每批內存大小
每批內存大小 = 1000 × sizeof(UniqueSQL)

4. 主要配置參數影響

4.1 instr_unique_sql_count
  • 直接影響:控制系統中最多能存儲多少個 unique SQL 條目
  • 內存影響:條目數越多,SRF 上下文需要的內存越大
4.2 pgstat_track_activity_query_size
  • 直接影響:控制每個 SQL 字符串的最大長度
  • 在 CN 節點上影響巨大:該值越大,每個條目占用的內存越多

5. 內存占用估算示例

假設配置如下:

  • instr_unique_sql_count = 5000 (5000個條目)
  • pgstat_track_activity_query_size = 4096 (4KB SQL字符串)
  • sizeof(UniqueSQL) ≈ 200 字節

CN 節點內存占用:

批次數 = ceil(5000 / 1000) = 5 批
每批內存 = 1000 × (200 + 4096 + 1) = 4,297,000 字節 ≈ 4.3 MB
總內存 ≈ 5 × 4.3 MB = 21.5 MB

DN 節點內存占用:

批次數 = 5 批
每批內存 = 1000 × 200 = 200,000 字節 ≈ 200 KB
總內存 ≈ 5 × 200 KB = 1 MB

6. 內存優化和保護機制

6.1 錯誤處理
if (unique_sql_array == NULL) {ereport(ERROR, (errmsg("[UniqueSQL] palloc0 error when querying unique sql stat!")));
}
6.2 內存清理
if (funcctx->user_fctx) {list_free_deep(((UniqueSQLResults *)(funcctx->user_fctx))->batch_list);pfree_ext(funcctx->user_fctx);
}

總結

SRF multi-call contextget_instr_unique_sql 函數中的內存占用主要取決于:

  1. 系統中 unique SQL 條目的總數量(最關鍵因素)
  2. 節點類型(CN/單機 vs DN)
  3. SQL 字符串的最大長度配置
  4. UniqueSQL 結構體的大小

在生產環境中,建議根據實際的 SQL 多樣性和系統內存容量合理配置 instr_unique_sql_countpgstat_track_activity_query_size 參數,以平衡監控精度和內存占用。

第三輪關鍵提問

Dark-Athena: 如果我一條SQL的長度沒達到pgstat_track_activity_query_size,也會申請pgstat_track_activity_query_size這個值大小的內存么?

GitHub Copilot: Summarized conversation history我來幫你分析一下在openGauss中,當SQL長度沒達到pgstat_track_activity_query_size時的內存申請情況。 讓我再搜索更具體的信息,關于SQL字符串的內存分配機制。 讓我重新搜索更直接相關的代碼: 現在讓我搜索關于st_activity緩沖區分配的代碼: 基于對代碼的分析,我可以回答你的問題了:

回答:是的,即使SQL的實際長度沒有達到pgstat_track_activity_query_size,系統也會為每個后端進程預先分配pgstat_track_activity_query_size大小的內存空間。

詳細分析:

1. 內存預分配機制

在pgstat.cpp的BackendStatusShmemInit函數中,可以看到:

/* Create or attach to the shared activity buffer */
t_thrd.shemem_ptr_cxt.BackendActivityBufferSize =mul_size(g_instance.attr.attr_common.pgstat_track_activity_query_size, BackendStatusArray_size);
t_thrd.shemem_ptr_cxt.BackendActivityBuffer =(char*)ShmemInitStruct("Backend Activity Buffer", t_thrd.shemem_ptr_cxt.BackendActivityBufferSize, &found);if (!found) {/* Initialize st_activity pointers. */buffer = t_thrd.shemem_ptr_cxt.BackendActivityBuffer;for (i = 0; i < BackendStatusArray_size; i++) {t_thrd.shemem_ptr_cxt.BackendStatusArray[i].st_activity = buffer;buffer += g_instance.attr.attr_common.pgstat_track_activity_query_size;  // 每個后端進程分配固定大小}
}

2. SQL寫入機制

pgstat_report_activity函數中,當記錄SQL活動時:

if (cmd_str != NULL) {len = pg_mbcliplen(cmd_str, strlen(cmd_str), g_instance.attr.attr_common.pgstat_track_activity_query_size - 1);
}// ...
rc = memcpy_s((char*)beentry->st_activity, g_instance.attr.attr_common.pgstat_track_activity_query_size,cmd_str, len);
beentry->st_activity[len] = '\0';

這里的關鍵點是:

  • len是實際要復制的SQL長度(經過截斷處理)
  • 但是memcpy_s的目標緩沖區大小始終是pgstat_track_activity_query_size
  • 只有實際使用的長度是變化的,但預分配的空間是固定的

3. 內存占用情況

  • 預分配空間:每個后端進程都會分配pgstat_track_activity_query_size字節的空間
  • 實際使用:只使用SQL實際長度 + 1(null終止符)的空間
  • 浪費空間pgstat_track_activity_query_size - (實際SQL長度 + 1)字節

4. 設計原因

這種預分配設計的原因:

  1. 性能考慮:避免動態內存分配/釋放的開銷
  2. 并發安全:共享內存中的固定布局,避免競爭條件
  3. 簡化管理:統一的內存布局便于管理和訪問

所以答案是肯定的:即使你的SQL很短(比如SELECT 1;),系統仍然會為每個后端進程的st_activity字段分配完整的pgstat_track_activity_query_size大小的內存空間。


🎯 結論分析

結合AI的分析,可以得出以下結論:

📌 核心發現

  1. 📏 固定內存分配

    • track_activity_query_size是固定會申請的內存大小
    • 占用的內存大小不會由于SQL比這個值小而減小
    • 實際單個unique sql所占用的空間,會比track_activity_query_size還要大一些
    • instr_unique_sql_count為GaussDB默認的200000,track_activity_query_size為40960時,unique sql hash table能達到8GB
  2. 🔄 SRF函數特性

    • SRF函數就是返回多行的函數
    • dbe_perf.statement里就是一個SRF函數
    • 為了在返回多行時保持數據穩定性,就會把查詢結果全存到內存里,直到查詢完才釋放
  3. 💾 內存復制機制

    • dbe_perf.statement查的就是unique sql hash table的內容
    • SRF為了數據穩定性,幾乎把這8GB的內存復制了一份,放到了SRF multi-call context里

🔍 深層問題探究

看上去這就是個非常低級的問題?但是問題還沒完:

為什么unique sql會那么快達到200000個?

這個環境才剛部署沒幾天,而且都是跑的存儲過程,沒有什么拼接SQL,理論上SQL數是穩定有限的。

通過截取statement.query前200個字符進行group by計數排序,發現很大一部分SQL都是一個declare開頭的自治事務匿名塊。

🔧 根本原因

由于我之前和openGauss社區的研發一起分析過plsql編譯依賴這個功能的問題,當時為了解決編譯時數據字典表鎖釋放的問題,我提出了可以起個自治事務去編譯,這樣能避免長時間持有元數據鎖的問題。而GaussDB也是用的這個方案(巧合?)。

所以我一眼就看出來GaussDB里記錄的這些SQL是在做存儲過程編譯,但是GaussDB把整個存儲過程的內容都放到這個匿名塊里了,而且還有一些oid信息,因此必然是不同的SQL。

這個匿名塊還會做一些數據字典的DML操作,這些DML的SQL是固定不變的,大概有6~7條。再來就是dbe_perf.statement里,對于parent_unique_sql_id不同的同一個unique_sql_id,會視為不同的記錄。

? 問題鏈條完整梳理

  1. 存儲過程編譯:每次改一個存儲過程,就在dbe_perf.statement里生成了約10條的記錄
  2. 重復生成:反復改就反復生成,有時候全量存儲過程重建,數字唰唰唰就漲上去了
  3. 資源耗盡:200000也經不起這么耗,真正的業務SQL可能還會計不進去了
  4. 內存占用:全是存儲過程編譯時自動產生的這些SQL

目前客戶這邊只能先調小track_activity_query_size到4K,讓dbe_perf.statement的內存占得小些,至少讓業務測試能繼續進行下去。


🧪 復現方法

診斷了這么多天的問題,弄清楚原理后,非常容易復現unique sql hash tableSRF multi-call context申請大量內存的現象:

🔧 環境配置

-- 這個是單個會話的unique sql上限,由于模擬測試都在一個會話里,所以要調大
gs_guc reload -c "track_stmt_session_slot=200000"-- 直接設置到最大值,內存漲得最快
gs_guc reload -c "track_activity_query_size=102400"-- 重啟,釋放 unique sql 內存
gs_ctl restart

📝 測試腳本

-- 構造不同的SQL
-- 注意GaussDB對于表別名或字段別名不同、常量不同的也會進行歸一化
-- 所以這里直接用不同的對象名
DECLAREx_sql1 text;x_sql2 text;i int;
BEGINFOR i IN 1..5000 LOOPx_sql1 := 'CREATE TABLE t_abcd'||i||'(a int);';x_sql2 := 'DROP TABLE t_abcd'||i;EXECUTE IMMEDIATE x_sql1;EXECUTE IMMEDIATE x_sql2;END LOOP;RAISE NOTICE '%', x_sql1;
END;
/

📊 驗證結果

-- 查詢動態共享內存里unique sql hash table的大小
SELECT contextname,count(1),ROUND(sum(totalsize)/1024/1024,2) AS TOTALSIZE_MB,ROUND(Sum(usedsize)/1024/1024) AS usedsize_MB 
FROM GS_shared_MEMORY_DETAIL  
GROUP BY contextname 
ORDER BY 3 DESC 
LIMIT 10;
contextnamecounttotalsize_mbusedsize_mb
unique sql hash table11468.751469
DefaultTopMemoryContext1172.5760
ASP unique sql hash table182.1182
  • unique sql hash table占1個多GB
-- 查詢SQL實際長度所占的空間,和按track_activity_query_size的長度計算的空間
SELECT sum(lengthb(query))/1024/1024 AS actual_size_mb,(count(1)*current_setting('track_activity_query_size')::int)/1024/1024 AS allocated_size_mb
FROM dbe_perf.statement;
actual_size_mballocated_size_mb
0.381401.07
  • 按track_activity_query_size來算,也是1個多GB。

📋 日志驗證

執行查詢后,日志中出現SRF multi-call context

2025-07-25 10:00:01.523 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor <Script-22.sql> 00000 31806672368306287 [BACKEND] LOG:  00000: unique_sql_id=3740540431, Current SQL is consuming about 1099 MB, allocating 102921000 bytes, memctx:SRF multi-call context, appname:, user_id: 16616, current query is: select sum( lengthb(query) )/?/?,(count(?)*current_setting(?)::int)/?/? from dbe_perf.statement
2025-07-25 10:00:01.523 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor <Script-22.sql> 00000 31806672368306287 [BACKEND] LOCATION:  mmgr_memory_check_reach_one_gb, framework_memory_memctrl_util.cpp:241
2025-07-25 10:00:01.523 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor <Script-22.sql> 00000 31806672368306287 [INSTR] LOG:  00000: [UniqueSQL] idx[10] - new memory allocated: 102921000
2025-07-25 10:00:01.523 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor <Script-22.sql> 00000 31806672368306287 [INSTR] LOCATION:  unique_sql_log_result_mem, instr_unique_sql.cpp:1407
2025-07-25 10:00:01.524 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor <Script-22.sql> 00000 31806672368306287 [INSTR] LOG:  00000: [UniqueSQL] total memory allocated: 1132131000
2025-07-25 10:00:01.524 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor <Script-22.sql> 00000 31806672368306287 [INSTR] LOCATION:  unique_sql_log_result_mem, instr_unique_sql.cpp:1408

💡 總結

🎯 經驗教訓

  1. 📚 經驗的積累非常重要

    • 國產數據庫使用過程中肯定會發現很多新問題,其中有一些綜合性的問題,原廠的內核研發都不一定能找到原因,具備綜合技能的DBA能從現場的蛛絲馬跡結合長期的工作經驗所產生的直覺,來發現問題癥結所在
  2. 🧠 知識的積累非常重要

    • 如果連內存區域都分不清,就算數據庫已經提供了很多方法來進行診斷,面對本文這個問題,腦子里只能是一團漿糊
  3. 🤖 需要學會借助AI

    • 現在的AI有了agent,能自動且快速完成很多事,本文中就讓AI自動翻源碼分析,對問題進行了快速定位
  • 本文作者: DarkAthena
  • 本文鏈接: https://www.darkathena.top/archives/GaussDB-Memory-Crisis-Diagnosing-Temporarily-Unavailable-Failures
  • 版權聲明: 本博客所有文章除特別聲明外,均采用CC BY-NC-SA 3.0 許可協議。轉載請注明出處

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/916817.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/916817.shtml
英文地址,請注明出處:http://en.pswp.cn/news/916817.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

FastDFS如何提供HTTP訪問電子影像文件

Nginx 作為高性能的 Web 服務器和反向代理服務器&#xff0c;與 FastDFS 結合使用&#xff0c;本文主要介紹用于處理 FastDFS 存儲文件的 HTTP 訪問請求&#xff0c;方便客戶端通過 HTTP 協議直接訪問存儲在 FastDFS 中的文件&#xff0c;在電子影像系統中&#xff0c;Nginx 可…

水面垃圾識別分割數據集labelme格式2111張8類別

數據集中有部分增強圖片&#xff0c;注意為了提供模型泛化識別能力&#xff0c;有很少一部分不是水面垃圾。具體看圖片數據集格式&#xff1a;labelme格式(不包含mask文件&#xff0c;僅僅包含jpg圖片和對應的json文件)圖片數量(jpg文件個數)&#xff1a;2111標注數量(json文件…

北京-4年功能測試2年空窗-報培訓班學測開-第六十二天-模擬未通過,繼續準備自我介紹項目介紹面試題中

今日產出&#xff0c;完成昨天模擬面試錄音的重聽和整理&#xff0c;完成自我介紹梳理&#xff0c;還重寫了三個算法題&#xff0c;寫了4個sql題。和同學打語音提問今天專注力不強&#xff0c;因為焦慮項目和面試題。關于項目&#xff0c;我理解的&#xff0c;老師以前錄屏講的…

自動化輔助工具教程

該工具支持全部平臺使用

里程碑 | 1Panel開源面板GitHub Star數量突破30,000個!

截至2025年7月23日20:00&#xff0c;飛致云旗下開源項目——1Panel開源Linux服務器運維管理面板GitHub Star數超過30,000個&#xff01; 繼Halo之后&#xff0c;1Panel成為飛致云旗下第二個GitHub Star數量超過30,000個的開源項目&#xff0c;也是飛致云旗下最快達成30,000個Gi…

雨雪霧冰全預警:交通氣象站為出行安全筑起“隱形防護網”

冬季濃霧彌漫高速&#xff0c;能見度降至 50 米以下&#xff1b;夏季暴雨傾盆&#xff0c;低洼路段眨眼間積水成河…… 惡劣天氣總是公路交通安全的大敵。關鍵時刻&#xff0c;交通氣象站挺身而出&#xff0c;成為保障道路暢通與行車安全的科技尖兵。交通氣象站專為復雜道路環境…

C++(面向對象之繼承、多態)

一、繼承前言在c里面&#xff0c;繼承是指2個類之間的關系例如&#xff1a;有一個org類&#xff0c;功能很完善&#xff0c;體量很大&#xff0c;突然有一天&#xff0c;需求發生改變&#xff0c;org類不能滿足新的需求&#xff0c;我們的第一想法是&#xff0c;復制 org 類代碼…

【數據可視化-75】北京密云區2025年7月暴雨深度分析:Python + Pyecharts 炫酷大屏可視化(含完整數據、代碼)

&#x1f9d1; 博主簡介&#xff1a;曾任某智慧城市類企業算法總監&#xff0c;目前在美國市場的物流公司從事高級算法工程師一職&#xff0c;深耕人工智能領域&#xff0c;精通python數據挖掘、可視化、機器學習等&#xff0c;發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN…

Voxtral Mini:語音轉文本工具,支持超長音頻,多國語音

Voxtral是什么 Mistral AI 推出的 Voxtral 是一款強大的音頻模型&#xff0c;專為語音交互打造&#xff0c;具備卓越的語音轉寫和語義理解能力。它提供 24B 和 3B 兩種版本&#xff0c;分別適用于大規模生產和本地部署。Voxtral 支持多語言、長文本上下文、問答與總結&#xf…

React Native環境配置完整版(超詳細)

目錄 第一步&#xff1a;打開React Native官方網站 第二步&#xff1a;安裝NVM與Node.js 一、安裝NVM 二、使用NVM安裝Node.js 三、NVM使用說明 四、環境變量配置 第三步&#xff1a;安裝JDK 一、JDK介紹 二、下載與安裝JDK 三、環境配置 四、驗證是否配置成功 第四…

Rust 最短路徑、Tide、Partial、Yew、Leptos、數獨實踐案例

基于Rust的Meta和pyrefly相關實例 以下是一些基于Rust的Meta和pyrefly相關實例的示例代碼和用法。這些示例涵蓋了常見的使用場景,包括元編程、代碼生成、Python交互等。 基本元編程示例 使用Rust的宏和元編程功能生成代碼。 macro_rules! greet {($name:expr) => {prin…

Microsoft-DNN NTLM暴露漏洞復現(CVE-2025-52488)

免責申明: 本文所描述的漏洞及其復現步驟僅供網絡安全研究與教育目的使用。任何人不得將本文提供的信息用于非法目的或未經授權的系統測試。作者不對任何由于使用本文信息而導致的直接或間接損害承擔責任。如涉及侵權,請及時與我們聯系,我們將盡快處理并刪除相關內容。 前…

Windows上WSL2實例與宿主機橋接網絡實現局域網互通

在 WSL 2 中&#xff0c;默認使用 NAT 網絡模式&#xff0c;這會導致 WSL 實例和宿主機所在的局域網無法直接互通。要讓 WSL 2 和宿主機所在的局域網互通&#xff0c;可以通過以下步驟配置 wsl.conf 并結合宿主機網絡設置&#xff0c;啟用 WSL 2 的橋接模式&#xff1a;修改 ws…

程序代碼篇---數據包解析

數據包解析是不同設備&#xff08;如電腦、ESP32 等嵌入式設備&#xff09;之間通信的核心環節。簡單說&#xff0c;就是把收到的 "一串數據" 翻譯成雙方都能理解的 "具體信息"&#xff08;比如溫度、濕度、命令等&#xff09;。下面介紹幾種常見的數據包格…

【開發雜談】用AI玩AI聊天游戲:使用 Electron 和 Python 開發大模型語音聊天軟件

項目地址&#xff1a; GitHub | wfts-ai-chathttps://github.com/HiMeditator/wfts-ai-chat 前言 最近一個基于 AI 的聊天游戲 Whispers from the Stars&#xff08;群星低語&#xff09;的 Demo 版本發布了。《Whispers from the Star》是一款科幻主題互動游戲。背景設定在…

SQL優化系統解析

MySQL的安裝就不講述了, 本篇文章著重講解sql優化 本篇是對B站顏群老師視頻講解的筆記梳理, 感興趣的可以去看下老師的原視頻: SQL優化 MySQL原理 1. MySQL邏輯分層: 連接層->服務層->引擎層->存儲層(如圖) 連接層&#xff1a;提供與客戶端連接的服務服務層&#…

【機器學習案列-25】電信用戶流失預測:從數據處理到模型評估

&#x1f9d1; 博主簡介&#xff1a;曾任某智慧城市類企業算法總監&#xff0c;目前在美國市場的物流公司從事高級算法工程師一職&#xff0c;深耕人工智能領域&#xff0c;精通python數據挖掘、可視化、機器學習等&#xff0c;發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN…

【MATLAB代碼】灰色預測與多項式預測、指數平滑預測的對比,包含預處理、模型構建和和可視化輸出。模擬預測若干年的GDP,訂閱后可查看完整代碼,有中文注釋

代碼實現了灰色預測模型GM(1,1)在GDP預測中的應用,并結合線性回歸、二次多項式回歸和指數平滑模型進行對比分析。代碼包含數據預處理、模型構建、可視化輸出和誤差驗證四個核心模塊,實現了從數據輸入到預測結果展示的全流程。 文章目錄 運行結果 MATLAB源代碼 GM(1,1)模型數學…

搜索二維矩陣Ⅱ C++

編寫一個高效的算法來搜索 m x n 矩陣 matrix 中的一個目標值 target 。該矩陣具有以下特性&#xff1a; 每行的元素從左到右升序排列。 每列的元素從上到下升序排列。class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {i…

如何在 Apache Ignite 中創建和使用自定義 SQL 函數(Custom SQL Functions)

這段內容講的是 如何在 Apache Ignite 中創建和使用自定義 SQL 函數&#xff08;Custom SQL Functions&#xff09;。我們可以分步驟來理解它的含義和用法。&#x1f4da; 一、什么是 Custom SQL Function&#xff1f; Apache Ignite 的 SQL 引擎支持 標準 SQL 函數&#xff08…