背景
本文基于 StarRocks 3.3.5
最近在做一些 StarRocks 相關的指標監控的時候,看到了FE master的CPU使用率相對其他FE節點是比較高的,且 呈現周期性的變化(周期為8分鐘),
于此同時FE master節點的GC頻率相對于其他節點高出很多倍,于是我們利用arthas采集了大約15分鐘CPU的火焰圖。如下:
對應的FE master節點的CPU使用率的變化如下圖:
FE 其他節點的CPU使用率的變化如下圖:
對應的FE master節點的Young GC的變化如下圖:
FE 其他節點的Young GC變化如下圖:
結論
CPU使用率高的主要集中在三個點:
- StatisticAutoCollector(占用28%)
- MemoryUsageTracker (占用9%)
- JVM GC(占用57%)
因為在我們的場景下,實時寫入的任務比較多,且寫入的是分區表(由于業務的場景問題,會更新以前分區的數據),所以會導致 StatisticAutoCollector 進行相關統計信息的收集,而這個統計信息的收集,會觸發System.gc
操作,從而導致FE master節點的 gc頻率比其他節點高很多。
分析
StatisticAutoCollector
StatisticAutoCollector 這個類只有在FE Master才會被調用,且調用的頻率為statistic_collect_interval_sec
,也就是5分鐘。
該線路數據流為:
StatisticAutoCollector.runAfterCatalogReady||\/
runJobs||\/
StatisticExecutor.collectStatistics||\/
FullStatisticsCollectJob.collect||\/
collectStatisticSync||\/
StmtExecutor.executeStatisticDQL||\/
StmtExecutor.executeDQL||\/
StatementPlanner.plan //走到 生成計劃||\/
createQueryPlanWithReTry||\/
collectOriginalOlapTables||\/
OlapTable.copyOnlyForQuery||\/
partitionInfo.clone()
partitionInfo.clone()
會初始化HashMap來復制partiiton的信息:
protected Object clone() {try {PartitionInfo p = (PartitionInfo) super.clone();p.type = this.type;p.idToDataProperty = new HashMap<>(this.idToDataProperty);p.idToReplicationNum = new HashMap<>(this.idToReplicationNum);p.isMultiColumnPartition = this.isMultiColumnPartition;p.idToInMemory = new HashMap<>(this.idToInMemory);p.idToTabletType = new HashMap<>(this.idToTabletType);p.idToStorageCacheInfo = new HashMap<>(this.idToStorageCacheInfo);return p;} catch (CloneNotSupportedException e) {throw new RuntimeException(e);}}
所以說在這種要收集的分區信息很多的情況下,HashMap的初始化,就很消耗CPU。
再者,在collectStatistics 之前會通過 StatisticsCollectJobFactory.buildStatisticsCollectJob 這個方法計算出要收集的 FullStatisticsCollectJob ,這里會通過執行select $quoteColumnName as column_key from $dbName.$tableName partition $partitionName
這種方法收集每個分區中某些字段的信息,這里后續會詳細說
MemoryUsageTracker
StatisticAutoCollector 這個類只有在FE Master才會被調用,且調用的頻率為 memory_tracker_interval_seconds
,也就是1分鐘。
該類的數據流為:
MemoryUsageTracker.runAfterCatalogReady||\/
MemoryUsageTracker.trackMemory||\/
MemoryTrackable.estimateSize||\/
SizeEstimator.estimate
這里會根據初始化方法initMemoryTracker
涉及到的對象進行內存的評估,具體的對象如下:
private void initMemoryTracker() {GlobalStateMgr currentState = GlobalStateMgr.getCurrentState();registerMemoryTracker("Load", currentState.getLoadMgr());registerMemoryTracker("Load", currentState.getRoutineLoadMgr());registerMemoryTracker("Load", currentState.getStreamLoadMgr());registerMemoryTracker("Load", currentState.getInsertOverwriteJobMgr());registerMemoryTracker("Compaction", currentState.getCompactionMgr());registerMemoryTracker("Export", currentState.getExportMgr());registerMemoryTracker("Delete", currentState.getDeleteMgr());registerMemoryTracker("Transaction", currentState.getGlobalTransactionMgr());registerMemoryTracker("Backup", currentState.getBackupHandler());registerMemoryTracker("Task", currentState.getTaskManager());registerMemoryTracker("Task", currentState.getTaskManager().getTaskRunManager());registerMemoryTracker("TabletInvertedIndex", currentState.getTabletInvertedIndex());registerMemoryTracker("LocalMetastore", currentState.getLocalMetastore());registerMemoryTracker("Query", new QueryTracker());registerMemoryTracker("Profile", ProfileManager.getInstance());registerMemoryTracker("Agent", new AgentTaskTracker());QeProcessor qeProcessor = QeProcessorImpl.INSTANCE;if (qeProcessor instanceof QeProcessorImpl) {registerMemoryTracker("Coordinator", (QeProcessorImpl) qeProcessor);}IDictManager dictManager = IDictManager.getInstance();if (dictManager instanceof CacheDictManager) {registerMemoryTracker("Dict", (CacheDictManager) dictManager);}memoryMXBean = ManagementFactory.getMemoryMXBean();LOG.info("Memory usage tracker init success");initialize = true;}
這里會對里面涉及到的所有對象進行內存的評估,用來后續的內存使用指標顯示。
JVM GC
這個方法是在每個SQL執行完后就會觸發的,具體的數據流為:
StatisticAutoCollector.runJobs||\/StatisticExecutor.collectStatistics||\/FullStatisticsCollectJob.collect||\/FullStatisticsCollectJob.collectStatisticSync ||\/flushInsertStatisticsData ||\/StmtExecutor.execute() ||\/GlobalStateMgr.getCurrentState().getMetadataMgr().removeQueryMetadata();||\/queryMetadatas.metadatas.values().forEach(ConnectorMetadata::clear)||\/LocalMetaStore.clear -> System.gc()
當然這也只是該 StatisticAutoCollector 定時的觸發的,還有如果有查詢SQL的話,也會進行觸發。具體看 StmtExecutor.execute方法:
public void execute() throws Exception {...try {...} finally {GlobalStateMgr.getCurrentState().getMetadataMgr().removeQueryMetadata();if (context.getState().isError() && coord != null) {coord.cancel(PPlanFragmentCancelReason.INTERNAL_ERROR, context.getState().getErrorMessage());}if (parsedStmt != null && parsedStmt.isExistQueryScopeHint()) {clearQueryScopeHintContext();}// restore session variable in connect contextcontext.setSessionVariable(sessionVariableBackup);}}