2013年10月20日 星期日

Riak實戰(4) - Secondary Indexes

NOSQL 不像 SQL,比較沒有 join 或針對某個 entry 的其中一個 attribute 做搜尋 (where) 的動作。通常都是以 key-value 的方式直接拿取資料,若遇到需要 join 的情況也往往是做多次的 query 拿到相關連的資料。反正概念上就是直接用 key 去對應 entry 或叫 document。
但是在使用 key 的過程中難免覺得彈性及可自定部分太少,使用起來不是這麼順手,於是 Riak 便有了 Second Indexes (2i) 的設計。

你可以將 2i 想成是 tag,讓你對特定的資料下標記,之後能更方便的 query 資料。例如我們可以從「engineers」這個 bucket 取出「language」設為「javascript」的資料。我們等等可以看看範例。


基本操作


新增並下 2i:

curl -X POST \
-H 'x-riak-index-twitter_bin: jsmith123' \
-H 'x-riak-index-email_bin: jsmith@basho.com' \
-d '...user data...' \
http://localhost:8098/buckets/users/keys/john_smith

然後這筆資料就有一個叫「twitter_bin」且值為「jsmith123」的secondary index;
以及一個叫「email_bin」且值為「jsmith@basho.com」的secondary index。


取資料 (Query):

取得特定 2i 的資料
curl http://localhost:8098/buckets/users/index/twitter_bin/jsmith123


進階操作


用範圍取資料 (Range) :

curl http://localhost:8098/buckets/users/index/field1_bin/val2/val4

範圍選取並回傳 2i (Range with terms):

curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/rock/rocl?return_terms=true'
當 return_terms=true 時,能夠連同被query到的 secondary index 一起返回


分頁 (Pagination):

相當好用,可以回傳一個token讓你再次取得下一頁的資料。
當 max_results 為 true 時,回傳結果會附上一個 continuation token,下一次的 query 只要附上這個 token,Riak 就知道要從什麼地方繼續提供資料給你。

curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/ri/ru?max_results=5&return_terms=true' | python -mjson.tool
{
    "continuation": "g2gCbQAAAAdyaXBqYWtlbQAAABIzNDkyMjA2ODcwNTcxMjk0NzM=",
    "results": [
        {
            "rice": "349222574510710785"
        },
        {
            "rickross": "349222868095217664"
        },
        {
            "ridelife": "349221819552763905"
        },
        {
            "ripjake": "349220649341952001"
        },
        {
            "ripjake": "349220687057129473"
        }
    ]
}

# Take the continuation value from the previous result set and feed it back into the query
curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/ri/ru?continuation=g2gCbQAAAAdyaXBqYWtlbQAAABIzNDkyMjA2ODcwNTcxMjk0NzM=&max_results=5&return_terms=true' | python -mjson.tool
{
    "continuation": "g2gCbQAAAAlyb2Jhc2VyaWFtAAAAEjM0OTIyMzcwMjc2NTkxMjA2NQ==",
    "results": [
        {
            "ripjake": "349221198774808579"
        },
        {
            "ripped": "349224017347100672"
        },
        {
            "roadtrip": "349221207155032066"
        },
        {
            "roastietime": "349221370724491265"
        },
        {
            "robaseria": "349223702765912065"
        }
    ]
}


效能問題

官網上有提到,當你的cluster超過512 partitions時,會有比較明顯的效能問題,這點必須注意一下。


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: