理解成為

最近寫個(gè)Presto的UDF,發(fā)現(xiàn)點(diǎn)比較有趣的地方.

因?yàn)楣δ苌闲枰\(yùn)行時(shí)訪問外部數(shù)據(jù),做個(gè)類似緩存的讀寫動(dòng)作.
所以形式上來說,這是一個(gè)帶狀態(tài)的函數(shù).

在SparkSQL里的話,這個(gè)處理比較簡(jiǎn)單.
由于整個(gè)execution pipeline是基于序列化的,所以只要能夠提供一個(gè)某種程度上determinist的初始狀態(tài),那么各個(gè)executor是可以有一致的表現(xiàn)的.

當(dāng)然,這個(gè)有一些前提.
主要是class版本的一致性.
這個(gè)主要是由于kyro本身的問題.

但總得來說是比較straightforward的.

但是presto不太一樣.
它采取了一些可能從設(shè)計(jì)上來說,比較學(xué)院派或者說不是那么實(shí)用主義的設(shè)計(jì).

從架構(gòu)上來說,它的udf主要通過一種plugin機(jī)制擴(kuò)展.
而為了比較好地.或者說便于udf的作者提供更flexible的實(shí)現(xiàn),所以采用了一個(gè)獨(dú)立的plug classloader的方式加載對(duì)應(yīng)的實(shí)現(xiàn).

這個(gè)從工程上來說,也是一個(gè)可能容易被選擇的方案.
因?yàn)槿趸藢?shí)現(xiàn)的庫(kù)版本依賴的約束,允許plugin作者選擇自己prefer的各種其他庫(kù).

這里它為了比較好地解決不同classloader同名類不兼容的問題,主要也是plugin spi類的可cast問題,在plugin classloader里面做了個(gè)白名單機(jī)制.
是的SPI的類從同一個(gè)classloader加載,避免隔離機(jī)制造成的互相不兼容.

不過這里形式上也提供了一種調(diào)用主classloader的實(shí)現(xiàn)的一些方式.
畢竟已知的白名單類可以得到app classloader,自然也就有辦法使用到所有的類.

不過這算classloader這種sandbox機(jī)制的某種特性吧.
畢竟形式上來說,這也是JVM的claasloader sanbox對(duì)立統(tǒng)一的一面.

而且整個(gè)udf runtime和execution pipeline的執(zhí)行某種程度也是依靠這種leakage來實(shí)現(xiàn)的.

因?yàn)樗幌馭parkSQL是依靠序列化傳遞函數(shù)實(shí)現(xiàn),而是靠比較輕量的某種執(zhí)行計(jì)劃描述在個(gè)節(jié)點(diǎn)節(jié)點(diǎn)重構(gòu)調(diào)用鏈的.
所以形式上來說,可能各個(gè)節(jié)點(diǎn)運(yùn)行的版本并不嚴(yán)格一致.
因?yàn)閖ar包可能不一樣.

不過這在實(shí)際場(chǎng)景下可能不是一個(gè)大問題.
尤其如果是用容器方式運(yùn)行的話.

這里的主要問題在于它對(duì)udf函數(shù)的初始化處理.

大體上來說,是比較標(biāo)準(zhǔn)的依賴注入的思路.
掃描類的annotation來生產(chǎn)udf的描述信息和運(yùn)行時(shí)綁定方式.

這里比較tricky的是對(duì)udf的綁定是通過method handler實(shí)現(xiàn)的.

這個(gè)大概率可能是一種基于性能借口的炫技.

因?yàn)閷?duì)于一個(gè)generic的udf來說,入?yún)?shù)量和返回值是不確定的.

像SparkSQL就索性采用了一個(gè)透明的類Object/Any方式.
好處是SPI接口簡(jiǎn)單.
壞處也顯而易見,不能簡(jiǎn)單地知道入?yún)⒊鰠⒌念愋?

Presto形式上來說也可以采用這樣的方式.
而且實(shí)際上來說,如果采用這種方式的話,可能更有利于vectorize.
畢竟本身就內(nèi)部pipeline的傳遞的page/block就是某種batch data.

但顯然作者沒有采用這種實(shí)用主義的手法.
而是用了method handler以便能夠在udf的聲明上就清晰函數(shù)的定義.
把heavy lifting的事情放在method handler的參數(shù)綁定上.

甚至炫技的地方還不單在這種類curry的functional化上.
甚至還允許函數(shù)聲明根據(jù)參數(shù)類型做specialize,一定程度上做著類似template specialized的事情.
對(duì)特定類型的參數(shù)可以提供統(tǒng)一但優(yōu)化的實(shí)現(xiàn)和聲明.

這個(gè)炫技本身倒沒什么.
只不過從實(shí)際實(shí)現(xiàn)上來說,它引入了一個(gè)隱性約束.
就是這個(gè)函數(shù)的implementaion部分必須是static的.

因?yàn)檫@樣才可能在早期綁定確定的method handler.

雖然看實(shí)現(xiàn)上也支持非static method的綁定.
但對(duì)于hosting class的constructor有一定的限制.

而且本質(zhì)上來說,這個(gè)hosting instance也是once bounded的.
作用上就是一個(gè)static的singleton.

所以沒辦法從這個(gè)角度去讓某個(gè)udf調(diào)用帶有狀態(tài).

于是要帶狀態(tài)的話,一種思路是把狀態(tài)相關(guān)的部分作為參數(shù)inject到udf的入?yún)⒗?

這個(gè)的問題主要是在explain語句的時(shí)候,會(huì)有一些可能或干擾或敏感或意義不明的部分讓人confuse,或者說知道了不必知道的實(shí)現(xiàn)細(xì)節(jié).

當(dāng)然,這個(gè)通過override某些函數(shù)是可以redacted的.
但是總的來說,不是很便利.

另外一點(diǎn)就是late binding的問題.

有時(shí)候udf入?yún)⒌囊徊糠植⒉恍枰⒓磂valuate,而是需要在udf實(shí)現(xiàn)里根據(jù)情況決定是否做eval.

這個(gè)在SparkSQL因?yàn)閑xpression是可以自定義哪些需要eval的.
不管是eval模式還是codegen模式,自主性都比較flexible.

而presto的因?yàn)榍懊鎚ethod handler的那些magic,基本上generate出來的bytecode都是實(shí)際eval出來的結(jié)果再入?yún)⒌?
也就是說,在這方面的可控性并不如SparkSQL.

如果將這些late binding需求的部分作為原始string傳入,再再udf里編譯的話,有幾個(gè)問題.

一個(gè)是這個(gè)compile流程不算友好.
如果bytecode模式的話,需要處理多個(gè)class loader的問題.

因?yàn)榍懊嬉舱f了,plugin本身的class是在一個(gè)半isolated的class loader.
而bytecode generate因?yàn)槭褂昧薽ethod handler這個(gè)builtin class,所以形式上可以不管實(shí)現(xiàn),在一個(gè)獨(dú)立的mini classloader就可以完成code gen.
而卒后執(zhí)行的時(shí)候又是在main classloader里執(zhí)行.

所以在運(yùn)行時(shí),或者說函數(shù)的調(diào)用邏輯里需要畢竟明確的知道會(huì)觸發(fā)class loading的點(diǎn)和確保bind了正確的classloader.

而如果是interpreter模式的話同樣有類似的問題.

并且除了這個(gè)問題之外,還有怎么構(gòu)造compiler/interpreter也是跟問題.

因?yàn)檎麄€(gè)presto是按照guice的inejctor構(gòu)造的.
而plugin的接口和loading方式又缺乏引入injector從而獲得相關(guān)依賴服務(wù)構(gòu)造對(duì)應(yīng)compiler/interpretor的方式,所以實(shí)際上也不太可行.
即使是強(qiáng)行構(gòu)造了一些等價(jià)接口的話,在覆蓋率上也是有所差異的.

這使得即使構(gòu)造成功,可能運(yùn)行時(shí)的表現(xiàn)也不盡如人意.

再就是即使構(gòu)造成功,處于性能考慮,如何對(duì)整個(gè)compile結(jié)果緩存也是個(gè)問題.

因?yàn)榍懊嫠f的method invoke是無狀態(tài)的.

不過因?yàn)楸旧肀磉_(dá)式是string形態(tài),所以緩存這方面倒相對(duì)來說更直接一點(diǎn).

而如果不走這種compile/interpretor方式的話,presto倒是提供了一種間接或者說直接late binding的方式.
那就是presto sql的lambda expression.

這個(gè)倒是解決了按需evaluate的問題.
并且不需要復(fù)雜的compile/interpreter,甚至于都不需要做method handler的緩存.
意味本身就已經(jīng)是bindable了的,在函數(shù)聲明里就是個(gè)通用的明確的functional interface.

但這里也回到了緩存狀態(tài)的問題上.

針對(duì)一個(gè)udf的調(diào)用,形式上是根據(jù)某些特定的入?yún)⑸梢粋€(gè)確定的cache key以幫助查找的.
但是如果同時(shí)有l(wèi)ate binding的需求的話,那么因?yàn)閒unctional interface本身不太cache sensitive.

也就說,很難說兩個(gè)functional interface是否具有相同的邏輯,從而增加了緩存的難度.

而這里因?yàn)榍懊嬲f的bytecode codegen的classloader是個(gè)不關(guān)心/aware plugin classloader的.
所以實(shí)際上它接收的functional interface只能是jdk builtin的interface.
無法通過擴(kuò)展的方式,使得運(yùn)行時(shí)能從這個(gè)入?yún)⒗锏玫揭恍┍容^顯著的信息.

能想到的方式只能是walkaroud地再inject一個(gè)足夠distinguish的比如原始expr的string參數(shù),同時(shí)想辦法在explain等場(chǎng)景里給它屏蔽/不顯示干擾.

總的來說,核心問題可能還是在于Presto整體設(shè)計(jì)有些學(xué)院派.
不像SparkSQL更多的是秉持某種能用就行.

雖然比較諷刺的是Presto本身是Facebook這種喊出move fast break things的工程實(shí)用主義的公司搞出來的.

不過想想自己也是.

曾幾何時(shí)倒也是看不太起Spark+Scala這種到處又不是不能用哲學(xué)組合的產(chǎn)品.

現(xiàn)在卻是走到了理解成為的階段.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容