Saturday, December 24, 2016

加速 jvm 的 start up time

最近因為個人嗜好,研究了一下 clojure 語言。既然開始動手研究,自然而然,會想實驗,如果…用 clojure 來做 shell script 會如何。然後,就立刻發現了,clojure 在 shell 下啟動,哪怕是一個 hello world 程式,啟動時間就需要 2 秒以上。

time /opt/test.clj hello world real 0m2.684s user 0m2.239s sys 0m0.186s


哇,這真是太扯了,怎麼會慢到這種程度?於是我就開始研究幾個問題:
(1) JVM 是不是真的很慢?
(2) 有沒有辦法來改進這件事?

第一個問題的答案滿曲折的,因為網路上也是各家有各家的說法,而 benchmark 本身就是一個研究的題目,我也不打算做太多嚴謹的驗証,最後我的結論是:JVM 是夠快的 virtual machine ,效能並不比 native code 差太多,幾乎是可以忽略的程度, 它就只有啟動的時候比較慢而已。很多 scripting languages ,像 Ruby, Python 會考慮編譯成 JAVA bytecode ,就是為了效能考慮。

第二個答案,我試了兩個解法:
解法 1 -- jamVM ( 原理就是:把大的 openjdk 的 JVM 換成小的 jamVM )

Let’s update our clojure executable /usr/bin/clojure:
#!/bin/sh

exec java -jamvm -jar /opt/clojure.jar "$@"
Let’s try it out:
time /opt/test.clj
hello world

real  0m0.866s
user  0m0.764s
sys   0m0.076s

解法 2 -- Drip (原理:It keeps a fresh JVM spun up in reserve with the correct classpath and other JVM options so you can quickly connect and use it when needed, then throw it away. Drip hashes the JVM options and stores information about how to connect to the JVM in a directory with the hash value as its name. )





Wednesday, December 21, 2016

gitbook 與 gh-pages

最近因為要做給北京快網運維看的教材,為了讓中國也能閱讀,我用了 gitbook 。其實也就是一種比 wiki 再更簡單的 blog 而已。然而,好景不常,沒有多久,快網的人就跟我講,我發表的 gitbook 速度太慢了。他們沒有辦法閱讀。

於是我考慮,換成用 github.io 的 gh-pages 來放我的 gitbook 。本來我一直被 gitbook 的選項誤導,以為我只要讓 gitbook 的內容和我的 github 帳號同步 (sync) 就可以了,後來實驗之後,才發現,完全不是這麼回事。正確的作法是這樣子:

1. 先把 gitbook 裡的所有檔案 md 檔、圖檔,都用 git 抓到本地端的資料夾,例如 ~/book/。
2. 在本地端安裝應用程式
     npm install gitbook-cli -g
3. 將 md 檔,輸出成為靜態網站,指定 ~/book 為「圖書目錄」,指定 ~/book/docs 為「輸出目錄」
    gitbook build ~/book  ~/book/docs
4. 將 ~/book 資料夾內的內容,同步到自己 github 帳號下的 book repository 的 master branch
5. 在自己的 github 帳號做 gh-pages 的設定,讓 gh-pages 的 source 指向 master branch 裡的 docs 資料夾。


 

Thursday, December 1, 2016

bloom filter, bitmap data structure, elasticsearch

因為公司工作的關系,最近在研究 elasticsearch 。很快地,我就一頭栽進了探討 elasticsearch 與 mysql 不同的地方。就用一個常見 SQL 運算來說明好了。
SELECT AVG(age) FROM login WHERE site = 'zzz' AND host = 'kkk';
(a) where 子句的部分,在一些文章中,常稱為 Filter 操作
filter 操作在 elasticsearch 可以做相當的最佳化,因為 elasticsearch 可以使用 bitmap 來做 AND, OR 的運算。而 mysql 只能用 btree 提高查詢效率。當 where 子句的條件有多個 AND 或是 OR 的運算時,btree 比 bitmap 差許多,因為 btree 的索引無法快速地做 AND 或是 OR 的運算。
(b) AVG(age) 函數的部分,在一些文章中,常稱為 Aggregate 操作 
aggregate 操作在 elasticsearch 也會相當的好,因為 elasticsearch 有一個 doc_value 屬性,會讓它成為 column-oriented database。相比 row-oriented 資料庫, column-oriented database 存取的資料量會少非常多。因為只有 aggregate 函數作用的「欄」 (column) 會被存取,同一「列」(row) 的其它「欄」 (columns) 並不會被存取。

然後,我回想起了 bitmap data structure 也在其它地方出現過了:
(1) bloom filter
用法也是差不多,但是多了一個 hash 的運算。將元素的值透過 hash 運算,變成「索引值」 (index) 。就是元素如果存在的話,就會在對應索引值的位元標為 1 ,不存在的話,標記為 0 。
(2) 「編程珠璣」 (programming pearl) 的第一章。講如何用極少的記憶體,排序超大的檔案。由於使用 bitmap 這種資料結構,可以將 counting sort 所需要的資料結構完整地放入主記憶體,效能可以大幅提高。

Wednesday, November 23, 2016

golang 啟動作業系統上的 process ,刪除時,連同 children processes 都一併清除

過去我在處理 golang 在作業系統上呼叫 process 的問題,是利用 stackoverflow 的範例程式碼。stackoverflow 的範例有考慮到 process 可能會執行太久,所以有處理 timeout 的問題,但是,少考慮到了一個問題: child process 和 grand child process 的問題。

當 golang 呼叫的外部 process 因為 timeout 的因素,而被 kill 的時候,如果只用 process id 去 kill ,而這個外部 process 已經有啟動了 child process 的話,就會造成它的 child process 都變成 orphan process 的問題。因為這些 child process 並不會因為 parent process 被 kill 而自動結束。

有考慮上述問題的有效作法,啟動外部的 process 時,要設定一個新的 process session id。如此,讓新增的 process 與 child process, grand child process 都使用新的 process session id 。當 timeout 發生的時候,對 process id 取負值後發送 kill signal 就可以將這些外部的 process 一次全部清除完畢。


Monday, November 21, 2016

influxdb vs Star Schema

工作上會用到 influxdb ,最初就是把文件看一看,然後寫成心得。

使用influxdb的重要觀念 

(a) 可以把 influxdb 類比成一般的資料庫
influxdb
一般的DB
measurement
table
timerow
fields, tagscolumn
field 和tags的差異: field是required, tag是optional。field沒有建立index。tag會自動建立index。
如何決定,某一個值應該使用field或是tag來儲存?
usage
field
tag
case1 if you plan to use them with GROUP BY()
case2if you plan to use them with an InfluxQL function 
case3if you need them to be something other than a string 

然而,過了一陣子之後,我卻重新思考了這個問題。問題思考的起始點是這樣子:「如果我要用 mysql 這類的 relational database 來做 time series database 的用途,我的 schema 該如何設計呢?」
查了一些網站之後,隨即發現,原來我思考的這個問題,根本就是 time series database 設計的重要問題之一,連維基百科的條目,都有提到如何用 relational database 來模擬 time series database 。而這一類的「時間序列資料」的特性就是「多維度」。多維度的資料如果要求要高效能做 OLAP = On-Line Analytical Processing ,最合用的 database schema 就是 Star Schema 。
理解了適合 time series database 的 schema 是 star schema 之後,要來設計 influxdb 的 schema ,似乎也就更清楚了一些。

Wednesday, November 16, 2016

Restful API design

在公司最近進行的項目,是 API 的設計。網路上已經有許多 Restful API best practice 的文章了。所以這邊我寫的是個人親手踩過的坑。

(1) Gzip compression support
http 要支援 Gzip 的話,在公司的架構算是一件容易的事,因為真實的 API server 前,還有一台 nginx 在做 reverse proxy 。所以,只需要設定 nginx ,讓 nginx 可以做 gzip 就可以了。並不需要從 API server 的源頭端改下去。
gzip on;
gzip_types    application/json;


(2) 送出的資料量如果大的話,要記得做 pagination 。最好的作法應該使用定義於 RFC 5988 的 Link header ,因為這樣子就不必將 link header 的資訊放在 body 裡頭。

(3) 如果 API 的參數有時間的參數,考慮使用 Unix time 格式或是 ISO-8601。Unix time 是我本來就知道的。 ISO-8601 也是標準格式,我則是查了一段時間才搞懂。










後記:
讀過了 Restful API best practices 之後,我認為其中最重要的精神應該還是不要重造輪子。能利用 http 既有 header 來做的功能,就儘量利用 http 來做。如此才可以將複雜度(complexity)隱藏在 http 傳輸協定裡。比方說:
1  資料壓縮                   → http 的 header  => Accept-Encoding: gzip
2  認証                           → http digest authentication
3  合理使用量               → http rate limit
4  分頁(pagination)        → http link header
5  加密                           → https
6  CRUD 不同的操作   → http method : put, delete, get, post
7  caching                       → ETag, Last-Modified
8  exception handling     →  http status code

Monday, November 14, 2016

寫 python 用的環境配置

大概是因為前陣子寫 golang 時,受到 golang 的啟發後,再也回不去了。最近又開始寫一些 python ,馬上就覺得好像少了一些什麼東西?

仔細一想,原來少了跟 vim 搭配的 style checker plugin 。於是我立刻找了兩套來安裝。兩套都用看看,才知道哪一套比較順手。一套是 autopep8 對應的 vim plugin,另一套是 flake8 對應的 vim plugin 。前者檢查略鬆。後者檢查比較緊,但是只差一些些而已。

另外,還有一個很重要的,是 vim plugin 無法解決的。變數、類別取名的規範。這個只能靠頭腦去記了。

Guidelines derived from Guido's Recommendations
TypePublicInternal
Packageslower_with_under
Moduleslower_with_under_lower_with_under
ClassesCapWords_CapWords
ExceptionsCapWords
Functionslower_with_under()_lower_with_under()
Global/Class ConstantsCAPS_WITH_UNDER_CAPS_WITH_UNDER
Global/Class Variableslower_with_under_lower_with_under
Instance Variableslower_with_under_lower_with_under (protected) or __lower_with_under (private)
Method Nameslower_with_under()_lower_with_under() (protected) or __lower_with_under() (private)
Function/Method Parameterslower_with_under
Local Variableslower_with_under

Simplicity can yield functionality, robustness, speed and space

Simplicity can yield functionality, robustness, speed and space. 這段話出自 programming pearls 一書。很奇怪,我學程式設計也滿久了,漸漸才理解程式設計最核心的問題,就是把本來複雜的問題加以化簡。可以舉的例子太多了,用一個困擾我許久的問題來解釋這個概念。

以前我要做網站時,就會遇到疑問,到底要用:
(1) drupal, joomla, wordpress 等 CMS 的解法
(2) RoR, Django, laravel 等 framework 的解法
(3) 全手刻後端 + javascript 前端

而我以前待過的公司一連兩間都是採用第三種解法。其中最主要的原因之一,多少是因為精通 framework 的工程師不是那麼好找。沒有信心之下,大家還是決定先直接手刻後端的程式算了。總不能為了還不清楚會如何發展的規格,冒然引進一大堆,工程師自己都無法理解的 middle ware 。以前我覺得好像這樣子不是很好的解法,總覺得這樣子的決定,似乎是在工程師本身對各種工具掌握度不足而做的選擇。

後來,又有一些新的名詞冒了出來:
(1) API centric design: 後端只提供 Restful API。前端完全用 javascript 來做。
(2) micro services: 後端不要做成一隻程式,而是做成好幾隻小小的程式。每一個程式有各自的功能,彼此之間用 RPC/MQ 來溝通。
(3) Headless Drupal: 後端用 Drupal 來做,但是只提供 Restful API 給前端,前端還是用 javascript 來做。

看到這些新的名詞逐漸流行,我想應該也是很多人也跟我有類似的看法。需要解決的網站開發問題沒有那麼複雜,用簡單一點的 library, framework ,一切會單純許多。「簡單」才是最重要的事。

最近我因為工作需要,一連用了 nodejs 和 python 寫了兩個 API server。nodejs 我用的是 expressjs。python 我用的是 bottle 。應該都是最簡單的 framework 了。大概因為太簡單了,我寫完沒有多久,就完全忘記怎麼寫了。XD

Sunday, November 6, 2016

db patch

公司的系統一直在增加功能,資料庫也會不斷地更改 schema 。然而,已經存在生產環境 (production environment) 中的資料庫,卻不能直接套用新的 schema ,而是要用 patch 的方式,將舊的 schema 改成新的 schema 。於是開發的工作就會有一項,是要比較新舊的 schema 來寫出 transformation script 。

很幸運的是,這個似乎已經是前人研究過的問題了。有現成的工具可以使用。

(1) 安裝 mysqldiff
      $ sudo apt-get install mysql-utilities
   
(2) 如果因為遇到一些奇奇怪怪 python library 的問題導致裝不起來時,也可以用考慮使用 docker 來迴避安裝的困難。
      $ docker pull samfulton/mysql-utilities
      $ docker run -ti samfulton/mysql-utilities

(3) 使用的實例1:比較兩張資料表 
   $ mysqldiff --server1=root:password@10.20.30.40 \
   boss.contacts:coss.contacts \
   --difftype=sql -v

(4)使用的實例2:比較兩個完整的資料庫,且遇到錯誤不停止,繼續比較。
   $ mysqldiff --server1=root:password@10.20.30.40 \
   boss:coss \  
   --difftype=sql -v --force

Friday, November 4, 2016

excel 表格的半自動處理

朋友是業務,業務的工作之一,就是整理要給客戶的報價單。

下圖,就是朋友的工作。左邊四個欄位是原始資料。最右邊的output 欄位是他要透過查表,才能寫出公司的 part number 型號。

朋友詢問我,是否有辦法可以幫他寫個查表的程式,可以讓他不需要依賴人力來處理愚蠢的查表工作。我構思了一下,又覺得有困難。原因是,他公司的 excel 表格,合併儲存格的部分,相當不規律,我很難用 excel 轉 csv 的方式,做統一的處理。但是,仔細思考之後,發現其實可以用半自動化的方式來解這個問題。


怎樣算是半自動解呢?由於解除儲存格,將儲存格填滿的部分,對於業務來說,其實是很容易的,也不太耗時間,這個部分就還是由人手工來做。我只用程式處理最後的查表工作。

同時,仔細想想,用 script language 去處理 csv 檔,就會需要朋友手動做匯出。其實這個操作對一般人很不直覺。

既然需要的資料都已經在同一個 row 上了,就寫了一隻使用者自訂函數來處理。

在 John Bentley 所著 Programming Pearls 一書中,也有一段話寫到相似的解題想法 ---
Keeping track of our organization's budget looked difficult to me. Out of habit, I would have built a large program for the job, with a clunky user interface. The next programmer took a broader view, and implemented the program as a spreadsheet, supplemented by a few functions in Visual Basic. The interface was totally natural for the accounting people who were the main users. 


Thursday, November 3, 2016

[SQL] 對一張 table 的 multiple rows 做更新

公司的源碼裡,有一段程式碼,被公司的資深工程師挑出來說需要重構。本來的程式碼做的事情是: 「對一張 table 的 multiple rows 做更新的動作」。

原始的寫法如下:
1 用 ORM 將整張 table 讀入記憶體,每一 row 恰好對應一個物件。
2 跑迴圈,對物件做檢查,如果合乎條件,則做更新。

上述的寫法在資料量少的時候沒有影響,然而,在資料量大的時候,效能就會極差。因為多做了將整張 table 讀入記憶體的動作。比較好的重構版如下:

1    將要寫入 table 的資料,先寫入一張 temporary table。
例如:
CREATE TEMPORARY TABLE IF NOT EXISTS table2 AS (SELECT * FROM table1)

2.1 開啟 transaction
2.2 基於 temporary table 的值,用 join 操作來更新目的地的 table
例如:
UPDATE TABLE1
       JOIN TABLE2
       ON TABLE1.SUBST_ID = TABLE2.SERIAL_ID
SET    TABLE2.BRANCH_ID = TABLE1.CREATED_ID;
2.3 關閉 transaction

3  丟棄 temporary table      

新的寫法,是將要用來寫入 table 的值先寫入資料庫裡,再透過 SQL 的指令去做資料的更新。如此,大量減少了記憶體與資料庫之間的資料搬移。對於數據量大的情況,就會有效能的大幅改進。

註:新的寫法中,其實可以不用加上 transaction ,因為只有一個 update 的操作。然而考慮實務上的程式,常常會有超過一個 update 的操作。當兩個 update 操作有必要緊接著完成,不可以在中間被其它的 session 插入讀取的動作,就會需要 transaction 。

Monday, October 31, 2016

[open-falcon] Graph 裡的資料會被週期性的異常清除

公司的 open-falcon 監控系統,最近發生了一個神奇的 bug 。公司的 4000 多台機器裡,只有一台,它在 graph 資料庫裡的資料,會被週期性清空。所以無論何時去看它的資料,總是只看到最後的,例如 15 分鐘左右。而且神奇的事是: judge 又是正常的,也就是該台機器其實是有正常的送資料到 judge 和 graph 。但是,每隔一段時間, graph 就會發生異常地資料清除。

後來,我們索性用肉眼來檢查 graph 用來畫圖的資料。才發現,原來是 timestamp 的問題。出問題的機器,可能是在某個時間點,機器的時間被調到 2024 年,於是,它上報的資料,記錄的 timestamp 就是 2024 年的時間。而 open-falcon graph module 內部是使用 RRD 資料庫。於是,當 RRD 每次看到該筆錯誤資料時,它會看到一個來自未來的時間,而且會判斷之前的正確資料都太舊,可以拋棄了。這就是導致資料被異常清除的理由。

補救之道,就得針對該筆錯誤的資料做移除了。

Friday, October 28, 2016

解析 json API 輸出 - Fold knowledge into data so program logic can be stupid and robust.

最近重構了一段程式碼,用來解析 json API 的輸出。寫完之後,發覺這個重構,它為什麼可以讓程式碼變得比較容易看懂、比較好維護,因為它符合了一條 Unix 設計哲學。

Fold knowledge into data so program logic can be stupid and robust.

Data is more tractable than program logic. It follows that where you see a choice between complexity in data structures and complexity in code, choose the former. More: in evolving a design, you should actively seek ways to shift complexity from code to data.

中譯:
將知識塞進資料,好讓程式的邏輯變得單純卻強固。

資料比起程式邏輯更容易被理解。因此,當你發現可以選擇資料複雜性或是程式碼複雜性時,選擇前者。更進一步來說,在系統設計演進的過程,你應該積極地尋找方式,好讓複雜性從程式碼進入資料。


Friday, October 21, 2016

[open-falcon] 在 open-falcon 監控系統中看到 lambda-architecture 的影子


這 

個人的工作,是基於 open-falcon 發展適合公司內部使用的監控軟體,每次因為工作的需求,要在 open-falcon 上增加功能時,我就不由得開始思考,這樣子加功能好嗎?會不會不小心破壞了所謂的「概念一致性」(conceptual integrity)?那什麼是最重要的「概念」呢?哪些是可以妥協?哪些是不該妥協的?

網路上介紹 open-falcon 的文章裡,有一篇是講,編寫 open-falcon 的構思過程。我也提了,從抽象的層次看待 open-falcon ,它像是什麼。目的是希望提出抽象層次的觀點,講述其概念一致性。日後在思考增加新功能時,優先考慮不傷害概念一致性的解法。
左側兩張圖,是大數據計算常使用的 lambda-architecture 計算模型。概念是,大數據分析系統收集資料之後,將資料分成兩份,一條路是走 batch view ,另一條路是走 real time view。

其中,batch view 因為要用完整的資料做批次處理的關系,資料的處理會略有延遲。這段延遲的時間,如果還是有提供運算結果的需求,就由 real time view 的模組來提供資料。

這樣子處理大數據的模型,我彷彿也在 open-falcon 裡看到了「似曾相似的東西」。參考右圖。就是 graph 和 judge 。其中,graph 類似於 batch view 。一方面,graph 某種程度地可以保存一年的資料,也算是 immutable data。graph 的 batch view 是用 RRD tool 做的。對資料做了某些程度的篩選,特別適合用來畫圖。所以圖就是 open-falcon lambda architecture 的 batch view 或稱 precompute view 。

judge 處理 stream data ,而且會做「實時告警」,也就是一查到符合告警條件,就會立刻觸發。 judge 做的工作,是對不斷送進來的資料流做是否觸發告警條件的檢查。 judge 產生的告警,則可以視為是一種 real time view 或 incremental view 。

略有不同的地方,是大數據的 lambda architecture 其實暗示了 batch view 和 real time view 都通過 serving layer 來呈現,所以使用者不知道裡頭存在了這兩種運算模組,甚至感覺這是同樣的模組做出的運算結果。另一方面,操作 open-falcon 的使用者,其實也未必分得清楚,繪圖功能和告警功能是在兩個不同的模組獨立計算的。

Monday, October 17, 2016

網路品質量測問題 --- mean opinion score

公司的 NQM (network quality measurement) 小組,做的系統是網路品質量測系統。該系統的目標是要評估兩地的連線品質,例如:中國的某兩個相鄰省分的連線品質。測量的方式,就是在公司的主機上,送出大量 ping 封包,透過取得 ping 封包的往返時間 round trip time 和 掉包率 packet loss rate,以及往返時間的變化量 jitter 來做品質的客觀評估。

同事很快就把量測系統、資料庫、前端的介面都做好了。最後一個關卡,變成是使用者與介面的問題。於是問題出現了:「我們的軟體可以呈現這些透過 ping 量測出來的數據,但是,怎樣子的數值叫做好?怎樣子的數值叫做不好?」

一開始我的想法很單純,就做統計吧。有一整天的資料,很容易就可以算出,此時此刻的網路品質相對於一整天來講,是偏好、還是偏不好,是在一個標準差之外、還是兩個標準差之外?但是,後來想一想這個邏輯完全不通。因為使用者,也就是運維人員,他們需要的資料,並不是此時此刻網路品質相對於該兩點的一天的平均品質而言有多好。而是對於客戶來講,客戶真實的感受。比方說:客戶傳輸音訊、傳輸 http ,傳輸影片,是否會覺得不順暢?

後來,才發現這個「如何定義傳輸品質好壞」問題已經有人研究過了。有一個詞彙叫「平均意見分數」mean opinion value。也就是說,有做過實驗,讓許多不同的人在透過網路傳輸語音,讓一群人對不同的品質打出分數,於是這個 mean opinion value 就定義成了人對於聲音傳輸品質的主觀感受平均分數。MOS 的數值是從 5 分到 1 分, 5 分是完美。 1 分是無法通訊。

有了 mean opinion value (MOS) 之後,研究人員後來又發展了換算方式,可以先把量測出來的數值:round trip time, packet loss rate, jitter,透過公式算出 R factor ,再算出 mean opinion value 。我查到的計算公式如下:

' Take the average latency, add jitter, but double the impact to latency
' then add 10 for protocol latencies
EffectiveLatency = ( AverageLatency + Jitter * 2 + 10 )

' Implement a basic curve - deduct 4 for the R value at 160ms of latency
' (round trip).  Anything over that gets a much more aggressive deduction
if EffectiveLatency < 160 then
   R = 93.2 - (EffectiveLatency / 40)
else
   R = 93.2 - (EffectiveLatency - 120) / 10

' Now, let's deduct 2.5 R values per percentage of packet loss
R = R - (PacketLoss * 2.5)

' Convert the R into an MOS value.(this is a known formula)
MOS = 1 + (0.035) * R + (.000007) * R * (R-60) * (100-R)

Sunday, October 16, 2016

物件關係阻抗不匹配(object-relation impedance) vs 快速開發的選項 ORM 或是 mongodb

物件關系阻抗不匹配是指:記憶體中的資料結構,往往是多維度的、巢狀的,和關聯式資料庫中的二維表格,其實是不同的。程式設計人員總是要花費不少時間,才能將記憶體中的資料結構轉換成資料庫中的資料結構。

現代許多流行的程式語言( ruby, python 等)都有提供「物件關系對應」 ORM( object-relational mapping ),主要是用來簡化程式開發人員處理「物件關系阻抗不匹配」問題。然而, ORM 麻煩的地方在於,批評者指出,它是一個 反面模式( anti-pattern )。是反面模式的理由主要是因為 ORM 違反了物件導向程式設計的重要原則:「封裝」。它沒有沒有完整地將 SQL 的細節隱藏在物件中,導致了程式變得難以測試。 批評者則是認為,應該要用 SQL-speaking object 來取代 ORM。

這個用 SQL-speaking object 來取代 ORM 的作法,固然是相當成熟、穩健的作法,因為有完整地封裝 SQL ,妥善地處理 object-relation impedance 問題。問題是,其實勢必還是有許多使用者之所以想用 ORM 最根本的理由,是懶得寫這麼多程式碼! 太麻煩了,因為要快速開發的話,根本一開始連需求都還沒有完全想好。

既然 ORM 有難以測試的問題的話,那還是使用 mongodb 吧。當然,等程式發展到一段時間之後,還是有可能會需要對資料庫做重新設計,但想要快速開發、想要跳過處理 object-relation impedance 的苦工,也只有 ORM 或是乾脆不要使用 RDBMS 這兩大類的解法了。

Friday, October 14, 2016

[open-falcon] micro services 與 message queue

公司做的監控系統是基於 open-falcon 去開發的。open-falcon 裡用來讓 agent 這個 micro services 可以同時對數個資料庫送出「訊息」的模組叫 transfer 。而由於一些討論 micro services 的文章建議,如果 micro service 之間是做一對一的溝通適合用 RPC(remote procedure call) ,如果是 micro services 之間做一對多的溝通,則建議使用 message queue 。

於是,當我上回安裝過 rabbitMQ 之後,我就不自覺地去想,能不能用 rabbitMQ 來取代 transfer ? 如果用 rabbitMQ 來取代 transfer 之後,可以有什麼好處? 我在除錯 agent 的時候,常常需要做的一件事是,用 tcpdump 去看 RPC 裡傳輸的資料,透過這個動作才容易把錯誤的點加以定位。如果用了 rabbitMQ 的話,就可以有一個 web UI 介面,可以輕易地看到正在傳遞的「訊息」,而不需要 tcpdump 了。

常常嗆主管是資淺工程師的M大大聽完我的想法之後,終於按捺不住地來指導我了。他的指導主要有兩件事:
(1) 上述的需求是除錯(diagnostics)的問題。

既然如此,應該考慮從 log 的改進、或是自行設計更合用的 run time debug tool。引入一個有複雜功能 rabbitMQ 並不是用來解決「除錯」問題的好解法,更不是一種透過「改進系統的架構」來解決問題的解法。好的系統設計,確實可以減少錯誤。但是那個原因是在於「概念完整性conceptual integrity

註:Conceptual integrity is the quality of a system where all the concepts and their relationships with each other are applied in a consistent way throughout the system.

(2) 抽象化可以解決問題,例如修改的彈性、或是程式的簡潔,但是抽象化也要付出代價。

M大大說著就提到了他找工作時,被考官質問的許多問題。「我在找求職時,有時候會被一些考官問一些莫名其妙的問題。例如,『你的前公司為什麼不用XX技術?XX技術不是OO大公司在用嗎?』這是什麼鳥問題?問這類問題的面試官一整個很像外行人。」

流行的技術或是工具背後,總是有力推的廠商或是社群,這些推廣技術的人,永遠只會講技術有多好,功能有多強。然而,從系統設計者的角度,要考慮的問題則是 trade off 。工具的功能再強不是重點,重點是適不適合解決手上的問題,還有解決問題需要付出什麼代價。

Saturday, October 8, 2016

[open-falcon] 利用 influxdb 來做 rrdtool 繪圖相關問題的除錯

最近公司基於 open-falcon 布署的監控系統,發現了一個難以理解的問題:「速度上限 10Gbps 的網卡,被監控系統量測出了 5Tbps 的超高速。」這樣子的問題該怎麼處理呢?


首先想到的解決方案是先從原始資料找起,但是,open-falcon 記錄這些數值的原始資料,是利用 RRDtool 來記錄的。所以一旦事發超過 12 小時,可以拿到的資料的精準度就大幅下降了。

所幸,當初公司也有把 open-falcon 的 transfer 模組,與 influxdb 加以串接。於是,我就可以在  influxdb 裡,找到由機器送過去的原始資料。

由於這一回要除錯的是網卡速度,我們就必須對資料的類別加以著墨。由 transfer 送往 Graph 和 influxdb 的資料,型態有 gauge 和 counter 。其中, gauge 比較單純,就是量測的數值本身,直接就可以被使用者所理解,例如:現在有幾個 processes 。而 counter 數值則是「累計數值」。為什麼要使用 counter 呢? 比方說,網卡速度就會需要了。因為像 linux 系統,其實直接透過 /proc/net/dev 只能取得某一張網卡瞬間累計接收/送出的 bytes 數目,而無法直接取得網卡的瞬時速度。換言之,監控系統之所以可以顯示網卡的速度,那是監控系統已經透過 counter 的資料型態,將速度從累計數值推算出來了。

 很快地,我就在 influxdb 裡找到了原始的資料。由於是 counter 型別的累計數值,原始資料必然是穩定上昇的直線。

而計算出 5T 速度的時間區段,也正好是區線呈現斷斷續續、不是穩定上昇的時刻。透過 grafana 的功能將時間區間定得更細之後,就看得更清楚了。
後記:

處理這個問題之後,我也比較了一下 influxdb 和 RRDtool 的。

RRDtool 是比較早的時代就發展的系統,它的特性就是很善於捨棄比較不重要的資料。所以像是 counter 型別的資料,RRDtool 是先將資料加以做類似對時間「差分」的運算,再平均值後才儲存。缺點就是要除錯時,因為不容易取得原始資料而難以下手。

而 influxdb 則是直接儲存原始的資料。也因此,如果要用 grafana 來對 influxdb 的 counter 類型資料做繪圖時,差分的運算就要自己來做了。所幸 influxdb 本身的 query language 也提供了這部分的支援,像下方的 derivative 指令,就是做這個差分運算。

SELECT 8 * derivative(mean("value"),1s)

從 unit test 到 functional programming

在最近的這分工作,才漸漸有在寫「單元測試」 unit test ,在和其它同事討論寫 unit test 的時候,我們討論到了寫 unit test 之前,就必須先把程式寫成「可以被測試」testable。

怎麼寫程式,程式可以是 testable 呢?
例如,函數儘量就只有透過 input argument 和 output argument 來和外界溝通,不要透過全域變數或是在內部呼叫會有副作用的函數。如此才能確保這個函數只要是相同的輸入,就可以總是產生一樣的輸出。

上述的種種要求,在 functional programming 的領域,就叫做要寫沒有副作用(side effect)的函數。或是叫做純函數 (pure function)。我查了網路之後,發現這個「單元測試」與「函數式編程」的關連性,也早就有許多人在談了。讓我來引用一下他們用的句子:

Writing unit tests is reinventing functional programming in non-functional languages.

Tuesday, October 4, 2016

vim, tmux - 256 colors support

現在做的工作,我都是用 vim 開發程式。網路上似乎很多人都是用 vim 搭配 tmux 。我也決定開始用 tmux 。畢竟每次開 terminal 都要用滑鼠點開一個新的 bash shell 好像也滿蠢的。

由於我用的 vim 是有設定 256 色支援的。在我的 ~/.vimrc 裡有下列兩行

colorscheme molokai
set t_Co=256

所以我用 tmux 的時候,自然也要做 256 色的處理。於是,我在 ~/.bashrc 裡加上了一行

alias tmux='tmux -2'

tmux 的 256 色出乎我意料之外的容易設定。

Monday, October 3, 2016

API 文件撰寫考慮的重點

公司在開會時,討論到我們該如何統一公司 API 文件的規範。本來大家都各說各話,主管則是表達:「請大家設法形成共識,不要讓我去推動一個 80% 的人都無法遵守的規範。」

終於,資深工程師M大大,發表了他對於 API 文件的先知卓見:

1 在我以前任職的公司,並沒有這種「先把程式寫完才寫 API 文件的這種開發方式。」一種可以做出穩定軟體產品的作法,是要先構思 API ,構思清楚之後才開始開發。 API 文件的重要性,可以類比資料庫的 schema 。

2 我個人比較重視下列三件事:
   (a) 編寫文件的表現自由度
   (b) 文件的格式必須是公開的格式,可以被改變的。因為外在的環境總是不停地在改變。
   (c) 是否可以快速地切換「檢視模式」和「編輯模式」

首先,apiary 這個平台,雖然有提供一些 sugar ,可以自動生成一些測試、說明,但是,它的平台功能並不是非常完整,同時也限制了編寫文件的自由度。如果需要撰寫的 API 是 stateful 的,是特別複雜的,其實用這種平台反而不會特別好用。這種平台只適合 API 的數目不多, API 相對單純的情況。於是,這個就導出了一個議題,是要選擇「半自動生成的 API 文件」還是選擇「純手刻但是 100% 自由的編寫方式」。如果考慮要做出高品質的文件,我還是傾向於後者。

關於文件格式的部分,我本來寫的文件一開始是用 mark down 寫在 gitbook 上,後來移到 trac 的 wiki 上。雖然 mark down 和 wiki 的語法不同,但是這些格式只要取代之後即可。所以使用公開的格式是很重要的。因為承載文件的平台很有可能會一直變換。然而,如果一開始是用 word 來寫,噢,那就糟了。

最後,像是 apiary 這個平台,它要快速切換「檢視模式」和「編輯模式」,並不是很順。然而,寫 API 文件如果要寫得清楚,就難免要用到比較進階的「格式化選項」。這時候就勢必要用肉眼來檢查,格式指令是否正確。所以能否快速切換,這個也是考量的重要條件。

javascript async/await promise ---- 驗証方式

在 javascript 裡,因為 I/O 都是非同步呼叫,如果需要寫出有相依性的業務邏輯(同步呼叫),因為要寫大量的 callback,可讀性上就會遇到 callback hell 的問題。新版的 javascript 已經有提供了 async/await  語法,搭配 promise 來取代 callback 的寫法。

然而,要如何驗証使用 async/await 之後,函數的呼叫真的是「同步」的呢? 這個問題該如何驗証呢?其實可以利用 chrome debugger 來驗証。
 圖一:這個是實作 async/await 之前的狀態。可以看出兩條紅線的右端,兩個呼叫後端的函數是在同一個時間點開始。
圖二:經過 async/await 的修改後,兩條紅線的右端同樣兩個函數,開始的時間產生了明確的差距,恰好就是第一個函數完成之後,第二個函數才開始。

Friday, September 23, 2016

snmp 與 VMware ESXi

我任職的公司開發的產品,是基於 open-falcon 的監控軟體。北京快網的人,最近對我提了一個需求,要我用 snmp 去監控 VMware 的 ESXi system。

最初也沒有想得很清楚,一直以為要監控的重點是 Guest machine。直到我跟北京快網的運維再次確認之後,才搞懂,原來要監控的重點是 Host machine 。因為 Guest machine 通常是使用 CentOS ,這種都是可以跑 open-falcon agent ,自然也就不需要使用 snmp 了。然而, Host machine 的作業系統是 ESXi system ,沒有辦法安裝 open-falcon agent,所以才會需要使用 snmp 。

我接到工作的時候,翻了翻文件,很快地就找到了一個 open-source 的套件,叫 swcollector ,是特製的 open-falcon agent,可以用來存取 snmp 。但是,仔細看了它的設置檔之後,發現 swcollector 適合去做監控 switch 的事,但是這次的需求是要監控 ESXi system,其實跟 switch 也差很多。

最關鍵的點是 OID 的部分,因為其實 ESXi system,就跟許許多多的 switch 一樣,它使用自己特別的 OID。我下了 snmpwalk 指令,把 ESXi system 所有的 OID 全部撈回之後,就發現 OID 還真的不是一般的多。這邊還有一個插曲,我本來不知道其實我需要去 VMware 的網站上,下載 VMware 的 mib 檔。所以我撈回來的 OID 裡,會有一部分,一定是用數字呈現。後來我注意到可以去 VMware 的網站上下載 VMware ESXi 專屬的 mib 檔,安裝到 /usr/share/snmp/mibs 之後,才把一些本來看不懂的 OID 變成可愛的英文。

不知不覺,這個工作也即將完成。 https://github.com/humorless/esxicollector 

過去用得很開心的 MRTG, Catci, 底下都是靠這個 snmp 在運作,終於搞懂了 snmp 是怎麼運作的了。

Wednesday, September 21, 2016

用 python 來測試 system call 的行為

在客戶的機器上,發現 curl 和 ping 的 DNS 解析行為不一樣。查了半天,才發現原來是底層呼叫的系統呼叫不同。 curl 使用 getaddrinfo() ,而 ping 使用 gethostbyname() 。於是,這時我發現了有一個問題,過去沒有仔細思考過,在 linux 的 shell 環境之下,要怎麼樣可以測試「系統呼叫」的行為呢? 查了一下,發現還是用 python 最省事。

另外,也是因為處理了這個問題,我才發現原來 DNS 也是有緩存的。Nscd caches libc-issued requests to the Name Service. If retrieving NSS data is fairly expensive, nscd is able to speed up consecutive access to the same data dramatically and increase overall system performance.  也因此,如果修改了 resolv.conf 的話,記得要叫 nscd 做一下重新載入。


Saturday, September 17, 2016

Use the index, Luke

use-the-index-luke 是 SQL performance 的教學網站。 內容滿深入淺出的。我一開始是為了要理解 clustered index 和 primary key 有什麼關系,而查到這個網站。想不到立刻就看到這個網站上的一篇文章,談論「 MySQL 的預設值將 primary key 設定為 clustered index 這是不盡理想的設計」,文章的大意是:

  1. 要使用 primary key 的話,建議要使用 non-clustered primary key。不要用 MySQL 的預設設置的 clustered primary key 。否則會得到 clustered index penalty 。
  2. 效能的重點在 index-only scan 

由於網站上的文章也相當多,我讀了兩三篇之後,改變心意,用速成的方式來學好了。於是我做了網站上的習題。結果,五題裡頭,我還真的只會兩題,就是有讀過網站上文章所以才會寫兩題。 題目很有啟發性,下方就是其中的一題,題目是不好的 SQL 語句,要能夠看出效能的瓶頸才算通過。

Thursday, September 8, 2016

SQL schema 設定 primary key 的 constraint

最近做的工作,我在產品的資料庫,新增了一張 table 。在 code review 時,被同事建議「要設定 primary key」。於是,我就順便研究了設定 primary key 的重要性及理由。

主要的原因如下:有設定 primary key 的 column ,其值必定是唯一的。目前設計的這個 table,它的 business logic 裡,有一個 column 它的值也是唯一存在的,不會有重複的值。既然 business logic 就隱含了 unique value 的概念,加上 Primary key 的 constraint 自然可以「讓錯誤看得出來是錯誤」。

而好的 Primary Key 該如何設定呢?
Good primary keys are essential to good database design. They let you query and modify each table row individually without changing other rows in the same table. When you evaluate candidates for a table's primary key, follow these rules:

  • The primary key should consist of one column whenever possible.
  • The name should mean the same 5 years from now as it does today.
  • The data value should be non-null and remain constant over time.
  • The data type should be either an integer or a short, fixed-width character.
  • If you're using a character data type, the primary key should exclude differential capitalization, spaces, and special characters, which might be difficult to remember.

Wednesday, August 31, 2016

vim 的插件 vim-go 的 tutorial

vim-go 的 tutorial  裡頭有很多有趣、有用的技巧。取出一些我覺得比較好用的。

Import 的技巧
:GoImportAs ff fmt
:GoImport strings

Edit 的技巧
if 表示函數的內部 inside a function
dif = delete inside a function
yif = yank   inside a function
vif = visual mode inside a function

af 表示整個函數 -> a function
daf = delete a function
yaf = yank a function
vaf = visual mode a function


Trace 程式碼的技巧
對 channel 的理解
 :GoChannelPeers  選擇某個channel,顯示它的send/receive/def

對 variable 的理解
 :GoReferrers     選擇某個變數,顯示所有被使用的地方。

對於 type 的理解
 :GoImplements    選擇某個類別,顯示它實作的介面。(一個有搭配函數的類別就很可能是有實作介面的類別)
 :GoDescribe      選擇某個類別,顯示所有使用它的函數。(用使用範例來"描述"一個類別)

對於 error 的理解
 :GoWhicherrs     選擇某個錯誤,顯示它可能包含的錯誤資訊。

對 func 的理解
 :GoCallees       選擇某個區域變數(該變數的型態是函數),顯示它可以對應到的函數。
 :GoCallstack     選擇某個函數顯示它被呼叫時的call stack
 :GoCallers       選擇某個函數的定義,顯示它被呼叫的位置

Refactor 的技巧
將區段的程式碼抽取出來,變成函數
 :GoFreevars      區塊選擇一段程式碼之後,顯示這段程式碼裡的輸入變數(input variables)
 :GoRename        修改一個變數的命名,自動修改多個位置。


註:在新版的 vim-go ,已經用 guru 取代了 oracle 。所以如果沒有看到 oracle ,不用再去找了。

log 與 error handling --- 讓錯誤看得出來是錯誤

公司的同事 mike 前幾天跟我講了幾句話,讓我受用無窮:

  1. 很多程式設計師寫出了幽靈程式碼,總是抓不出錯。這是因為在撰寫程式時,就不夠嚴謹。
  2. 要如何寫出嚴謹的程式碼呢? 遇到異常(exception)或是錯誤(error),有辦法處理,就要設法處理。沒有辦法處理時,也要寫在規格(也就是註解),至少讓後來維護的人知道這邊有個「坑」


於是我歸納了一些要寫出嚴謹的 golang 程式,可以注意的重點:

  1. 不要亂用 log level ,要小心地使用 log level 。比方說,在 golang 裡, log.Fatal 就會呼叫 exit(1) ,這個就絕對不可以亂用。有時候,網路上找到的 code snippet 就用了 log.Fatal。如果直接不加思索的照抄。那程式就會異常終止了。
  2. log level 和 log message 要好好地搭配使用: 比方說,如果引入了 logrus 這套 log level 的函式庫。對於不會影響程式正常運作的 log level 就有 info, warn, error 三種。既然已經用了 warn 的 log level ,其實 log message 就該避開使用 error  這個關鍵字。
  3. 儘量在程式裡處理所有的函數傳回的 error 。要做到這件事,有一個 golang tool 可以使用: errcheck 。這個工具可以檢查出,所有傳回錯誤,錯誤卻沒有被處理的函數。如果有用 vim-go 的話,可以下指令 :GoErrCheck  

Thursday, August 18, 2016

依賴注入 - dependency injection

寫單元測試時,常常會遇到一問題,原始的程式碼本身的結構,難以加入單元測試。

最近我遇到了一個問題,我寫的程式會呼叫 git clone ,而 git clone 在網路環境不佳時,甚至會執行超過24小時,將程式的 go routine 整個卡住。所以解法就是需要將 golang 的外部指令呼叫,改成有 timeout 的。

要加上這個 timeout 並不難,困難點是,要如何測試這個功能?因為其實要模擬出 git clone 長時間執行的環境,並不是很簡單的事。

解法是這樣子:
加上這個功能時,必須將這個「為指令加上 timeout 功能」實作成一個獨立的函數,使它的功能獨立,並不「依賴」於 git clone 。換言之,「 git clone 」對應的指令,會成為這個函數的輸入變數(input argument),由外部「注入」。於是這個功能就可以獨立地來寫單元測試來測。測試它的時候,就可以用「 sleep 500 」對應的指令,來做為它的輸入變數(input argument)

原始程式碼的修改
新增的程式碼和單元測試

除錯的利器 - tcpdump

開發程式,我最常用來除錯的技巧主要就是幾種:

  1. 寫單元測試 
  2. 查 log 檔

然而,上述兩種技巧都有一些限制。

單元測試的話,如果原本的程式寫的很差,我自己新寫的部分有時也很難測,要先重構舊的程式才能寫單元測試。查 log 檔的話,如果一些 bug 是在 production 環境才出現,也很難取得完整的 log ,因為在 production 環境, loglevel 通常是設定成 info 而已。

也因此,在 production 環境之下,用 tcpdump 有時可以取得比 log 還更完整的資訊。

Wednesday, August 17, 2016

例外處理




寫程式有一段時間了,有一些習慣問題,卻一直沒有深刻的認識。直到真的寫出 bug 以後,才會理解。

上方的例子,就是一個不好的例子。 GetCurrPluginVersion() 的傳回值,其實應該要有兩個資料型態,一個應該要用 string ,另一個應該要用 error 。而上頭的例子,只用一個 string 就裝了兩個不同資料型態的資料。這樣子讓使用 GetCurrPluginVersion() 的使用者,很容易潛意識地主觀認為,這個函數是一定會成功的,不會有 error 。於是,真的有 error 時,就一直穿透,直到很後期才發現。

Monday, May 9, 2016

vim的插件vim-go使用心得

這幾天認真地來試用了一些 vim-go 裡提供的指令:
1 :GoDef        可以快速跳到 symbol 的定義。( 按Ctrl + O 跳回 )

2 goimports     基本上,算是 gofmt 的加強版
  直接在 .vimrc 裡設定,取代掉 gofmt
  let g:go_fmt_command = "goimports"

3 :GoLint       可以得到一些很好的 code review 的建議。( 按 :ccl 可以關閉 quickfix window )
4 :GoRename     refactor 的利器,用來快速修改函數的名稱
5 :GoAlternate  快速切換 test file 和 original source file
6 :GoVet           做靜態分析,抓出一些邏輯的 bug 。比方說,如果有資料結構有定義 marshal 後的json格式。而格式寫錯,就可以透過 go vet 抓得出來。
7 :GoErrCheck   可以檢查出所有沒有處理的 error 。許多函數都會丟出 error ,寫程式時粗心一點,就會忘記要處理妥善處理這些 error 。有時連寫進 log 這麼基本的事情也沒有做,於是就會造成「錯誤卻看不出來是錯誤

Thursday, April 7, 2016

把vim變成go language的編輯器

安裝vim的plugin manager。 apt-vim,以及 pathogen
curl -sL https://raw.githubusercontent.com/egalpin/apt-vim/master/install.sh | sh

用這個指令來看,目前安裝了哪些vim plugin。
apt-vim list
=======================================
安裝 vim-go
$apt-vim install -y https://github.com/fatih/vim-go.git
安裝完之後,會得到syntax highlight 和存檔時自動gofmt的功能。

vim-go要正常作用。需要搭配的.vimrc內容為 (參考)
execute pathogen#infect()
syntax on
filetype plugin indent on
=======================================
安裝好看配色 molokai
$ cd ~/.vim
$ mkdir colors

$ git clone https://github.com/tomasr/molokai

$ mv molokai/colors/molokai.vim ~/.vim/colors/
在 ~/.vimrc裡增加兩行
colorscheme molokai
set t_Co=256