2021 To 2022

這篇本來應該要早點完成,但是整個2021年實在是太多事情了,趁著2022年初一有個空擋來紀錄一下。

2021年初最讓人興奮的事情就是Appier成功上市了,雖然我加入這個大家庭也才短短的兩年半,但是還是很高興有機會能夠一起經歷這個過程。

緊接著IPO之後沒多久我買了我人生的第一間房子,雖然這間房子只是間小公寓,但總覺得在結婚之前如果能有間房子,感覺能讓另一半感覺更加踏實,無論如何在2021年我完成了人生一個重要的里程碑。

隨著時間的推進,沒想到我盡然會在公司剛完成IPO的這年離開公司,加入了一個傳產新創艾立運能,如同我在前一篇所提到的,我不該讓自己沈浸在Appier成功的喜悅太久,與之前不同的是,這次我是在公司非常早期的時候加入,希望過去的各種經驗能幫上忙,我不會說要複製Appier的成功模式,但是我會利用這些經驗再創造另一個不一樣的成功,假以時日有成功的那天,我再來回顧這段故事。

我想一個人在一年內經歷了上面的轉換,應該也是忙得不可開交了,但是沒想到緊接而來在年底,又要迎來我另一個人生大事,就是和我交往四年多的女朋友結婚了,2021年整年受到新冠疫情的影響,讓我們在婚事的進度上有一搭沒一搭的走著,然而,在日子定下來後,我又快速進入另一個忙碌的階段,各式習俗的準備、雙方的溝通都讓我們兩個人有點累,過程雖然不是很完美,但是我和我老婆也在2021/11/15正式完成登記,在這個過程中一方面要準備各式各樣的結婚用品(中西式餅、餐廳、禮車、習俗等等), 另一方面房子也在進行裝潢,同時我又剛換完工作,中間還來個小插曲,教育召集7天,這段時間真的是我這些年來最忙碌,也最充實的一段,我想幾年後這些事情我還是會再重複的說幾遍,在這邊也要特別感謝我的老婆,在這麼有限時間內,頂著這些壓力,耐著性子,讓事情能夠順利的完成。

回顧整個2021年,我幾乎把人生幾個大事都完成,我自認為我是個謹慎的人,對事情的處理、風險的控管都做的不錯,然而在這些超載的日子裡,有時候也感覺到無力,我們能做的就是把自己能處理好的部分都完成,剩下的就只能交給命運,畢盡,不是所有事情都是我能掌控的。

在經歷了充滿變動的2021年,我想替2022年也立下幾個目標

  1. 家庭和工作之間找到新的平衡
    2021年以前我就是獨善其身,大部分的時間也都放在工作上,但是隨著進入人生另一個階段,我必須要對此作出調整,希望我能在有限的時間內,維持同等的績效也兼顧家庭的照顧。
  2. 深耕新的產業領域,等待下一次跳起來的機會
    2021年加入了一個新的領域,除了產業的轉換之外,角色上也有所不同,套一句我最近常說的,以前的我是追求個人的成功,現在的我要追求的是公司的成功,在這麼大的調整之下,我想2022年我還有很多功課要做。
  3. 持續增加可控的風險投資
    2021年事情很多,伴隨而來就是錢也噴得很多,多虧之前的投資習慣,讓我能夠度過這段金流拮据的時期,希望我能在風險可控的前提下,持續增加投資的金額,加速實現財富自由的時間。

Journey of Appier (preface)

Two years ago I join Appier, I join the AIQUA in the most chaotic time, but also a lovely time. I left Appier after two and a half years on 2021/08/27 to chasing another success.

In shorts, the result of what I have done in Appier was more like a summary of all my past work experiences.

I will try to document it and explain it. Here was what I experienced from company to company before joining Appier.

IGS

In IGS, I learned the basic scaling idea, including sharding, and horizontal service scaling. At the same time, I also learned how to profiling the DB query both in MongoDB and SQL server.

MeepShop

In MeepShop, it’s my first time to knowing the container ecosystem, including docker, docker-compose, and Kubernetes of cause. Also, it’s also my first time have a chance to get close to cloud service(GCP). Knowing lots of tools/frameworks like GraphQL, Protocol Buffers, GRPC, Let’s Encrypt, etc. Experienced of system migration (MeepShop V1 -> MeepShop V2), database migration (MongoDB -> PostgreSQL). Thank my friend bring me to Vim’s world, it made me more efficient while developing.

Trend Micro

In Trend Micro, I strengthened the DevOps skill and infrastructure provision. I spent most of the time read the AWS documents, try almost every service on AWS, including s3, SQS, SNS, EMR, CloudWatch, etc. I also learned some basic ideas on how to saving costs, how to monitoring the service, and how to do the DR site design. Sorts of the big data ecosystem, like Spark, Hadoop, and how to write the PySpark, how to manage/monitor the ETL pipeline.

All of the experiences in the past, help me achieve some almost impossible missions during the Appier.

In the end, I want to remind myself with few words. “Don’t linger in an established career

  前些日子有些迷惘,所以中斷了一點時間,因此用一篇嘴砲來抒發一下內心情緒,這次我想要說說台灣的教育,台灣的教育成功嗎?從微薄的起薪到低迷的就業率看來或許有人覺得不太成功,但是如果從蓬勃的社會運動、人權思維,我倒是覺得台灣的教育超級成功,最近我在思考一件事,教育的本質到底是什麼?雖然我還不能完整的闡述一個完整的想法,但是我想藉由這次的文章,看看能不能勾勒出一些形狀,基本上在訂完這個主題的時候,我內心馬上就出現了現在社會存在的矛盾例子,

*場景一:某某企業說,現在大學畢業的學生進入職場完全沒有競爭力,所以政府應該調整課綱以利學生畢業之後能有競爭力。  

*場景二:某某學生說反正大學畢業也是22K,況且比爾蓋茲和賈伯斯大學都沒畢業,所以我覺得讀書沒用。

兩個不同身分角色似乎都對台灣現況的教育很不滿意,再繼續下去之前我想要簡單的分析一下這兩個立場的想法,以企業的角度,學生在經由一連串的國民義務教育之後,必須具備有進入職場工作的能力,換言之,政府應該代替企業以教育為手段訓練人才替企業省下人事訓練的費用,基本上如果我是勞工,我絕對說這是個慣老闆,那如果以學生的觀點來看,我已經學會各種知識學問,企業只願意花22K雇用是有病?我浪費了十幾年讀書考試,換來這樣的東西根本不值得,基本上如果我是老闆,我只會說你學的東西就不是企業所需求,我當然只能比較低微的報酬。
  那這兩個到底誰對誰錯呢?以及為何基礎九年國民教育,乃至之後的大學以及碩、博士的教育,所提供的給學生的能力,和社會所期待畢業生該具備的東西有如此大的落差呢?希望在接下來的文章我可以慢慢的抽絲剝繭將一些似是而非的東西釐清。
  在我來看其實這兩個立場我覺得都沒錯,那會造成這種落差的原因到底是什麼?經過我思考後我發現一件事,那就是大家對教育的期待以及所懷抱的憧憬是不是太多?到底是什麼理由讓教育背負這麼多的期望?我想了很久突然一段話讓我有了想法

        《勸學詩》
富家不用買良田,書中自有千鍾粟。
安居不用架高堂,書中自有黃金屋。
娶妻莫愁無良媒,書中有女顏如玉。
出門莫愁無人隨,書中車馬多如簇。
男兒欲遂平生志,五經勤向窗前讀。

這裡的__書中__是指教育嗎?又或者你我都將書本知識視為是教育的一部分呢?我想多讀書能讓你更有競爭力,有競爭力當然能獲得更多的成就與報酬這是對的,但是這樣一個結果是國家義務教育所應該提供或是具備的功能嗎?我想這答案是否定的,在我看來我們所泛指的教育應該是

1. 教育須讓人有分辨是非對錯的能力
2. 教育須讓人有基本獲取知識的能力
3. 教育須讓人有基本國家和民族意識
4. 教育須讓人有思想知道為何而存在

大家知道「授人以魚,不如授人以漁」,我想教育的功能也是如此,當你受過教育表示你有能力去分辨是非對錯,就像是同志婚姻權利的爭取,兩人了相愛與否不該因為性別而有所差異(雖然我還是不太能接受,不過這是個人好惡的問題,我不想引戰),不過因為受過教育讓我知道這個權力應是人生就該賦予,我知道中華民國就是中華民國,它和中華人民共和國是不一樣的,不論什麼原因理由,我們都應該以身為中華民國國民為榮,在外如有不公正的評論我們就有責任和義務去捍衛我們的國家,教育讓我知道,現在我在職場上能力有所不足,所以我應該再去書本多充實自我,相對教育也讓我有能力去教導提拔那些未來的中流砥柱,最後,因為有教育讓我能有思想,並且試著去分析社會的現象,哪些可能是人為刻意的操弄,哪些才是我們該堅守的原則,什麼東西是可以犧牲?什麼東西是不可碰觸的底線,一切的一切都是受過教育後所賦予我的能力,我很高興我受過這樣的教育,我也認為台灣的教育其實比你我想像中的都還要來的好,發完牢騷明天繼續上班吧。

Google Cloud Load Balancing

  延續著上一篇的步調,這次來談談Google Load Balancinig,至於為什麼我會需要操作或是說用程式來控制就讓我娓娓道來,我想有使用過雲端服務的人應該都大概知道Load Balancer的功用,簡單說Load Balancer就是能夠讓你將網站或是服務乘載量大幅提高的工具,不過前提是你的程式也要能支援這樣擴展性,那我這次被指派的任務是什麼呢?簡單說就是要幫我們的Load Balancer提供Https的Ssl憑證,並且還要定期去更新這個憑證,由於這種服務的控制正常流程都是上Google Cloud Console點一點就結束了,所以官方提供的api/compute感覺上就是基於協定由程式產生的程式碼,所以使用起來真的是相當不直覺也沒有太多的說明,基本上可以當作是這個SDK幫你發一個POST到Compute Engine API,至於POST要提供的參數、呼叫API的先後順序大概只能用通靈的方法得知吧,因此這篇就簡單紀錄一下怎麼用這個SDK以及呼叫API的順序。
  如上面所說的,Google提供的SDK相當的不方便,因此我自己就把這個SDK簡單的wrap,這個wrapper同時也實作了polling的功能,用過雲端服務的人基本上都知道,服務的開關不像是通電一樣說來就來,基本上都是需要一小段時間服務才會開始正常運作,因此如果你再不對的時間的要去存取一個還在準的資源(Resource),那就會有很大的機會遇到一些怪怪的錯誤,尤其是像Load Balancer這種服務,如果你去Google Cloud Console上操作可能會覺得只有一個指令,但是如果用SDK操作那麼你最少會需要存取5~7種資源

1. Backend Service
2. Url Map
3. Target Proxy
4. HTTPS Target Proxy
5. Global Address
6. Forwardig Rule
7. Certificate

這些條列的資源在Load Balancing的進階選項基本上都能看到,同時上面的順序也就是要產生一個Load Balancer所需要操作API的順序,至於各個資源各自所代表的意思和功能大家在自己上去文件看吧。
  到此萬事都具備了,再來就是要操作SDK了,這邊簡單接紹一下我的gcp-loadbalancing,由於api/compute所涵蓋的功能超級多,所以我這邊也只將會用的進行處理,所提供的功能如下

  • GetBackendService func(name string) (*compute.BackendService, error)

  • GetUrlMap func(name string)(*compute.UrlMap, error)

  • GetTargetHttpProxy func(name string)(*compute.TargetHttpProxy, error)

  • GetTargetHttpsProxy func(name string)(*compute.TargetHttpsProxy, error)

  • GetGlobalAddress func(name string) (*compute.Address, error)

  • GetForwardRule func(name string) (*compute.ForwardingRule, error)

  • GetSslCertificate func(name string)(*compute.SslCertificate, error)

  • InsertUrlMap func(name string, defaultService string)(*compute.Operation, error)

  • InsertTargetHttpProxy func(name string, urlMap string)(*compute.Operation, error)

  • InsertTargetHttpsProxy func(name string, urlMap string, sslCert []string)(*compute.Operation, error)

  • InsertTcpGlobalForwardRule func(name string, ipAddress string, targetProxy string, portRange string) (*compute.Operation, error)

  • InsertGlobalAddress func(name string)(*compute.Operation, error)

  • InsertCertificate func (name string, certificate string, privateKey string) (*compute.Operation, error)

  • SetTargetHttpsProxySslCert func (name string, sslCert []string)(*compute.Operation, error)

基本上Get開頭就是去讀取資源的名稱、狀態或是其他詳細資訊,Insert開頭就是建立一個相對應的資源,不囉嗦直接看程式碼

1
2
3
4
5
6
7
8
9
10
11
import lbt "github.com/lbeeon/gcp-loadbalancing"

tools := lbt.NewLoadBalanceTools(projectID string)

// get backend service info
backendServ, err := tools.GetBackendService("service_name")

// polling result automatically
op, err := tools.InsertCertificate("new_cert", "certificate","privateKey")

op, err = tools.SetTargetHttpsProxySslCert("new_cert", []string{"https://www.googleapis.com/compute/v1/projects/xxxxxxxx-xxxxxx-xxxxx/global/sslCertificates/xxxxxxxx"})

第一行是載入要使用的package,再來就是要建立一個可以操作API的物件,這邊要提醒一下要操作API都是要提供Credentials的,那麼在這邊我的程式會和類似BigQuery SDK一樣自動去抓GOOGLE_APPLICATION_CREDENTIALS環境變數,我想有在使用GCP的人大概都知道這是官方強烈建議要設定的環境變數,因此用這個當作標準我想是很合情合理的一件事,不過要建立一個API的物件還是要提供一個ProjectID的參數,這邊的設計基本上也是想要依循其他SDK的標準,有了這個物件之後,再來就可以簡單的試用了,像是用Get的方法去看一下Backend Service的狀況阿,或是建立一個新的憑證,又或者是幫Https Target Proxy設定一個憑證都可以簡單的操作了。
  當然如果你有一些自許或自尊什麼作祟的話那建議你可以去用官方提供的api/compute,因為我也不保證我提供的程式碼完全沒有問題XD,不過在使用過這些時間以來我是沒有遇到什麼問題就是了,以上就是簡單紀錄一下怎麼使用SDK操作Google Load Balancing。

Intro

  想想到meepshop也將在這個月底滿一年了,在這過去一年多內學了不少新東西,也玩了不少服務,不過主要都是以GCP(Google Cloud Platform)的服務為主,因此,接下來幾篇文章都會聚焦在這些服務上,也算是一個簡短的工作回顧吧,至於我有用過哪些服務呢(這邊以有用過SDK為主),以下簡單列舉一下

  • BigQuery
  • Storage
  • Datastore
  • Spanner
  • SQL(Postgres)
  • PubSub
  • Networking(DNS, loadbalance etc…)

以上這些服務基本上都是以golang作為開發主要語言,因此,之後的介紹也應該會是圍繞在如何操作SDK,並配合簡單範例以供參考,廢話不多說直接來進入主題吧。

BigQuery

  為什麼第一篇選擇介紹BigQuery呢?原因無它,因為她是我第一個用的GCP服務,簡單介紹一下什麼BigQuery,BigQuery是Google提供的一個服務,主要是用來統計大量的資料,特別的是它支援NoSQL的資料結構同時又能使用SQL like的語法查詢,因此可以說是相當強大,猶豫敝公司所使用的是NoSQL資料庫,因此,看到這些支援的特色整個就是很合胃口,所以公司就委託小弟我去簡單研究一下,在正式進入程式碼之前還有一個東西要提一下,就是Goolge的SDK貌似有兩種,一種是google/google-api-go-client,另一種則是GoogleCloudPlatform/google-cloud-go的版本,那麼這兩種有什麼差別呢?簡單來說google開頭的repos包含了所有google所提供的服務api-sdk,像是translate、drive以及GCP上面有提供的BigQuery、Datastore等等,而另外一個GoogleCloudPlatform開頭的,感覺比較像是將前面那一個SDK和一些語言本身的使用習慣wrap之後的SDK,因此,使用上相對會比較簡單,而在GCP的官方教學內容,大部分也都是以GoogleCloudPlatform為主,不過身為一個硬派玩家,只使用GoogleCloudPlatform的開發工具,有很多事情似乎辦不到,以BigQuery來說好了,如果你想要在程式裡面使用UDF的話,在GoogleCloudPlatform的SDK上是完全沒有辦法,不能的理由也非常簡單就是他根本沒有實作這個部分,因此,我一開始也是看官方推薦使用,但是在開發產品的時候才會發現,基本上要用的功能想要什麼就沒什麼,所以我這邊才會找到另外一個google-api-go-client的repos,不過如果你需要僅是非常基本的功能,那照著官方的文件應該就可以滿足你的需求,不過這邊主要還是要介紹比較底層SDK的使用方法,再來就簡單敘述一下怎麼使用這SDK吧。
 
這邊介紹一下整個範例程式的架構,簡單來說就是先建立一個BigQuery的client,再來就是到BigQuery建立一個要執行的Job並展示結果,詳細程式碼我最後會附上連結

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func main() {
// create a bigquery client
jwtConfig, err := NewJWTConfig()
if err != nil {
log.Fatalln(err)
}

client := jwtConfig.Client(context.Background())
bigqueryService, err := bigquery.New(client)
if err != nil {
log.Fatalln(err)
}

// create a jobsService
jobsService = bigquery.NewJobsService(bigqueryService)

// create an async job
jobId, err := jobInsert(cmd)
if err != nil {
log.Fatalln(err)
}

// polling to check status
err = jobDone(jobId)
if err != nil {
log.Fatalln(err)
}

// get result
jobGetResult(jobId)
}

感覺上好像沒有什麼特別,不過和官方教學最大的差異是這裡執行的是一個非同步的Job,而同步和非同步的差別主要是在於執行時所能夠使用的資源和時間,因此如果你想要分析上百GB的資料,官方提供的方法是完全不管用的,因為在運算的時候你就會拿到timeout或是response too large的錯誤,這時候如果你看官方資料它會建議你開啟allow large results的選項,不過尷尬的是如果你要用這個功能就必須是一個非同步的Job,而官方的開發文件對這邊對非同步Job幾乎沒有什麼著墨,所以我這邊就當作是分享順便記錄方便之後使用吧。
  要使用原生的api-sdk一開始就是需要建立一個認證連線,這邊我只用的是jwt的方法,配合各個服務所提供credentials來做一個認證,至於這個NewJWTConfig的方法也不是我發明的,而是直接參考GoogleCloudPlatform的認證方法在小改良一下,基本上GCP上面所有的服務如果要使用api-sdk這樣的方式都是通用的,有興趣各位在自己去爬爬原始碼吧,再來是要建立一個新的job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func jobInsert(cmd string) (string, error) {
jobConfigQuery := bigquery.JobConfigurationQuery{
Query: cmd,
UserDefinedFunctionResources: []*bigquery.UserDefinedFunctionResource{&bigquery.UserDefinedFunctionResource{
InlineCode: `
function Calendar(row, emit){
var startTime = new Date(row.start*1000), endTime = new Date(row.end*1000);
while(endTime > startTime){
emit({YEAR: startTime.getUTCFullYear(), MONTH: startTime.getUTCMonth()+1, Day: startTime.getUTCDate(), Hour: startTime.getUTCHours()});
startTime.setTime(startTime.getTime()+60*60*1000)
}
}
bigquery.defineFunction(
'Calendar', // Name of the function exported to SQL
['start', 'end'], // Names of input columns
[{'name': 'YEAR', 'type': 'integer'}, // Output schema
{'name': 'MONTH', 'type': 'integer'},
{'name': 'Day', 'type': 'integer'},
{'name': 'Hour', 'type': 'integer'}
],
Calendar // Reference to JavaScript UDF
);`,
}},
}
jobConfig := bigquery.JobConfiguration{Query: &jobConfigQuery}

result, err := jobsService.Insert(projectID, &bigquery.Job{Configuration: &jobConfig}).Do()
if err != nil {
return "", err
}
return result.JobReference.JobId, nil
}

要建立一個非同步的job最重要的事情就是要建立一個JobConfigurationQuery的條件,說真如果只憑GoDoc上面的說明要產生上面的程式根本是天方夜譚,我個人認為是不可能,因為所有api-sdk的文件都是機器產生出來的,所以教學範例也少的可憐,基本上能有這段程式碼,我是藉由google console所送出的封包,反向推敲才設定出來的結果,真的可以說是行行皆辛苦,如果你僅需要執行一個耗時很久查詢,而沒有需要UDF,基本上你可以直接把UserDefinedFunctionResources這東西整個拿掉,其實這整篇的重點有70%就是在這個JobConfigurationQuery的設置,至於後面的job的狀態和資料的讀取就請大家直接看原始碼吧,最後附上範例程式