2013年10月16日 星期三

台灣市場大?不如回來賣牛肉麵吧~

看了「台灣市場小,那市場多大才算大?」一篇文章,剛看完沒啥感覺。仔細想想後才發現自己浪費了五分鐘。

我用個簡單仿效一下這一篇的架構:
巷口的醬油很貴,多少才算便宜?

總是有人說:
「隔壁村的醬油比較好,而且又便宜」
「我們的阿婆年紀大,做出的醬油品質差」
「醬油醬油醬醬油」

……類似的觀點流傳太久、重複太多次,以致於「巷口醬油貴」幾乎成為不可質疑的觀點。但話說回來,每次聽到別人說「巷口醬油太鹹」之後,你好奇地追問:「那,醬油要多鹹才夠呢?」卻從來得不到明確答案。

總是有人說,一個村的醬油品質取決於村裡的勞動人口。我舉幾個反例吧:
隔壁村的醬油重600克,售200元。
城裡的醬油重400克,售140元。
我們的醬油1000克,300元。
你覺得呢?我不知道。

我只想當個打醬油的。 

其實大濕你的結論沒錯,因為你的文章根本沒結論

但是還是有一點營養價值的,

大概只比上面那一篇營養一點。

廢話不會錯,只是沒營養罷了。但是的確勾起了我一些想法。裡面提到統一和全家超商,說他們只靠內需,也做的很好。我同意,但這是大家都同意的廢話。那你要不要對王雪紅說,
「你們外銷做不好沒關係,你還能在台灣賣牛肉麵啊!我們內需很夠。」

這是個可行的solution,但可能不是最好的選擇。


我們來提提台灣內需市場,真的很大嗎?

統一超商夠大吧?
王品集團夠大吧?
潤泰集團夠大吧?
義美食品夠大吧?

大不大其實見人見智,如果你待的產業環繞著民生需求,也覺得上述產業做的不錯,那我想對你來說夠大了。
但就像古時候的望族或土豪一樣,大多數環繞民生需求的產業都是早期進入者,早就卡了柴米油鹽醬醋茶等等需求,口袋鼓鼓後繼續想要怎麼進一步買下這個國家人民的生存權,例如土地之類的。這個情勢在發生什麼巨大的天災人禍之前不可能逆轉。

「好吧,我們活該出生晚」

於是年輕人這麼想,他們想跳脫紅海,開始創新、開發前所未有的需求。但是問題來了,「台灣市場好像有點小,新玩意的早期使用者太少,養不起團隊」。而且就算如預期成長了,上了年紀的投資人們也會說,「你們東西很有趣,但是只做台灣賺不了幾個錢」。於是一個在美國、大陸或許能大放異彩的點子、產品就這樣在台灣胎死腹中。
市場不夠大,所以我們也不可能創造出另一個facebook、twitter或paypal。

你說我看到這篇四不像的廢文氣不氣?


廢話,內需一直都在,只是拜託不要再故步自封了

台灣一直以來都是外銷國家,從香蕉、雨傘到IC,台灣沒有太多資源但是還是能創造許多經濟奇蹟,靠的就是外銷。
內需一定一直都在,但有限,尤其是人口和產能沒有明顯增加的情況下。舉超商或餐廳來說,一家開了、一家就倒,放眼整個台灣,這樣的平衡大致是成立的。如果以目前的市場自滿,那就.....


「雪紅,趕快收收回台灣賣牛肉麵吧」


2013年10月9日 星期三

Riak實戰(3) - Link Walking

Riak中有個頗強大的功能,叫做link walking。但是在介紹這個功能之前先讓我們來談談graph DB。


什麼是Graph DB?

在台灣或許不多人接觸過graph db,但是希望大家能多少了解一下他的設計概念及使用情境。
Graph DB主要的訴求就是要解決傳統關聯式資料庫所無法處理的關係搜尋。在Graph DB的架構下,每筆資料(document形式)就是一個node,node之間可以存在edge,這個edge儲存的就是node間的關係,如下圖:

(來源:http://readwrite.com/2011/04/20/5-graph-databases-to-consider)
這樣的架構不只直覺,也克服了某些特定架構下的搜尋效能問題。舉例來說,如果你希望搜尋的是「A的朋友的朋友的親人」,這樣的架構在傳統的關聯式資料庫的實作就很複雜,因為人與人之間的關係是多對多的,一定會有個table去儲存這些關係。在我們所提到的「A的朋友的朋友的親人」的使用情境下,我們至少會做 6 次的join!( A->關係->朋友->關係->朋友->關係->親人)
這樣操作並不直覺,效能也不甚理想,所以因應這種需求的Graph DB就出現了,例如:Neo4jTitanInfinite。當然除了這編列的三種以外,還有許多graph db,甚至有些是為了生物學或特定科學領域而誕生的。



Riak 的 Link Walking

我個人覺得 Riak 為這個機制的命名實在非常生動,怎說呢?
Riak在各筆資料之間可以建立一個「單向」的 Link,透過許多link的彼此連結,我們可以再這些資料節點上「散步(walking)」,例如:
GET localhost:8091/riak/people/kevinshu/_,friend,_/_,friend,_/_,relative,_/

從上面的例子中做的事是:

  1. 我們先進入 kevinshu 這個節點
  2. 再「走」到 kevinshu 的朋友
  3. 再「走」到 kevinshu 的朋友的朋友
  4. 再「走」到 kevinshu 的朋友的朋友的親人

於是這樣傳回的結果就是「 kevinshu 的朋友們的朋友們的所有親人們」,一個看似複雜的關係就這樣透過 Link Walking 找出來了。



Link Walking 的建立與query


建立Link:

$ curl -v -XPUT http://127.0.0.1:8091/riak/people/timoreilly \
  -H 'Link: </riak/people/dhh>; riaktag="friend"' \
  -H "Content-Type: text/plain" \
  -d 'I am an excellent public speaker.'
以上的範例除了建立了 davethomas 和 timoreilly 的link外,也用 "riaktag" 來定義這段關係,如 "friend", "relative", "employee"


Query:

$ curl -v http://127.0.0.1:8091/riak/people/timoreilly/{{bucket}},{{tag}},{{Keep}}

  • Bucket: 指定某個bucket,如果這個值是 _ (underscore),則比對所有的 bucket
  • Tag: 指定 riaktag 的值,如果這個值是 _ (underscore),則比對所有的 tag
  • Keep: 0 或 1,是否回傳這個階段的搜尋結果。以一開始提到的使用情境來說,如果我只也想知道 kevinshu 的「朋友」和「朋友的朋友」是誰,我就必須在這兩個query的  Keep 值定為 1。
    如果這個值是 _ (underscore),除了在最終階段的 query 視為 1 外,中間層的query則使用預設值 0 (不回傳)。這樣講不容易懂,直接看例子:
    /riak/people/kevinshu/_,friend,_(視為0)/_,friend,_(視為0)/_,relative,_(視為1)/



Reference


  • http://docs.basho.com/riak/latest/dev/using/link-walking/
  • http://neo4j.tw/understand_graph


2013年10月6日 星期日

Riak實戰(2) - MapReduce in javascript

原理

在接收到client端來的MapReduce要求後,會將request中的process散佈給cluster中的所有node。再將inputs所指定的資料從vnode中取出,再經過「Map」、「Reduce」兩個階段的處理。一一傳給Map function,再由Reduce function給收斂起來。


處理階段


Map

這個階段會依 inputs 所指定條件中算出所有符合的 bucket-key,vnode會依bucket-key找出這些data並將他丟到Map function中做運算,回傳結果(通常是Array)。


Reduce

這個階段所拿到的input就是在Map階段算完的結果的集合。舉例來說:A data 及 B data 經過Map function運算之後的結果分別是 [1,2,2,4] 和 [3,3,4,5],在進入Reduce階段前會先被結合成 [1,2,2,4,3,3,4,5] ,再跑Reduce function。
請注意,Reduce function通常會被重複的執行,以Riak來說至少會被執行兩次,也就是再各個node reduce一次,再將各個node reduce後的結果reduce起來,成為最終的輸出。




HTTP Query Syntax


由RESTful介面向Riak下mapreduce指令的方式是POST到cluster主機的 "mapred" path,例如:
POST http://localhost:8098/mapred

而POST過去的data大約長這樣:
{"inputs":[...inputs...],"query":[...query...],"timeout": 90000}


Inputs (String or Array):

我們在下query時,在inputs這個欄位可以輸入兩種資料格式:
1. 字串:代表我要對哪個bucket做mapreduce
2. 二維陣列:對某個bucket的某筆資料做mapreduce,例:
[ ["bucket1","key1"],["bucket2","key2"],["bucket3","key3"] ]
或是:
[ ["Bucket1","Key1","KeyData1"],["Bucket2","Key2","KeyData2"],["Bucket3","Key3","KeyData3"] ]


Query (Array):

這個參數內會放一個或多個分別屬於不同階段的Mapper、Reducer或Link,格式大約有這幾種:

  • {modfun, Module, Function} where Module and Function are atoms that name an Erlang function in a specific module.
  • {qfun,Fun} where Fun is a callable fun term (closure or anonymous function).
  • {jsfun,Name} where Name is a binary that, when evaluated in Javascript, points to a built-in Javascript function.
  • {jsanon, Source} where Source is a binary that, when evaluated in Javascript is an anonymous function.
  • {jsanon, {Bucket, Key}} where the object at {Bucket, Key} contains the source for an anonymous Javascript function.

如果想把Riak的MapReduce玩熟,上面這五種格式可能需要好好看過一遍。


先來看Mapper的簡單範例:
{ "map":{"language":"javascript","source":"function(v) { return [v]; }","keep":true} }
首先會用key註明這個是map()、reduce或link,在key對應的object中則記錄了是使用「什麼語言(language)」、「執行式(source)」以及「是否在最後的結果中顯現(keep)」。language和source應該不用特別解釋,keep是個布林值,代表在這個階段運算的結果要不要呈現在最終的運算結果裡面。通常只有在最後一個階段,才會將keep設為true。

TimeOut (Integer, optional):

Map/Reduce queries 預設 timeout 為 60000 milliseconds (60 seconds),當運算超過timeout時間的時候會回傳error。我們可以依需求覆寫他(in milliseconds)。


Key filters

Key filters 可以在MapReduce之前先對input資料做預處理(pre-process),在載入資料之前事先檢查是否通過檢查條件。

使用方法

將inputs的key_filters對應到一個array,riak將會依陣列中的條件順序進行篩選,如:
{
  "inputs":{
     "bucket":"invoices"
     "key_filters":[["tokenize", "-", 1],["eq", "basho"]]
   },
   // ...
}
以上面這個例子來說,riak會先將invoice這個bucket中每筆資料的key先以"-"符號做分割(tokenize),取得「第一個」字串區段,再將這個字串與"basho"進行「完全比對」(eq)。
我們接下來就來看看在key_filter中有哪些方法可以用:

tokenize

將key依傳入的符號進行分割,再取出某個區段,通常還會接著其他條件做篩選。
"key_filters":[["tokenize", "-", 1]]

to_lower

將key的每個字母字元都轉為小寫,通常還會接著其他條件做篩選。
"key_filters":[["to_lower"]]

eq

將key與傳入字串進行完全比對。
"key_filters":[["eq", "kevin"]]

between

提供一個區段進行比對
"key_filters":[["between", "20100101", "20101231"]]

matches

篩選key中包含傳入字串的資料,這樣的比對方式叫做「wildcard」,通常會有效能上的問題。
"key_filters":[["matches", "kevin"]]

ends_with

篩選key結尾為傳入字串的資料
"key_filters":[["ends_with", "riak"]]


範例

以下的範例就是做SQL中的distinct動作,Mapper單純將自身的資料以array的形式傳回,多個map的結果concate在一起後,由reducer做distinct的動作。

{ "inputs":"goog",
  "query":[{"map":{
                "language":"javascript",
                "source":"function(value){ return [ value ];}"}
           },
           {"reduce":{
                "language":"javascript",
                "source":"function(values){ 
                              var i=0, max=values.length, r=[];
                              for(i=0;i++;i<max) {
                                  if( r.indexof(values[i])==-1 ) {r.push(values[i]);}
                              }
                              return r;
                          }"
           }]
}

2013年10月5日 星期六

JavaScript 1.7 老梗新炒 - generator, iterator, array comprehension, let

ECMAscript 1.7 在2006年年底就已經release了,我是到了最近才碰到這個版本的一些「新功能」,分別有:
  • 產生迭代器(Iterator) 的 Generator
  • 陣列簡約式(Array comprehension)
  • Let表達式

這些功能在這篇Mozilla的技術文章中已經講的很清楚了,就不多做介紹。

現在才搞懂這些....

我果然還得多練練啊!


Riak實戰(1) - 3分鐘安裝與基本操作

在Mac OSX環境安裝Riak


首先,確認你的mac已經安裝了brew,然後執行:
brew install riak

此時我們可以試著啟動他看看:
riak start

riak預設啟動在8098 port,我們可以連到以下網址看看:
http://localhost:8098/
在啟動Riak時,你可能會看到這樣的警告:
 WARNING: ulimit -n is 256; 1024 is the recommended minimum. 
要怎麼改善呢?.....嗯.....還是自己看吧~


利用RESTful介面做CRUD

Riak的儲存機制是key-value的模式,在Riak底下有多個Bucket,可以把它想像成是SQL的Table。在Bucket下就是你要儲存的的資料了,用Key-Value的方式來做對應。
操作資料的方式如下:

讀取

GET http://localhost:8098/riak/bucket/key

儲存

PUT http://localhost:8098/riak/bucket/key

新增(隨機key)

POST http://localhost:8098/riak/bucket

刪除

DELETE http://localhost:8098/riak/bucket/key


各種環境的Client

除了使用curl,你也可以使用Riak的client端套件來操作Riak。官方以及相關社群已經提供了許多不同語言的client套件,可以在這裡找到:
http://docs.basho.com/riak/latest/dev/using/libraries/

2013年10月3日 星期四

Riak介紹及原理

Hadoop在台灣最近很HOT(一如往常的,台灣在資訊技術上總是慢了國外五年以上的時間),但是在survey相關資料時,發現Hadoop的學習曲線實在異常陡峭,而且如果我只想要分散式儲存及map/reduce機制,對HDFS這種專為大檔案設計的儲存機制沒有特別需求,但是HBase是depend on it的。如果考慮使用Hadoop作為解決方案的朋友可以先參考這篇文章再做決定:「你不需要 Hadoop 做数据分析的 10 个理由
如果需求相當簡單只需要做分散式儲存,很多NOSQL都能做得相當好而且非常容易做擴充,甚至可以提供MapReduce的機制,如MongoDB、CouchDB以及今天我要介紹的Riak


Riak的優點


1. 很容易架設cluster,做擴充
2. 支援 HTTP/RESTful 介面,操作方便
3. 支援Map/Reduce (JavaScript 或 Erlang)
4. 可以下Secondary Index,幫助搜尋(可以想像成是下tag)
5. 社群支援度高,各種語言的client套件齊全
6. Document詳細


Riak的儲存機制

Ring 環

Riak是採用buckets和keys來記錄和分類資料,舉例來說:「student(bucket) / kevin(key)」,這是我們表面看到的,但是在底層的實作上,Riak是將bucket和key做hash以後得到一個新的key(160-bit binary),再將所有的新key聚在一起,成為一個「Ring」。這個Ring會依你的設定被分割為多個Partition,每個partition都由一個vnode負責。

什麼是vnode?

Riak主打的就是他的延伸性,也就是能輕鬆的結成一個cluster。在這個cluster裡面每個Riak instance都是一個node,每個node上又跑了好幾個virtual node,也就是vnode,每個node都有差不多數量的vnode。舉例來說,我有一個4個node所組成的riak cluster,當我設定這個cluster的ring需要被切割為16個partitions時,整個cluster也會需要產生16個vnode來對應這些partition,在這個情況下,每個node會平均開啟8個vnode來認領(claim)這些partition。

資料備份數目

每個Bucket都可以設定他所儲存的資料要被保存幾份(N),以確保整個cluster的容錯能力。所以當有新資料要被插入或更新到這個cluster時,會看這筆資料所在的partition是屬於哪幾個vnode,將這個新增或更新的request PUT到這些vnode。
當要取得資料時,這個request也會被送到所屬的nodes。特別的是,這個request可以要求這個cluster必須在R個node都回應以後才將response吐回去。(R<=N)


Reference:


2013年9月27日 星期五

Rails 的 Counter Cache 機制

雖然很基本,但是一定要做一下記錄。
我們先想像一個情況:你目前的專案中,有user及comment兩種model:

class User < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :user
end

如果在一個頁面需要將使用者列表,並同時顯示他所擁有的回應數時,我們直覺上會在迴圈中,一個個進行user.comments.count,並同時產生一個db query,這樣的情況就是經典的「N+1」問題。


讓我們把counter cache住吧!

在系統架構上還滿常用到的一個手法是,將「讀取機會比寫入機會大很多」的數值記錄起來,例如這次會講到的counter cache,就是乾脆在user資料表上開一個comments_count欄位,在新增或刪除comment的同時,記錄他目前所擁有的comment數。
如果你對rails有一定程度的了解,應該會想到使用ActiveRecord callback的機制在新增或刪除comment後進行user.comments_count的更新。但是也因為這是一個還算常見的作法,Rails本身就內建了這個功能,能夠讓我們所做的工減少很多。


進入正題

1. 新增counter欄位

首先我們要先在user資料表中開一個專門儲存comment數的欄位,由於rails「Convention over configuration」的核心精神,這個欄位應該被命名為 [resource複數]_count ,否則rails可能會認不得他。以這個例子來說,就是要新增一個叫 comments_count 的欄位,並且預設值為0。新增欄位的過程就不贅述了。


2. 修改relation選項

接下來要做的是在comment.rb中的relation上加上一個選項::counter_cache ,告知Rails這個relation是要做counter的同步動作的。程式碼變成:

class Comment < ActiveRecord::Base
  belongs_to :user, :counter_cache => true
end

如果目前的專案增加這個欄位時,並沒有存在任何資料,也就是所有的user所擁有的comment是0,這樣的情況下其實這樣已經ok了。但如果之前就已經有一些user有創造comment,這時我們就要做counter的更新。


更新counter cache

可能有些人會想說,直接遍尋所有user,讓他們的 comments_count = user.comments.count 就好。
很不幸的,Rails對於counter欄位有保護機制,我們不能這樣直接修改他,而必須使用 update_counters(id, counters) 或 reset_counters(id, *counters) 兩種方法,以reset_counter來說,migration的程式碼就像這樣:

User.find_each do |user|
  User.reset_counters user.id, :comments
end