2019年7月28日 星期日

Vespa 的資料與索引型態

Vespa 裡面支援了許多資料型態(data type),包括 int、long、byte、double、boolean、string、struct(物件)、collection(例如 array、weighted set)等等,可以參考官方文件 [1],這些資料型態最主要的影響在於存放時所消耗的空間大小。而關於使用時的使用模式,則主要是來自於設定的索引型態(index type)來決定。

索引型態

索引型態主要分為 attribute 和 index 兩種。概略來說,可以直接把它們看成是「存放在記憶體」和「存放在硬碟」的差別。在 Vespa 的設計裡,好像基本上會假定字串以外的型態都放在 attribute 比較好的樣子,因為如果要以資料欄位作為像是排序的依據的話,資料欄位必須要是 attribute index 才行。

attribute

attribute 的欄位都會被存放在記憶體裡,意味著其實如果可以的話,最好不要把長度無法預測的資料欄位設定為 attribute,因為這樣會導致身為稀缺資源的記憶體被大量耗用在存放這些字串。不過實際運作時,因為 OS 會有虛擬記憶體的概念,因此不一定會真的完全保證 attribute 的欄位就一定是被放在記憶體上,如果想要確保這件事,首先就得確保 OS 不會使用到虛擬記憶體。

設定為 attribute 的欄位,就能夠執行以下的這些操作:

  • grouping
  • sorting
  • word-match search
  • numerical search
  • prefix search
  • ranking functions
  • document summaries
  • document reference

在對 attribute 的欄位做搜尋的時候,原則上 Vespa 只會做線性搜尋,只是因為搜尋時是對記憶體內的資料做,所以一般狀況下效率不會太差。但當有必要的時候,對 attribute 欄位設定 fast-search 可能是蠻重要的事情,這會讓 Vespa 對該欄位建立 B-tree 索引,就像在 RDBMS 上對欄位建索引一樣。到這裡,某種程度上可以感覺到 Vespa 對這種基本型態的處理其實算是比較粗糙的,效能很大程度是直接依賴「資料放在記憶體上」的這件事。所以如果沒有要做什麼比較複雜的排序或計算之類的話,基本資料的儲存並不是 Vespa 的強項,把資料放在 RDBMS 裡可能可以獲得相差不遠的效率但大幅下降的維運成本。

index

index 的欄位是存放在硬碟中的 [2-3],代表資料被定義為 index 的話,儲存成本相較於 attribute 會比較低,但同時存取速度也會比較慢。index 欄位只能用在字串上(這又突顯了 Vespa 對於基本型態的處理比較粗糙的事實),除了存放位置以外,index 跟 attribute 最大的不同就是 index 的欄位都會做 normalization 和 tokenization [3]。換言之,index 主要(唯一?)的使用情境是在於做各種字串處理或者字串搜尋。

Search Definition 的定義

在 Search Definition 上定義資料的索引型態時,attribute 和 index 並非是只能設定其中一個,其實可以同時設定兩個,只是同時設定時有個有點有趣的小細節。因為索引型態是透過 pipe 連接起來的,pipe 連接的意思就是會依序執行,所以它的結果會很像是使用 Java 8 的 stream API 那樣。舉例來說,如果我對某個欄位設定 indexing: index | attribute,這表示的是這個欄位的資料會先進行 index,在這個過程中會被 tokenize 等等,接著被 tokenized 的資料再送去 attribute 做索引!換言之,在一般狀況下如果我們想要同時設定 attribute 跟 index,通常我們應該會想設定 indexing: attribute | index,而不太會是反過來放的狀況。同時,當我們設定 indexing: attribute | index 時,Vespa 的搜尋會去搜尋 index 而不會搜尋 attribute [4]。(不過這樣的話那幹麻要支援同時寫 attribute 和 index 呀….?)

參考資料
  1. Search Definition Reference: field
  2. Proton: index
  3. Search Definition Reference: index
  4. Search Definition Reference: indexing
  5. Performance And Tuning: Document Attributes
  6. Performance And Tuning: Attribute Memory Usage

2019年7月21日 星期日

dependencyManagement 和 parent

最近專案上遇到一個奇怪的狀況,概略來說就是 @NotEmpty(javax.validation.constraints.NotEmpty)這個驗證無效。後來同事研究了好一段時間,發現問題好像是出在 dependency conflict。然後就做了一些 dependencyManagement 和 parent 的小研究~不過因為沒有很仔細在研究,所以也只是粗略紀錄一下的程度。

  1. dependencyManagement 指的是事先宣告 dependency 的參考,然後當後面在 dependencies 裡面有使用到宣告的 dependency 時,就會引用事先宣告的參考版本。換句話說,如果 dependencyManagement 裡面宣告了 dependencies 裡面沒有的東西,那就不會造成什麼影響。
  2. parent 是強制引入所有宣告在 parent 裡面的 dependency。因此不管專案自己的 dependencies 裡面有沒有使用到,parent 宣告的 dependency 都會被引入。
  3. parent 裡宣告的東西好像是強制引入,就算在 depepdencies 裡面想要覆寫版本也是沒有用的。不過聽同事說,如果是間接引用的狀況,例如 parent 裡的 dependency A 引用的 dependency B,就可以透過 maven 本身的特性覆蓋或變更~這點是還沒有確實實驗過。
參考資料
  1. Maven实战(六)--- dependencies与dependencyManagement的区别
  2. maven 中parent 与 dependencyManagement 
  3. What is the difference between “pom” type dependency with scope “import” and without “import”?
  4. maven如何不继承parent里面的部分依赖
  5. How to exclude artifacts inherited from a parent POM? 
  6. Spring Boot 不使用默认的 parent,改用自己的项目的 parent

2019年7月18日 星期四

Spring WebFlux:Reactive Programming(持續更新中…)

最近開始認真在讀 Spring WebFlux 的文件,所以稍微做一點筆記~。

何謂 Reactive?

在 Spring 的定義來說,Reactive 包含兩件重要的事情:「Reacting」以及「Non-blocking back pressure」。在 Reacting 的部份,Spring 的描述如下:

The term, “reactive,” refers to programming models that are built around reacting to change — network components reacting to I/O events, UI controllers reacting to mouse events, and others. In that sense, non-blocking is reactive, because, instead of being blocked, we are now in the mode of reacting to notifications as operations complete or data becomes available.

重點在於最後面的描述,我們現在處於「回應通知」的模式,在操作完成或者是資料可以存取的時候才採取反應。而對於 Non-blocking back pressure 的部份,則是以下的描述:

In synchronous, imperative code, blocking calls serve as a natural form of back pressure that forces the caller to wait. In non-blocking code, it becomes important to control the rate of events so that a fast producer does not overwhelm its destination.

這個在我以前一開始聽到的時候,其實不太能理解它的重要性,不過聽過很多次以後,慢慢地有了一點感覺~XD。在同步操作中,因為呼叫者會需要等待,所以呼叫者通常可以很明確地知道接收者什麼狀況開始受不了了;但在非同步的模式中,呼叫者跟接收者並沒有直接性的連接,所以基本上容易落入完全不知道對方狀況的情境,然後一股腦地一直發送事件,讓接收者被塞爆~。而在 Reactive Programming 的概念中,應該要包含接收者必須要具備 back pressure 的能力,有點像是在必要時壓制呼叫者,叫它停下來這樣。

Spring WebFlux 對於 Reactive 的支援

Spring 的體系中,現在分成兩大類:Spring MVC 和 Spring WebFlux,如下圖所示。兩邊都同時有支援 Annotated Controller 的模式,也就是可以用類似於過去的 Spring MVC 的方式使用 Spring WebFlux。不過如果要使用類似 Java 8 Lambda 的那種 Functional Programming 的 model 的話,就得採用 Functional Endpoints 的方式來開發了。


參考資料
  1. Web on Reactive Stack

2019年7月14日 星期日

Groovy 的 RESTClient 相關

忽略 HTTPS 的伺服器端檢查

restClient.ignoreSSLIssues()
回覆的 status code > 399 時,不要拋出 exception REST

Client restClient = new RESTClient()
restClient.handler.failure = restClient.handler.success
參考資料
  1. Is there an easier way to tell HTTPBuilder to ignore an invalid cert?
  2. Can't use ignoreSSLIssues in HttpBuilder version 0.7.1
  3. Can I override RESTClient default “HttpResponseException” response to >399 Return Codes?

2019年7月7日 星期日

Java 的 SSL 驗證

一般在 Java 要存取 HTTPS 的服務的時候,大多數狀況因為常見的 CA 應該都已經列在 JRE 的 cacerts(CA Certification 檔)裡了,所以通常不會遇到什麼問題。不過如果遇到要存取的是像是公司內部的服務,公司自己有自己的 CA 時,可能就得做點事情來讓 HTTPS 的 SSL/TLS 能夠正常運作。

概念上來說,當一個客戶端要存取 HTTPS 的服務時,客戶端只會相信它相信的對象。而要如何決定要相信誰?方法就是客戶端自己會存放一個信任的 CA 的清單,如果發現 HTTPS 服務的憑證是由信任的 CA 所發放的、並且憑證所保護的對象也確實是現在存取的對象,那客戶端就會相信這個 HTTPS 服務。以 Java 的狀況來說,客戶端指的是 JRE,JRE 內建的信任 CA 的清單會是 $JAVA_HOME/lib/security/cacerts 這個檔案。如果沒有特別指定的話,Java 程式在存取 HTTPS 時,進行 SSL/TLS 三方握手時就會以 JRE 內建的 cacerts 來決定對方是否能夠信任。

不過現實上,CA 還會分成 Root Certificate 和 Intermediate Certificate,例如 [1] 的範例一樣:

這裡 Entrust.net Secure Server CA 是 Root Certificate,DigiCert High Assurance EV Root CA 和 DigiCert High Assurance CA-3 則都是 Intermediate Certificate,最後 *.atlassian.com 這個網域的憑證是由 DigiCert High Assurance CA-3 所簽發的。客戶端若要驗證 *.atlassian.com 的憑證,它必須要信任上面的所有 CA(也稱為 Certificate Chain)才行。

2019年7月3日 星期三

在 Maven 專案中自行指定原始碼的位置

在我們團隊的習慣,會在 Maven 專案裡面使用 Groovy 來寫測試,並且在寫測試時會用 Cucumber 來做整合測試。其中整合測試也許是為了想要跟單元測試分開,所以不會擺在一般 Maven 預設的路徑 src/test 裡面,而是會放在 src/integration-test 裡面,這樣的狀況會導致 IDE 無法正確找到原始碼,Maven 的 dependency 設定也會出現問題。

舉例來說,在我的案例裡,因為 Cucumber 只有測試階段才會使用,因此很自然我會在 Maven 上做這樣的 dependency 設定:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>4.3.1</version>
    <scope>test</scope>
</dependency>

這邊的 scope 會特地設定成 test,表示 Cucumber 的套件只有在測試期間才會被載入。接著我如果直接上把 src/integration-test/groovy 加入 build path,會因為 Cucumber 只會在測試階段被引用的關係,導致 IDE 發出編譯錯誤的訊息。根本來說,問題是在於我必須要好好地告訴 IDE 或者 Maven 說,src/integration-test/groovy 這個資料夾應該要被視為是測試的資料夾。

所幸,Maven 本身有方法可以自行指定資料夾的功能,也就是利用 build-helper-maven-plugin 這個 plugin。這個 plugin 的功能主要就是讓我們可以告訴 Maven 說哪個資料夾要看成是 source directory、哪個資料夾要看成是 test directory 等等。

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>${maven-build-helper-plugin.version}</version>
            <executions>
                <execution>
                    <id>add-test-sources</id>
                    <phase>validate</phase>
                    <goals>
                        <goal>add-test-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>src/test/groovy</source>
                            <source>src/test/java</source>
                            <source>src/integration-test/groovy</source>
                            <source>src/integration-test/java</source>
                        </sources>
                    </configuration>
                </execution>
                <execution>
                    <id>add-test-resources</id>
                    <phase>generate-test-resources</phase>
                    <goals>
                        <goal>add-test-resource</goal>
                    </goals>
                    <configuration>
                        <resources>
                            <resource>
                                <directory>src/integration-test/resources</directory>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

可以注意到,在上述的 POM 當中,我設定了 src/integration-test/javasrc/integration-test/groovy 兩個資料夾都是 test-source 的 source,而 src/integration-test/resources 則是 test-resource 的 resource。如此一來,我就可以在 eclipse 裡面直接依靠 Maven 來幫我設定專案的原始碼資料夾了。

參考資料
  1. build-helper-maven-plugin
  2. Maven插件之build-helper-maven-plugin