Software entities (class, modules, functions, etc.) should be open for extension, but closed for modification. Junior programmers create simple solutions to simple problems. Senior programmers create complex solutions to complex problems. Great programmers find simple solutions to complex problems. 註1:本部落格的範例程式碼在 2015 年以前的文章中,大多是以全型空白做縮排。如需服用,請自行用文字編輯器的取代功能把全型空白取代成半型空白。
- Bertrand Meyer
- Charles Connell
註2:本部落格的內容授權請參閱部落格底部的授權宣告。
2020年6月15日 星期一
2020年4月19日 星期日
Replications
本篇為 Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems [1] 的讀書筆記。
Replication 指的是在分散式系統中將一份資料複製並存放在多個節點上,以帶來幾個可能的好處:
- 將資料複製到距離使用者較近的節點,以減少 latency
- 提高 availability
- 增加讀取的 throughput
一般討論分散式系統的 replication 時,關注的困難點都是如何處理資料的變化。因為若資料不會變化,那只要整個複製貼上就完工了,沒什麼困難點。但若資料會變化,那麼就會需要考慮多個節點間要如何控管資料的變化了。
2020年4月8日 星期三
attribute 的記憶體需求
Vespa 的欄位如果 index-type 被宣告為 attribute 的話,就會直接被放在記憶體上,因此實務上會需要估計到底需要多少記憶體才能撐住所有的資料的問題。
2020年4月4日 星期六
2020年3月29日 星期日
Reactive Programming(三):Project Reactor
本系列的文章為 Modern Java in Action / Project Reactor 的讀書筆記,因此內容可能會有點跳躍。
2020年3月18日 星期三
在 Spock Framework 測試包含 parallel stream 的標的時卡住
紀錄一下最近解了幾天的問題,程式碼大體上類似 [1] 裡面的 MyRecordProcessor 和 MyRecordProcessorTest 那樣。因為本來是要建置環境比較簡單的測試來重現問題,而原本的問題是發生在開發 Kinesis 的 RecordProcessor 中遇到的,因此 [1] 的範例也沿用了這個關係。不過實際上最後發現的原因跟 Kinesis 沒有關係就是了。
問題的徵兆是,當要測試的標的內部改寫成有 parallel 的狀況時,Spock 這邊執行測試就可能會出現測試整個卡在某個地方的狀況。卡住時會完全沒有回應,但也沒有拋出錯誤或者停止執行。這個問題好像遇到的人並不是那麼多,所以可以找到的資料也非常少。不過主要參考了 [2-3],最後確認的原因是因為測試時用了 Spy。Spy 好像存在某些限制(在 [3] 的回答中有提到,雖然他沒提到到底是什麼限制),實際上似乎導致在 parallel 的狀況時,Spy 的 dynamic proxy 似乎有時會生效、但有時會無效。所以最後解決方法就是~不要用 Spy。
參考資料
2020年3月1日 星期日
Vespa 入門(一):在本機建立 containerized Vespa
因為在公司持續地在用 Vespa,所以打算寫個系列文章慢慢紀錄 Vespa 的一些使用、調校經驗。(雖然說過去寫的系列文章大多…寫到第二或第三篇就………。)
本篇會紀錄在本機啟動 Vespa 的 container 的方法,通常這麼做可以在開發階段用來測試 Vespa 的語法。可以參考 Vespa 官方文件 [1]。
環境準備
依照 [1] 的描述,Docker 環境最重要的就是需要有至少 6GB 的記憶體。而在我的環境中,我是在 Windows 10 上跑 Docker 的,因此至少要是個有支援 Hyper-V 的 Windows 版本,才能夠執行 Docker。
取得 Vespa 範例
首先,Vespa 有提供一些簡單的範例 Application,可以從以下的 Github 取得。裡面有很多資料夾,包含了數種在 Vespa 官方文件中會講到的例子。
git clone https://github.com/vespa-engine/sample-apps.git
這裡假設我把這個 repository 放在 G:\git 裡。
建立並啟動 Vespa container
要啟動 Vespa 之前,我們需要執行兩個動作。第一個是先啟動一個 Vespa 的 container,這個步驟只會準備好 Vespa 需要的執行環境而已。接著第二個是部署 Vespa Application,這個動作就像是在 RDBMS 裡執行 CREATE DATABASE 一樣,是讓 Vespa 準備好 schema 並且真的啟動伺服器。
docker run --detach --name sample-vespa --hostname vespa-container --privileged --volume g:\git\vespa-sample-apps:/vespa-sample-apps --publish 8080:8080 vespaengine/vespa docker exec sample-vespa bash -c 'curl -s --head http://localhost:19071/ApplicationStatus'
首先如前所說,我們先以 vespaengine/vespa 的 image 啟動一個 Vespa container,這裡我們把這個 container 命名為 sample-vespa,並且把資料夾 g:\git\vespa-sample-apps 掛到 container 的 /vespa-sample-apps 路徑上,方便之後我們在這個 container 中部署與啟動 Vespa。
container 開好以後,可以透過第二行指令來確認 Vespa 的狀態。不過因為 container 雖然綁了 port,但卻還沒有啟動任何服務,因此我們要確認狀態只能利用在 container 裡執行 curl 指令來確認。如果正常的話會獲得類似這樣的回覆:
HTTP/1.1 200 OK Date: Sun, 01 Mar 2020 11:31:41 GMT Content-Type: application/json Content-Length: 9232
接著就是要在裡面部署和啟動指定的 Vespa Application 了。
docker exec sample-vespa bash -c '/opt/vespa/bin/vespa-deploy prepare /vespa-sample-apps/album-recommendation-selfhosted/src/main/application/ && /opt/vespa/bin/vespa-deploy activate' curl -s --head http://localhost:8080/ApplicationStatus
這邊我們先部署 album-recommentation-selfhosted 這個 Vespa Application。因為 Vespa 的 Search Definition 是放在 src/main/application 裡的,因此在部署的時候要指定路徑是這個資料夾。第一個指令會獲得類似這樣的反應,表示已經順利地啟用 Vespa Application。
Uploading application '/vespa-sample-apps/album-recommendation-selfhosted/src/main/application/' using http://localhost:19071/application/v2/tenant/default/session Session 3 for tenant 'default' created. Preparing session 3 using http://localhost:19071/application/v2/tenant/default/session/3/prepared WARNING: Host named 'vespa-container' may not receive any config since it is not a canonical hostname. Disregard this warning when testing in a Docker container. Session 3 for tenant 'default' prepared. Activating session 3 using http://localhost:19071/application/v2/tenant/default/session/3/active Session 3 for tenant 'default' activated. Checksum: 71cf99ec40a0a08c8edbd1aa3cee03ed Timestamp: 1583062394458 Generation: 3
再使用第二個指令確認 Application 狀態時,可以獲得:
HTTP/1.1 200 OK Date: Sun, 01 Mar 2020 11:34:39 GMT Content-Type: application/json Transfer-Encoding: chunked
參考資料
2019年12月29日 星期日
2019年11月6日 星期三
2019年10月11日 星期五
為何在 Dockerfile 中使用 tail 是 anti-pattern
最近因為一個蠻莫名的問題,花了點時間在研究依靠 tail 來把 log 輸出到 docker log driver,然後發現其實這個作法算是一種 anti-pattern。目前認知到的問題有兩個:
1. tail 可能無法如預期地運作
在 stackoverflow 上,有好幾篇問題都做了類似的事情:
FROM debian:latest RUN touch /var/log/mylog.log CMD ["tail", "-F", "/var/log/mylog.log"]
在上面的 Dockerfile 中,先 touch 產生一個 log 檔,接著就一直 tail –F 去看那個 log 檔,感覺上好像很普通。但實際執行起來,會發現 docker logs 裡面什麼也沒有,tail 指令好像根本沒有正常在工作。而這個問題的根本原因是在於 docker 中間有 image layer,每一行 RUN 指令都會覆蓋上一層 image layer,因此實際上 touch 指令和 tail 指令在 docker 中是存取到不同的 inode。建議的作法應該要改為:
CMD ["sh", "-c", "touch /var/log/mylog.log && tail -f /var/log/mylog.log"]
也就是說,產生檔案以及監看檔案應該要在同一個 CMD 裡進行,這樣才能避免被 image layer 隔開而看不到預期的結果。
2. 若 container 的 main process 並非是主要的運行指令,container 可能無法對某些錯誤做正確的反應
這個問題主要是考量到能不能善用 container 本身的特性。在透過 Dockerfile 執行程序時,container 對程序的期待是如果程序執行完了,container 就會被認為不再需要了,於是 container 就會自動關閉。這個特性也可以用在如果程序執行有問題導致非預期地中斷,container 也一樣會自動關閉。此時如果外頭有 load balancer 的話,load balancer 就可以自動做一些反應,例如因為 container 數量減少了,因此自動做 scale-out 重新生出一個 container。但如果 container 的 main process 並非是在執行主要程序,而是在執行 tail 的話,那有可能會遭遇到主要程序出問題結束了,但 tail 沒有問題,這時就可能存在失敗的服務一直繼續留在那邊無法正確被關閉。當然這個動作也可以透過例如另外做一套 health check 的流程來處理,不過如果能夠很簡單地就利用 container 的天性來達成這個目的,也是可以考慮利用一下~。