全部產品
Search
文件中心

MaxCompute:計算成本控制

更新時間:Jun 19, 2024

當您發現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時,對應的費用也會大幅升高。

相關文檔