PostgreSQL 10.0 preview 性能增強 - 推出JIT開發框架(朝著HTAP邁進)
標簽
PostgreSQL , 10.0 , HTAP , 動態編譯 , JIT , LLVM , 表達式 , 函數跳轉
背景
數據庫發展了幾十年,出現了很多產品,有麵向OLTP(在線事務處理)的,有麵向OLAP(在線分析)的。
雖然兩個場景各有需求特色,但是企業需要為其需求買單,因為目前很少有產品可以同時滿足在線處理和在線分析的需求。
比如一家企業,通常都有業務的波峰波穀,比如遊戲業務,通常波穀可能是在淩晨,因為大多數人都睡了。而波峰可能出現在每天的工作閑時、遊戲運營時段、節假日等。
為了分析業務數據,企業通常需要構建一個數據倉庫,將業務數據從OLTP數據庫寫入OLAP數據庫,這裏存在幾個問題
1. 需要額外的成本
2. 數據存在延時
如果OLTP數據庫產品本身已經具備了OLAP的能力,以上兩個問題將迎刃而解。
例如在業務高峰期,使用備庫進行查詢類分析,在業務低峰,使用主庫計算並產生報表。
那麼什麼樣的數據庫能同時具備OLTP和OLAP的能力呢?(現在這種場景稱之為HTAP,混合業務)
PostgreSQL在OLTP領域的功能、性能、穩定性、可靠性久經考驗
PostgreSQL在OLAP方麵有哪些強項呢?
首先我們了解一下OLAP數據庫應有的能力。
OLAP數據庫應具備的能力
1. 列存儲
通常OLAP是基於列的分析,列存儲可以減少分析時需要掃描的數據量,提高性能。
同時列存儲還有幾個好處,壓縮比高,可以更好的支持向量計算。
2. JIT
通常OLAP分析可能涉及較多的聚合,條件過濾等,這些操作在數據庫內核中是一些對應的函數,可能每一行都會流經這些函數,當分析的數據量很龐大時,需要大量的函數調用,stack的進出交換,內存拷貝等。
動態編譯,可以減少函數調用,解決以上瓶頸。
3. 向量計算
利用CPU的cache和向量化執行指令,可以批量執行,從而提高大量數據處理的速度,往往有10X的性能提升。
4. 多核並行
現在的主機,CPU核數都很多,上百個核的機器已經越來越普遍,而OLAP並不是高並發的應用,他們往往並發低,但是單條QUERY需要的運算量非常龐大。
所以能充分利用多核處理單條QUERY,可以很好的滿足OLAP的需求。
5. sharding+MPP
單機的能力畢竟是有限,所以數據分片,MPP(節點間自由協作,數據自動重分布),可以有效的解決單機的性能瓶頸。
6. GPU\FPGA擴展計算能力
CPU擅長的是任務型的運算,而GPU\FPGA可以更好的滿足計算密集型的事務,單機的數據處理能力可以通過GPU或FPGA計算得到提升。
7. OLAP函數和語法、窗口語法、擴展分析語言等
OLAP分析可能需要用到複雜的函數、聚合、窗口。甚至需要自己動手編寫一些複雜的計算函數,或者分析函數。
所以對數據庫內置的函數是否完備、支持的SQL語法是否完備息息相關。同時對數據庫的編程能力,功能擴展能力也息息相關。
8. 塊索引
通常數據倉庫的數據量級非常龐大,往往B-TREE索引在體量、檢索速度、建立速度可能不能滿足業務需求。
塊級索引,是以塊為單位,記錄塊內元數據的索引(最大值、最小值、空值、COUTN、SUM、相關性等)可以很好的支撐OLAP的場景。
9. 壓縮
壓縮也是OLAP的核心訴求之一,當你的曆史數據越來越龐大時,你可能就知道壓縮的好處了。
主要是節約成本,(CPU加解壓)時間換空間。
10. 數據攝取、導入能力
對於OLAP係統,數據的攝取能力很重要,比如兼容更多的數據類型,可以訪問更多的數據來源。
導入能力,考驗數據導入的速度,加載數據的接口是否豐富。
PostgreSQL HTAP之路
PostgreSQL 從2010年就開始布局HTAP,正在穩步的引領開源數據庫占領HTAP高地。 從FDW,列存儲,多核並行,GPU插件,sharding,向量計算,JIT等方麵均有體現。
《分析加速引擎黑科技 - LLVM、列存、多核並行、算子複用 大聯姻 - 一起來開啟PostgreSQL的百寶箱》
《PostgreSQL 10.0 preview 功能增強 - OLAP增強 向量聚集索引(列存儲擴展)》
《PostgreSQL 9.6 引領開源數據庫攻克多核並行計算難題》
《PostgreSQL 聚集存儲 與 BRIN索引 - 高並發行為、軌跡類大吞吐數據查詢場景解說》
《PostgreSQL 9.5 new feature - Support GROUPING SETS, CUBE and ROLLUP.》
https://www.postgresql.org/docs/9.6/static/tutorial-window.html
10.0 布局JIT框架
PostgreSQL 10.0把SQL執行的框架從樹形遞歸調用時改成了非遞歸的opcode驅動模式,可以大幅降低處理大批量數據,大批表達式時的函數調用、內存拷貝等開銷。
同時這種執行模式使得JIT的開發變得更加輕鬆,朝著HTAP邁進。
Faster expression evaluation and targetlist projection.
This replaces the old, recursive tree-walk based evaluation, with
non-recursive, opcode dispatch based, expression evaluation.
Projection is now implemented as part of expression evaluation.
This both leads to significant performance improvements, and makes
future just-in-time compilation of expressions easier.
The speed gains primarily come from:
- non-recursive implementation reduces stack usage / overhead
- simple sub-expressions are implemented with a single jump, without
function calls
- sharing some state between different sub-expressions
- reduced amount of indirect/hard to predict memory accesses by laying
out operation metadata sequentially; including the avoidance of
nearly all of the previously used linked lists
- more code has been moved to expression initialization, avoiding
constant re-checks at evaluation time
Future just-in-time compilation (JIT) has become easier, as
demonstrated by released patches intended to be merged in a later
release, for primarily two reasons: Firstly, due to a stricter split
between expression initialization and evaluation, less code has to be
handled by the JIT. Secondly, due to the non-recursive nature of the
generated "instructions", less performance-critical code-paths can
easily be shared between interpreted and compiled evaluation.
The new framework allows for significant future optimizations. E.g.:
- basic infrastructure for to later reduce the per executor-startup
overhead of expression evaluation, by caching state in prepared
statements. That'd be helpful in OLTPish scenarios where
initialization overhead is measurable.
- optimizing the generated "code". A number of proposals for potential
work has already been made.
- optimizing the interpreter. Similarly a number of proposals have
been made here too.
The move of logic into the expression initialization step leads to some
backward-incompatible changes:
- Function permission checks are now done during expression
initialization, whereas previously they were done during
execution. In edge cases this can lead to errors being raised that
previously wouldn't have been, e.g. a NULL array being coerced to a
different array type previously didn't perform checks.
- The set of domain constraints to be checked, is now evaluated once
during expression initialization, previously it was re-built
every time a domain check was evaluated. For normal queries this
doesn't change much, but e.g. for plpgsql functions, which caches
ExprStates, the old set could stick around longer. The behavior
around might still change.
Author: Andres Freund, with significant changes by Tom Lane,
changes by Heikki Linnakangas
Reviewed-By: Tom Lane, Heikki Linnakangas
Discussion: https://postgr.es/m/20161206034955.bh33paeralxbtluv@alap3.anarazel.de
參考
https://postgr.es/m/20161206034955.bh33paeralxbtluv@alap3.anarazel.de
https://postgr.es/m/26088.1490315792@sss.pgh.pa.us
最後更新:2017-04-01 17:00:39