當您發現MaxCompute賬單持續上漲,而且成本變得難以管理時,您可以從計算作業著手,通過對SQL作業和MapReduce作業的最佳化而減少計算成本。本文為您介紹SQL作業和MapReduce作業計算成本的控制方法。
預估計算成本
您可以在計算前對計算成本進行預估,控制計算成本。詳細的預估方法,請參見TCO工具。也可以配置消費預警,預防意料之外的高額費用。如果計算成本過高,您可以參考下面的方法進行最佳化,以控制計算成本。
SQL作業計算成本控制
對於SQL計算作業,大部分費用較高的SQL都是由全表掃描引起的。另外,調度頻繁也會引起SQL作業費用的增加,調度頻繁可能會產生任務的堆積,在後付費的情況下會造成排隊現象,如果任務多又出現了排隊,那麼第二天的賬單就會異常。通過如下策略進行SQL作業計算成本控制:
避免頻繁調度。MaxCompute是批次運算的服務,距離即時的計算服務還是存在一定距離的。如果間隔時間變短,計算頻率增加,再加上使用SQL的不良習慣就會導致計算費用飆升,產生費用較高的賬單。所以請盡量避免頻繁調度,如果要進行頻繁調度請通過CostSQL等方式預估一下SQL的開銷到底有多大,不然會造成較大預估外的開銷。
控制全表掃描。您可以通過以下幾種策略來控制全表掃描問題:
設定參數關閉全表掃描功能。目前支援Session層級和Project層級的控制。
--禁止session 層級全表掃描。 set odps.sql.allow.fullscan=false; --禁止project層級全表掃描。 SetProject odps.sql.allow.fullscan=false;
使用列剪裁。在讀資料的時候,唯讀取查詢中需要用到的列,而忽略其他列,避免使用
SELECT *
引起全表掃描。SELECT a,b FROM T WHERE e < 10;
其中,T包含5個列
(a,b,c,d,e)
,列c,d將會被忽略,只會讀取a,b,e列。使用分區剪裁。分區剪裁是指對分區列指定過濾條件,使得唯讀取表的部分分區資料,避免全表掃描引起的錯誤及資源浪費。
SELECT a,b FROM T WHERE partitiondate='2017-10-01';
SQL關鍵字的最佳化。計費的SQL關鍵字包括:JOIN、GROUP BY、ORDER BY、DISTINCT、INSERT INTO。您可以根據以下建議進行最佳化:
在進行JOIN的時候,一定要先進行分區剪裁再進行JOIN,不然的話就可能會先做全表掃描。分區裁剪失效請參考分區剪裁失效的情境分析。
減少FULL OUTER JOIN 的使用,改為UNION ALL。
SELECT COALESCE(t1.id, t2.id) AS id, SUM(t1.col1) AS col1 , SUM(t2.col2) AS col2 FROM ( SELECT id, col1 FROM table1 ) t1 FULL OUTER JOIN ( SELECT id, col2 FROM table2 ) t2 ON t1.id = t2.id GROUP BY COALESCE(t1.id, t2.id); --可以最佳化為如下語句。 SELECT t.id, SUM(t.col1) AS col1, SUM(t.col2) AS col2 FROM ( SELECT id, col1, 0 AS col2 FROM table1 UNION ALL SELECT id, 0 AS col1, col2 FROM table2 ) t GROUP BY t.id;
在UNION ALL內部儘可能不使用GROUP BY,改為在外層統一GROUP BY。
SELECT t.id, SUM(t.val) AS val FROM ( SELECT id, SUM(col3) AS val FROM table3 GROUP BY id UNION ALL SELECT id, SUM(col4) AS val FROM table4 GROUP BY id ) t GROUP BY t.id; 可以最佳化為--------------------------- SELECT t.id, SUM(t.val) AS val FROM ( SELECT id, col3 AS val FROM table3 UNION ALL SELECT id, col4 AS val FROM table4 ) t GROUP BY t.id;
臨時匯出的資料如果需要排序,盡量在匯出後使用Excel等工具進行排序,避免使用ORDER BY。
盡量避免使用DISTINCT關鍵字,改為多套一層GROUP BY。
SELECT COUNT(DISTINCT id) AS cnt FROM table1; 可以最佳化為--------------------------- SELECT COUNT(1) AS cnt FROM ( SELECT id FROM table1 GROUP BY id ) t;
盡量避免使用INSERT INTO方式寫入資料,可以考慮增加一個分區欄位。通過降低SQL複雜度,來節省SQL的費用。
避免使用執行查詢的方式預覽表資料。如果您想預覽表資料,可以使用表預覽的方式查看資料,而不會產生費用。如果您使用DataWorks,在資料地圖頁面,可以預覽表以及查看錶的詳情,具體方法請參見查看錶詳情。如果您使用MaxCompute Studio,雙擊表就可以進行表資料預覽。
計算時合理的選擇工具。由於MaxCompute的查詢響應是分鐘級,不適合直接用於前端查詢,計算出的結果資料同步到外部儲存中儲存,對於大部分使用者來說,關係型資料庫是最優先的選擇。輕度計算推薦使用MaxCompute,重度計算(即直接出最終結果。前端展示時,不做任何判斷、彙總、關聯字典表、甚至不帶WHERE條件)推薦使用RDS等關係型資料庫。
MapReduce作業計算成本控制
通過如下策略進行MapReduce作業計算成本控制:
設定合理的參數。
split size
Map預設的split size是256 MB,split size的大小決定了Map個數多少,如果使用者的代碼邏輯比較耗時,Map需要較長時間結束,可以通過
JobConf#setSplitSize
方法適當調小split size
。然而split size
也不宜設定太小,否則會佔用過多的計算資源。MapReduce Reduce Instance
單個job預設Reduce Instance個數為Map Instance個數的1/4,使用者佈建作為最終的Reduce Instance個數,範圍[0, 2000],數量越多,計算時消耗越多,成本越高,應合理設定。
MapReduce減少中間環節
如果有多個MapReduce作業之間有關聯關係,前一個作業的輸出是後一個作業的輸入,可以考慮採用Pipeline的模式,將多個串列的MapReduce作業合并為一個,這樣可以用更少的作業數量完成同樣的任務。一方面減少中間表造成的多餘磁碟IO,提升效能;另一方面減少作業數量使調度更加簡單,增強流程的可維護性,具體使用方法請參見Pipeline樣本。
對輸入表列裁剪
對於列數特別多的輸入表,Map階段處理只需要其中的某幾列,可以通過在添加輸入表時明確指定輸入的列,減少輸入量。例如只需要c1,c2列,可以參考如下設定。
InputUtils.addTable(TableInfo.builder().tableName("wc_in").cols(new String[]{"c1","c2"}).build(), job);
設定後,在Map中讀取到的Record就只有c1,c2列,如果之前是使用列名擷取Record資料,不會有影響,而用下標擷取的需要注意這個變化。
避免資源重複讀取
資源的讀取盡量放置到Setup階段讀取,避免資源多次讀取的效能損失,另外系統也有64次讀取的限制,資源的讀取請參見使用資源樣本。
減少物件建構開銷
對於Map、Reduce階段每次都會用到的Java對象,避免在Map/Reduce函數裡構造,可以放到Setup階段,避免多次構造產生的開銷。
{ ... Record word; Record one; public void setup(TaskContext context) throws IOException { // 建立一次就可以,避免在map中每次重複建立。 word = context.createMapOutputKeyRecord(); one = context.createMapOutputValueRecord(); one.set(new Object[]{1L}); } ... }
合理使用Combiner
如果Map的輸出結果中有很多重複的Key,可以合并後輸出,Combiner後可以減少網路頻寬傳輸和一定Shuffle的開銷。如果Map輸出本來就沒有多少重複的,就不要用Combiner,用了反而可能會有一些額外的開銷。Combiner實現的是和Reducer相同的介面,例如一個WordCount程式的Combiner可以定義如下。
/** * A combiner class that combines map output by sum them. */ public static class SumCombiner extends ReducerBase { private Record count; @Override public void setup(TaskContext context) throws IOException { count = context.createMapOutputValueRecord(); } @Override public void reduce(Record key, Iterator<Record> values, TaskContext context) throws IOException { long c = 0; while (values.hasNext()) { Record val = values.next(); c += (Long) val.get(0); } count.set(0, c); context.write(key, count); } }
合理選擇Partition Column或自訂Partitioner
合理選擇Partition Columns,可以使用
JobConf#setPartitionColumns
這個方法進行設定(預設是Key Schema定義的Column),設定後資料將按照指定的列計算HASH值分發到Reduce中, 避免資料扭曲導致作業長尾現象,如有必要也可以選擇自訂Partitioner,自訂Partitioner的使用方法如下。import com.aliyun.odps.mapred.Partitioner; public static class MyPartitioner extends Partitioner { @Override public int getPartition(Record key, Record value, int numPartitions) { // numPartitions即對應reducer的個數 // 通過該函數決定map輸出的key value去往哪個reducer。 String k = key.get(0).toString(); return k.length() % numPartitions; } }
在jobconf裡進行設定如下。
jobconf.setPartitionerClass(MyPartitioner.class)
需要在jobconf裡明確指定Reducer的個數。
jobconf.setNumReduceTasks(num)
合理使用JVM記憶體參數
過於追求調優,把MapReduce任務記憶體設定過大也會造成成本上升。標準配置是
1 Core 4G ,odps.stage.reducer.jvm.mem=4006
,當CPU與記憶體比超過1:4
時,對應的費用也會大幅升高。
相關文檔
使用MaxCompute過程中,還可以考慮從儲存和資料上傳和下載方面進行成本最佳化,請參見儲存成本最佳化、資料上傳下載成本最佳化。
查看賬單,對賬單中的異常點進行分析和最佳化,請參見成本追蹤。
進一步最佳化計算成本和提高資源利用效率,請參見計算成本最佳化。