Presto常見問題優化


轉載:http://www.voidcn.com/article/p-kmaltben-bse.html

presto參數優化

查詢速度慢, 如何優化?

解決方法1: 避免單節點處理

雖然Presto是分布式查詢引擎, 但是一些操作是必須在單節點中處理的. 例如:

  • count(distinct x)

    • 考慮使用approx_distinct(x)代替
    • 但是需要注意這個函數有個大約在2.3%的標准誤差, 如果需要精確統計的情況, 請繞道.
  • UNION

    UNION有個功能是: 如果兩條記錄一樣, 會只保留一條記錄(去重).
    • 如果不考慮去重的情況, 請使用UNION ALL
  • ORDER BY

    Presto對數據排序是作用在單節點上的
    • 如果要排序的數據量超過百萬行, 要謹慎考慮. 如果非要排序,盡量將排序的字段減少些.

解決方法2: 減少表掃描的范圍

通過添加條件達到減少表掃描的范圍.

也可以考慮將大數據量的表, 水平查分, 通過查不同的表分區達到效果.

解決方法3: 避免使用 SELECT * FROM

要明確寫出所有要訪問的列, 能加快速度.比如:你去拿幾個東西過來/你去把1,2,3拿過來一個道理
例如

SELECT * FROM my_table

改成:

SELECT id, name, address FROM my_table

解決方法4: 將幾個LIKE語句放到函數regexp_like()

Presto的查詢優化器不能改善許多LIKE語句使用的地方, 導致這樣的語句查詢速度慢.

例如

SELECT ... FROM access WHERE method LIKE '%GET%' OR method LIKE '%POST%' OR method LIKE '%PUT%' OR method LIKE '%DELETE%'

上面的語句能用regexp_like函數優化成一句

SELECT ... FROM access WHERE regexp_like(method, 'GET|POST|PUT|DELETE')

如何優化JOIN性能?

盡量讓JOIN的條件簡單,最好是ON后面的比較表達式兩邊必涉及計算。

例如

SELECT a.date, b.name FROM left_table a JOIN right_table b ON a.date = CAST((b.year * 10000 + b.month * 100 + b.day) as VARCHAR)

上面的SQL語句的JOIN性能不高,因為JION條件包含了表達式計算。我們可以通過子查詢的形式來優化上面的語句。

SELECT a.date, b.name FROM left_table a JOIN ( SELECT CAST((b.year * 10000 + b.month * 100 + b.day) as VARCHAR) date, # generate join key name FROM right_table ) b ON a.date = b.date # Simple equi-join

上面的語句,就是直接比較兩個VARCHAR的值,這樣會比比較一個VARCHAR和一個表達式結果的性能高。

我們還能繼續優化,使用Presto的WITH語句進行子查詢。

WITH b AS (
  SELECT CAST((b.year * 10000 + b.month * 100 + b.day) as VARCHAR) date, # generate join key name FROM right_table ) SELECT a.date, b.name FROM left_table a JOIN b ON a.date = b.date

如何使查詢簡單化

解決方法1: 使用WITH語句

如果你的查詢語句非常復雜或者有多層嵌套的子查詢,請試着用WITH語句將子查詢分離出來。

例如

SELECT a, b, c FROM ( SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl GROUP BY a ) tbl_alias

可以被重寫為下面的形式

WITH tbl_alias AS (SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl GROUP BY a) SELECT a, b, c FROM tbl_alias

同樣,也可以將各個步驟的子查詢通過WITH語句羅列出來,子查詢之間用“,”分割。

WITH tbl1 AS (SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl GROUP BY a), tbl2 AS (SELECT a, AVG(d) AS d FROM another_tbl GROUP BY a) SELECT tbl1.*, tbl2.* FROM tbl1 JOIN tbl2 ON tbl1.a = tbl2.a

解決方法2:在CREATE TABLE語句中使用WITH語句

如果CREATE TABLE語句的查詢部分很復雜或者潛逃了多層子查詢,就需要考慮用WITH語句

例如:

CREATE TABLE tbl_new AS WITH tbl_alias AS (SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl1) SELECT a, b, c FROM tbl_alias
CREATE TABLE tbl_new AS WITH tbl_alias1 AS (SELECT a, MAX(b) AS b, MIN(c) AS c FROM tbl1), tbl_alias2 AS (SELECT a, AVG(d) AS d FROM tbl2) SELECT tbl_alias1.*, tbl2_alias.* FROM tbl_alias1 JOIN tbl_alias2 ON tbl_alias1.a = tbl_alias2.a

解決方法3:用GROUP BY語句時,GROUP BY的目標可用數字代替

在Presto SQL中,GROUP BY語句需要與SELECT語句中的表達式保持一致,不然會提示語法錯誤。

例如:

SELECT TD_TIME_FORMAT(time, 'yyyy-MM-dd HH', 'PDT') hour, count(*) cnt FROM my_table GROUP BY TD_TIME_FORMAT(time, 'yyyy-MM-dd HH', 'PDT')

上面的SQL語句的GROUP BY部分可以用GROUP BY 1,2,3 ...來表示

SELECT TD_TIME_FORMAT(time, 'yyyy-MM-dd HH', 'PDT') hour, count(*) cnt FROM my_table GROUP BY 1
Note: 這些數字是從1開始的,有別於程序要思維從0開始。

Exceeded max (local) memory 錯誤

Presto會跟蹤每個查詢的內存使用情況.可用內存的多少是根據你的查詢計划變動的,所以在大多數情況下可以從寫查詢語句來達到優化內存使用的目的.

下面列出來的就是內存密集型的語句塊:

  • district
  • UNION
  • ORDER BY
  • GROUP BY (許多字段的情況)
  • joins (各種JOIN)

解決方法1: 盡量少使用distinct

distinct 會排除所有不唯一的行.下面的例子就是檢查你的數據表中是否包含了相同的數據行(c1,c2,c3)

SELECT distinct c1, c2, c3 FROM my_table

上面的操作會存儲一整字段c1,c2和c3到presto的單個工作節點的內存, 然后檢查(c1,c2,c3)的唯一性. 隨着字段的增多以及字段數據量的增大,所需要的內存也會直線上升.

所以, 去掉查詢語句中的distinct關鍵字, 或者只在子查詢(有有限少量字段的情況下)使用.

解決方法2: 用approx_distinct(x)代替count(distinct x)

NOTE: approx_distinct(x)會返回一個正確的近似值, 如果只是需要看一個大概的趨勢,可以考慮.

解決方法3: 盡量用UNION ALL代替UNION

和distinct的原因類似, UNION有去重的功能, 所以會引發內存使用的問題.

如果你只是拼接兩個或者多個SQL查詢的結果, 考慮用UNION ALL

解決方法4: 盡量避免ORDER BY

SELECT c1, c2 FROM my_table ORDER BY c1

Presto在排序的時候啟用的是單一節點進行工作, 所以整個數據需要在單節點內存限制的范圍內, 超過這個內存限制就會報錯.

如果你需要排序的數據在一個小的量級, 用ORDER BY沒有問題; 如果需要排序的數據在GB的級別,需要考慮其他的解決方案.

例如: 大量級的數據排序可以考慮結合HIVE和presto. 首先, 用Presto將大量的數據存儲到一個臨時表中,然后用HIVE取對數據排序.

解決方法5: 減少GROUP BY的字段

SELECT avg(c1), min_by(c2, time), max(c3), count(c4), ... FROM my_table GROUP BY c1, c2, c3, c4, ...

減少GROUP BY語句后面的排序一句字段的數量能減少內存的使用.

解決方法6:用大表取JOIN小表

下面這種用小數據表去JOIN大數據表的查詢會極度消耗內存.

SELECT * FROM small_table, large_table WHERE small_table.id = large_table.id

Presto 會默認執行廣播式的JOIN操作,它會將左表拆分到幾個工作節點上, 然后發送整個右表分別到已拆分好的處理左表的工作節點上. 如果右表非常大就會超出工作節點的內存限制,進而出錯.
所以需要用大表JOIN小表

SELECT * FROM large_table, small_table WHERE large_table.id = small_table.id

如果左表和右表都比較大怎么辦?

  1. 修改配置distributed-joins-enabled (presto version >=0.196)
  2. 在每次查詢開始使用distributed_join的session選項
-- set session distributed_join = 'true'
SELECT * FROM large_table, large_table1 WHERE large_table1.id = large_table.id
核心點就是使用distributed join. Presto的這種配置類型會將左表和右表同時以join key的hash value為分區字段進行分區. 所以即使右表也是大表,也會被拆分.

缺點是會增加很多網絡數據傳輸, 所以會比broadcast join的效率慢.

查詢生成的大量數據優化的問題

Presto用JOSN text的形式保存數據。如果查詢出來的數據大於100G,Presto將傳輸大於100G的JSON text來保存查詢結果。所以,即使查詢處理即將完成,輸出這么大的JOSN text也會消耗很長時間。

解決方法1:不要用==SELECT *==

解決方法2:用result_output_redirect='true' 注釋

在查詢語句前添加注釋(result_output_redirect='true'),能讓查詢更快些。

-- set session result_output_redirect='true' select a, b, c, d FROM my_table

上面的語句能讓Presto用並行的方式生成查詢結果,能跳過在Presto協調器進行JSON轉換的過程。

Note: 但是,如果使用了ORDER BY語句,這個魔術注釋將被忽略。

Presto查詢優化

數據存儲

合理設置分區

與Hive類似,Presto會根據元信息讀取分區數據,合理的分區能減少Presto數據讀取量,提升查詢性能。

使用列式存儲

Presto對ORC文件讀取做了特定優化,因此在Hive中創建Presto使用的表時,建議采用ORC格式存儲。相對於Parquet,Presto對ORC支持更好。

使用壓縮

數據壓縮可以減少節點間數據傳輸對IO帶寬壓力,對於即席查詢需要快速解壓,建議采用snappy壓縮

預先排序

對於已經排序的數據,在查詢的數據過濾階段,ORC格式支持跳過讀取不必要的數據。比如對於經常需要過濾的字段可以預先排序。

SQL優化

  • 只選擇使用必要的字段: 由於采用列式存儲,選擇需要的字段可加快字段的讀取、減少數據量。避免采用*讀取所有字段
  • 過濾條件必須加上分區字段
  • Group By語句優化: 合理安排Group by語句中字段順序對性能有一定提升。將Group By語句中字段按照每個字段distinct數據多少進行降序排列, 減少GROUP BY語句后面的排序一句字段的數量能減少內存的使用.
  • Order by時使用Limit, 盡量避免ORDER BY: Order by需要掃描數據到單個worker節點進行排序,導致單個worker需要大量內存
  • 使用近似聚合函數: 對於允許有少量誤差的查詢場景,使用這些函數對查詢性能有大幅提升。比如使用approx_distinct() 函數比Count(distinct x)有大概2.3%的誤差
  • 用regexp_like代替多個like語句: Presto查詢優化器沒有對多個like語句進行優化,使用regexp_like對性能有較大提升
  • 使用Join語句時將大表放在左邊: Presto中join的默認算法是broadcast join,即將join左邊的表分割到多個worker,然后將join右邊的表數據整個復制一份發送到每個worker進行計算。如果右邊的表數據量太大,則可能會報內存溢出錯誤。
  • 使用Rank函數代替row_number函數來獲取Top N
  • UNION ALL 代替 UNION :不用去重
  • 使用WITH語句: 查詢語句非常復雜或者有多層嵌套的子查詢,請試着用WITH語句將子查詢分離出來


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM