Tuesday, December 25, 2018

Erlang 的啟發

(1) Remote procedure call 不是一個好的 idea
What seems like a good, simple idea on the surface -- hiding networks and messages behind a more familiar application-development idiom -- often causes far more harm than good.

Request-response is a network-level message exchange pattern, whereas RPC is an application-level abstraction intended.

Equating RPC with synchronous messaging means the later wrongly suffers the same criticisms as RPC.

RPC 主要有兩個問題:
(1) 無法提供合理的抽象層來處理 network failure
(2) 讓工程師沒有注意到透過網路呼叫程序的代價,容易設計出 response time 過長的系統。

Erlang 沒有提供「遠端程序呼叫」。Erlang 只有提供 message passing 和 failure handling 的 primitives ,即 send/receive 和 link 。

(2) Scalable, fault-tolerant, hot-upgradable 的共同點
一個系統同時若要同時滿足上述三件事,其實需要實作下列的 primitives
(a) detect failure
(b) move state from one node to another

When designing a system for fail-over, scalability, dynamic code upgrade we have to think about the following:

What information do I need to recover from a failure?
How can we replicate the information we need to recover from a failure?
How can we mask failures/code_upgrades/scaling operations from the clients

(3) Cooperative multitasking is fragile
協作式多工是「脆弱」的設計。( 但是,cooperative 的效能通常會比 preemptive multitasking 好一些。)

The weakness of the cooperative model is its fragility in server settings. If one of the tasks in the task queue monopolizes the CPU, hangs or blocks, then the impact is worse throughput, higher latency, or a deadlocked server. The fragility has to be avoided in large-scale systems, so the Erlang runtime is built preemptively. The normal code is “instrumented” such that any call which may block automatically puts that process to sleep, and switches in the next one on the CPU.

(4) Stacking Theory for Systems Design
對 service 的可用性,定義出數個不同的 mode of operation ,比方說:
VM 有 run 起來,這就是 level 0 。
database connection 有通,這就是 level 1 。

如果某些條件達成,系統就會轉換到高一級的 operational level 。如果 error 發生,就自動轉換低一級的 operational level 。

By "stacking" service, we can go back to level 0 and start best-effort transitions to level 1 again. This structure is a ratchet-mechanism in Erlang systems: once at a higher level, we continue operating there. Errors lead to the fault-tolerance handling acting upon the system. It moves our operating level down — by resetting and restarting the affected processes — and continues operation at the lower level.

用了這樣子多個 operational mode 的設計,系統較容易得到「容錯」的特性。

A system assuming the presence of other systems has an unwritten dependency chain. You have to boot your production systems in a certain order or things will not work. This is often bounds for trouble.

(5) Processes are the units of error encapsulation --- errors occurring in a process will not affect other processes in the system. We call this property strong isolation.

關於如何將軟體模塊化總是有許多不同的爭議。編譯器設計者通常把硬體想象成完美的,並且主張由通過靜態編譯時型別檢查來提供良好的隔離性。與編譯器設計者們相反,作業系統設計者們則主張運行期檢查,並主張將進程做為保護單位與故障單位

Friday, December 21, 2018

why does 12 factor recommend not to daemonize processes?

最近在研究 Erlang/Elixir ,又發現了以前自己總是不小心忽略的東西 supervisor 。

然後就查了一下 12 factor app。為什麼會想查 12 factor app ?印象中,我好像不知道在哪裡看過一篇文章,提到,許多 Erlang 的設計,後來都被 12 factor app 所採用,意思大概是說,Erlang 真的就是分散式系統設計的先趨。

結果,12 factor app 裡頭也有一條 rule  有提到 process supervisor 的相關概念。但是,我覺得說明最清楚的,反而是 stackoverflow 上,對下列問題的解答:
「為什麼 12 factor app 不推荐我們 daemonize processes?」

答案是:
重點不在於不要使用 daemonize processes 這個解法。重點在於: process management framework 其實 *nix 早就有了,沒事不要自己做。要善用現有的工具,這個才是 DevOps 的精神。



Friday, November 9, 2018

Web scraping notes

最近公司的工作裡有一部分是做 web scraping 。要做的事,大致可以分成 2 個步驟:
a.  login/authentication
b.  extract data from web page

其中,登入的部分,客戶的網站大概可以分成兩大類別:
(1) token based authentication
最新一代的標準是 JSON Web token 。
(2) session based authentication
這種通常都需要去處理 cookie 才有辦法登入

客戶的網站有的是 backend rendering ,有的是 frontend rendering 。如果是前者,通常 backend API 傳回的資料是 JSON ,算是最容易處理。如果是後者,backend API 傳回的資料則是 HTML/XML 的格式

要從 HTML/XML 中提出資料,通常可以考慮三種做法:
(1) Regular expression  => 直接硬做字串的比對
(2) XPath                       => 可以看懂 XML tree ,透過資料的結構關系去定位資料的位置。
(3) CSS selector            => 表達能力比 XPath 略弱一些,但是使用比較廣泛。


Wednesday, November 7, 2018

Effective REPL-driven Clojure

Effective REPL-driven Clojure 這篇文章裡,介紹了一系列 REPL-driven 的好用技巧:

1. Evaluate forms frequently
每次如果修改了某個 forms  ,尤其是它是 def 的 form ,就可以考慮把它重新求值一下。 求值的作用可以想象成是一種存檔。

2. Start stateful things in REPL before you do anythings else
使用 mount 或是 component

3. Use comment forms to document your experiments
在 REPL 要做的求值實驗,可以考慮用 (comment ... ) 記錄在程式裡,方便日後理解。

4. Use def for debugging
REPL 可以對定義好的函數輕易地求值。然而,函數內部的 form 要如何 debug 呢? 函數內部的 form 往往會依賴於函數的 arguments 。很簡單,就是用 def 把函數的 arguments 指定一個固定值,這樣子就可以 debug 了。記得 git commit 之前要把這種 debugging 的 def 移除。範例

5. Unmap namespace during experimentation
ns-unaliasns-unmap 可以視為 def 和 require 的反運算。

6. Re-open libraries for exploration
在 require 某個 library 之後,使用 in-ns 可以進入該 library 的 namespace 裡,並且在這邊重新定義變數,以達成除錯或是取得執行資訊的目的。(monkey-patch)

7. Pretty Print
使用 clojure.pprint 這個函式庫來做輸出

Friday, October 12, 2018

first class

現在一些比較新的 programming language 有時候都會在語言裡加入新的 first class element 。對我來講,這個詞彙 first class ,總是讓我覺得有「理解的困難」,到底什麼才叫 first class 呢?

有一回,我終於在 SICP Distilled 看到定義了。

In general, programming languages impose restrictions on the ways in which computational elements can be manipulated. Elements with the fewest restrictions are said to have first-class status, an idea originally due to Christopher Strachey
Some of the “rights and privileges” of first-class elements are that they may be:
  • Named by variables
  • Passed as arguments to functions
  • Returned as the results of functions
  • Included in data structures
Lisps, unlike other common programming languages, awards functions full first-class status. This poses challenges for efficient implementation, but the resulting gain in expressive power is enormous.


Software Engineering Task Orders


Stage 1
(1) end-to-end test
(3) CI/CD -> cloud formation
(4) linter

Stage 2
(1) unit test
(2) cloudwatch filtering/alarm

Stage 3
(1) integration test
(2) profiling
(3) automatic document generation


開發軟體總是需要做一些軟體工程的事情,不過,通常總是有一些壓力讓我沒有辦法事事做到完美。於是我自己弄了一個排序。如果有時間的話,先做哪些、後做哪些。 stage 1 是我覺得最重要、最該先做的。 stage 2 是我覺得可以拖一下的。stage 3 是行有餘力才做的。

為什麼 stage 2 可以拖一下呢?
(*) 以 unit test 來講,如果已經有 end to end test 並且加上可以用環境變數關閉的 debugging log ,某種程度來講,也可以把該測的程式碼測到乾淨。沒有 unit test 似乎也沒有這麼嚴重了,因為已經有了替代方案了。

(*) cloudwatch 的 filtering/alarm 的話,這個通常是設計用來抓出某些本來預期不會發生的錯誤、或是超大的流量發生的情況。但是,專案剛開始發展的時候,流量有時候也不大,所以可以拖一下。在 Erlang  的設計哲學裡,這部分設計應該算是所謂的「故障曝光性質」 (failure status property) 與「持久存儲性質」 (stable storage property)  ,算是直接要設計在 programming language layer 的東西,而不是要設計在 infrastructure layer 的東西。

為什麼 stage 3 可以拖更晚呢?
(*) 專案在「探索期」的時候,對資料庫的查詢,可能會快速地一直更換,太早加上 integration test ,有時候也太費力了,因為加上去了,總是要維護吧?
(*) 如果專案沒有效能瓶頸,profiling 沒有做也還好吧?
(*) 如果專案還沒有長太大,沒有自動產生的文件,似乎也不太嚴重。

Friday, October 5, 2018

cooperative multitasking

過去初學 event-driven 或是 cooperative multitasking 時,沒有特別想清楚 。等到後來,多寫了一些 Nodejs 的 async/await ,還有 Clojure 的 go-block 之後,才覺得這個 cooperative multitasking 真的大有文章在。

這邊有一個參考資料,比較了兩種 cooperative multitasking 不同的實作方式:

實作一: 讓語言本身的排程器可以被 I/O 觸發,在 I/O blocking 的同時,自動做出 context-swtich 。這是 golang 的實作法。
實作二:讓 I/O 呼叫變成 non-blocking 的。這是 Nodejs, Python, Clojure 的實作法。

原文如下:
For cooperative concurrency to work well with I/O, the language should either make the scheduler aware of the I/O calls (to be able to switch to another context while blocking) or the I/O should be non-blocking. The former requires runtime support in the language, like Go; the latter is what programming environments like Python (with asyncio) and Node.js (with its fully non-blocking standard library) do. The same applies to Clojure, where core.async is just a library without actual runtime support.

在 golang ,如果要應用語言本身提供的同步特性,常見的寫法是:
將有 I/O 呼叫的函數,放在一個被 go 關鍵字修飾的函數裡頭 (即放在一個獨立的 goroutine 裡),並且透過 channel ,將 I/O 呼叫的結果送出來。