顯示具有 pymongo 標籤的文章。 顯示所有文章
顯示具有 pymongo 標籤的文章。 顯示所有文章

2014年2月25日 星期二

[Linux] 從 MongoDB Standalone (Single-Noe) 到 MongoDB HA (Replica Set) 的線上轉移方式(without shutdown/offline) @ Ubuntu 12.04

不關機的轉換原理應該都差不多?例如把水管從 A 處改接到到 B 處,只是情境跟操作手法適不適用是需要考慮的重要因素,在此先敘述使用情境:
  • 一隻 PHP 定期收到資料後,把資料存在 /tmp 區,再透過 popen 在背景發動一隻 pymongo 程式讀取 /tmp  資料,由 pymongo client 連到 mongodb server 塞資料,塞完後就斷線的模式。所以 CGI 有自己的 PID,而 pymongo 寫的 tool 也有自己的 PID。
  • 資料的單純作 insert 為主,還沒有負責處理 query 的需求,故 mongodb 只要能確保資料有被記錄起來,沒有損失即可,但無法保證讓 Replica Set 的資料馬上就跟 Standalone 一致。
線上轉換原理:
  1. 架設 MongoDB Replica Set
  2. 將更改 pymongo 程式,將 mongodb server 位置改到 Replica Set 即可
  3. 將 Standalone MongoDB 用 mongoexport 匯出資料 table.json, table2.json, ...
  4. 將資料用 mongoimport 方式匯入到 Replica Set 之 PRIMARY node 即可
在上述的使用情境下,資料的收集就不會間斷,至於官方介紹的方式也可以參考一下,屬於把 mongod 關掉後重開:Convert a Standalone to a Replica Set

此外,一些心得:
  • 原先資料就使用系統 _id 管理,不覆蓋 _id ,而 _id 有時間資訊,匯入新的機器(Replica Set) 沒有問題
  • 匯入資料若碰到 _id 一樣時,會 skip 掉,所以不必擔心重複匯入的問題
  • 如果匯錯資料時,例如預計到 table 1,結果不小心匯到 table 2 時,假設兩種資料格式有關鍵的差異點,例如在 table 1 的 record 才有 {"helloworld":0} 的屬性,那就好辦,把這類 record 找出來刪掉即可

# 找尋 table2 中,record 裡 hellworld 屬性的資料筆數
firstset:PRIMARY> db.table2.find({helloworld: { $ne : null } }).count()
firstset:PRIMARY> db.table2.remove({helloworld: { $ne : null } })

2014年2月5日 星期三

[MongoDB] 透過 MapReduce 進行 Collections Join @ Ubuntu 12.04

使用 NOSQL 時,其先天設計無法提供像 MySQL Table Joins 等招數,此時的解法就是把兩個 tables 倒進一張 table 來解決。

以 MongoDB command line 操作,新增資料:

> use mydb
switched to db mydb
> db.t1.insert({"data":1, "data2":2, "data3":3})
> db.t1.insert({"data":4, "data2":5, "data3":6})
> db.t1.insert({"data":7, "data2":8, "data3":9})
> db.t1.find()
{ "_id" : ObjectId("52f1f9c7bf6317bb2615b216"), "data" : 1, "data2" : 2, "data3" : 3 }
{ "_id" : ObjectId("52f1f9cbbf6317bb2615b217"), "data" : 4, "data2" : 5, "data3" : 6 }
{ "_id" : ObjectId("52f1f9cebf6317bb2615b218"), "data" : 7, "data2" : 8, "data3" : 9 }
> db.t2.insert({"data4":3, "data5":2, "data6":1})
> db.t2.insert({"data4":6, "data5":5, "data6":4})
> db.t2.insert({"data4":9, "data5":8, "data6":7})
> db.t2.find()
{ "_id" : ObjectId("52f1f9d4bf6317bb2615b219"), "data4" : 3, "data5" : 2, "data6" : 1 }
{ "_id" : ObjectId("52f1f9d7bf6317bb2615b21a"), "data4" : 6, "data5" : 5, "data6" : 4 }
{ "_id" : ObjectId("52f1f9dbbf6317bb2615b21b"), "data4" : 9, "data5" : 8, "data6" : 7 }


定義 t1 跟 t2 的 mapper function:

> t1_map = function() {
emit(this.data3, { "data": this.data, "data2": this.data2, "data3": this.data3 });
}
> t2_map = function() {
emit(this.data4, { "data4": this.data4, "data5": this.data5, "data6": this.data6 });
}


定義 reducer function:

> reducer = function(key, values){
var result = {} ;
values.forEach( function(value){
var field;
for(field in value)
if( value.hasOwnProperty(field) )
result[field] = value[field];
});
return result;
}


進行 map-reduce:

> db.tmp.drop()
true
> db.t1.mapReduce(t1_map, reducer , {"out":{"reduce":"tmp"}} )
{
        "result" : "tmp",
        "timeMillis" : 10,
        "counts" : {
                "input" : 3,
                "emit" : 3,
                "reduce" : 0,
                "output" : 3
        },
        "ok" : 1,
}
> db.tmp.find()
{ "_id" : 3, "value" : { "data" : 1, "data2" : 2, "data3" : 3 } }
{ "_id" : 6, "value" : { "data" : 4, "data2" : 5, "data3" : 6 } }
{ "_id" : 9, "value" : { "data" : 7, "data2" : 8, "data3" : 9 } }
> db.t2.mapReduce(t2_map, reducer , {"out":{"reduce":"tmp"}} )
{
        "result" : "tmp",
        "timeMillis" : 6,
        "counts" : {
                "input" : 3,
                "emit" : 3,
                "reduce" : 0,
                "output" : 3
        },
        "ok" : 1,
}
> db.tmp.find()
{ "_id" : 3, "value" : { "data4" : 3, "data5" : 2, "data6" : 1, "data" : 1, "data2" : 2, "data3" : 3 } }
{ "_id" : 6, "value" : { "data4" : 6, "data5" : 5, "data6" : 4, "data" : 4, "data2" : 5, "data3" : 6 } }
{ "_id" : 9, "value" : { "data4" : 9, "data5" : 8, "data6" : 7, "data" : 7, "data2" : 8, "data3" : 9 } }


以上的概念就是 t1 採用 data3 作為 join key,而 t2 採用 data4 ,分別跑 map-reduce 後儲存在 tmp 這張表(collection),如此 tmp 即為常用的 SQL Join 結果。

以上須留意的是跑 map-reduce 時,要指定 out 為 reduce 形態,細節可以在 out Options 查看,可分成 replace, reduce, merge ,若沒指定為 reduce 的話,上述最後結果僅有 t2 的資料。

此外,在 pymongo 上也能達成,有興趣可以參考 map-reduce-join.py,寫的彈性一點,所以閱讀性較差,用法:

$ python map-reduce-join.py --host localhost --database mydb --reset-result --result tmp --show-result --select-out-1 data data2 data3 --join-key-1 data3 --select-out-2 data4 data5 data6 --join-key-2 data4

結果:

Mapper 1 Code:
                function() {
                        emit(this.data3, {"data": this.data, "data2": this.data2, "data3": this.data3});
                }

Reducer 1 Code:
                function(key, values) {
                        var out = {};
                        values.forEach( function(value) {
                                for ( field in value )
                                        if( value.hasOwnProperty(field) )
                                                out[field] = value[field];

                        });
                        return out
                }

{u'counts': {u'input': 3, u'reduce': 0, u'emit': 3, u'output': 3}, u'timeMillis': 34, u'ok': 1.0, u'result': u'tmp'}
{u'_id': 3.0, u'value': {u'data': 1.0, u'data3': 3.0, u'data2': 2.0}}
{u'_id': 6.0, u'value': {u'data': 4.0, u'data3': 6.0, u'data2': 5.0}}
{u'_id': 9.0, u'value': {u'data': 7.0, u'data3': 9.0, u'data2': 8.0}}
Mapper 2 Code:
                function() {
                        emit(this.data4, {"data4": this.data4, "data5": this.data5, "data6": this.data6});
                }

Reducer 2 Code:
                function(key, values) {
                        var out = {};
                        values.forEach( function(value) {
                                for ( field in value )
                                        if( value.hasOwnProperty(field) )
                                                out[field] = value[field];
                        });
                        return out
                }

{u'counts': {u'input': 3, u'reduce': 0, u'emit': 3, u'output': 3}, u'timeMillis': 5, u'ok': 1.0, u'result': u'tmp'}
{u'_id': 3.0, u'value': {u'data5': 2.0, u'data4': 3.0, u'data6': 1.0, u'data': 1.0, u'data3': 3.0, u'data2': 2.0}}
{u'_id': 6.0, u'value': {u'data5': 5.0, u'data4': 6.0, u'data6': 4.0, u'data': 4.0, u'data3': 6.0, u'data2': 5.0}}
{u'_id': 9.0, u'value': {u'data5': 8.0, u'data4': 9.0, u'data6': 7.0, u'data': 7.0, u'data3': 9.0, u'data2': 8.0}}

2014年1月24日 星期五

[MongoDB] 使用 PyMongo 把玩 MapReduce @ Ubuntu 12.04

稍微接觸了一下 MongoDB 後,對此感到無比的興奮?與 Hadoop 相比,感覺上手度滿高的,不過,使用 PyMongo 的情況下,還是需要比較適合熟悉 Javascript 的人,因為 Mapper 跟 Reducer 的撰寫仍是用 Javascript 的,更正確來說是 BSON's JavaScript code type.

既然是 MapReduce,那就先來個 word count 吧!說真的,word count 也是我學 Hadoop 時跑的第一個範例。

簡單試了一下(文字來源:Taiwan wiki - Names):

$ python import.py -
{"field":"There are various names for the island of Taiwan in use today, derived from explorers or rulers by each particular period. The former name Formosa (福爾摩沙) dates from 1544, when Portuguese sailors sighted the main island of Taiwan and named it Ilha Formosa, which means \"Beautiful Island\".[21] In the early 17th century, the Dutch East India Company established a commercial post at Fort Zeelandia (modern Anping, Tainan) on a coastal islet called \"Tayouan\" in the local Siraya language; the name was later extended to the whole island as \"Taiwan\".[22] Historically, \"Taiwan\" has also been written as 大灣, 臺員, 大員, 臺圓, 大圓 and 臺窩灣."}
{"field":"The official name of the state is the \"Republic of China\"; it has also been known under various names throughout its existence. Shortly after the ROC's establishment in 1912, while it was still located on the Asian mainland, the government used the abbreviation \"China\" (\"Zhongguó\") to refer to itself. During the 1950s and 1960s, it was common to refer to it as \"Nationalist China\" (or \"Free China\") to differentiate it from \"Communist China\" (or \"Red China\").[23] It was present at the UN under the name \"China\" until 1971, when it lost its seat to the People's Republic of China. Since then, the name \"China\" has been commonly used internationally to refer only to the People's Republic of China.[24] Over subsequent decades, the Republic of China has become commonly known as \"Taiwan\", after the island that composes most of its territory. The Republic of China participates in most international forums and organizations under the name \"Chinese Taipei\" due to diplomatic pressure from the PRC. For instance, it is the name under which it has competed at the Olympic Games since 1984, and its name as an observer at the World Health Organization.[25]"}
Import:  2
[ObjectId('52e265a69c3fe514be2534fe'), ObjectId('52e265a69c3fe514be2534ff')]


$ python map-reduce-word-count.py --show-result --delete-result
Collection(Database(MongoClient('localhost', 27017), u'db'), u'tmp_2014-01-24_131025')
{u'_id': u'1544', u'value': {u'count': 1.0}}
{u'_id': u'17th', u'value': {u'count': 1.0}}
{u'_id': u'1912', u'value': {u'count': 1.0}}
{u'_id': u'1950s', u'value': {u'count': 1.0}}
{u'_id': u'1960s', u'value': {u'count': 1.0}}
{u'_id': u'1971', u'value': {u'count': 1.0}}
{u'_id': u'1984', u'value': {u'count': 1.0}}
{u'_id': u'21', u'value': {u'count': 1.0}}
{u'_id': u'22', u'value': {u'count': 1.0}}
{u'_id': u'23', u'value': {u'count': 1.0}}
{u'_id': u'24', u'value': {u'count': 1.0}}
{u'_id': u'25', u'value': {u'count': 1.0}}
{u'_id': u';', u'value': {u'count': 1.0}}
{u'_id': u'Anping', u'value': {u'count': 1.0}}
{u'_id': u'Asian', u'value': {u'count': 1.0}}
{u'_id': u'Beautiful', u'value': {u'count': 1.0}}
{u'_id': u'China', u'value': {u'count': 12.0}}
{u'_id': u'Chinese', u'value': {u'count': 1.0}}
{u'_id': u'Communist', u'value': {u'count': 1.0}}
{u'_id': u'Company', u'value': {u'count': 1.0}}
{u'_id': u'During', u'value': {u'count': 1.0}}
{u'_id': u'Dutch', u'value': {u'count': 1.0}}
{u'_id': u'East', u'value': {u'count': 1.0}}
{u'_id': u'For', u'value': {u'count': 1.0}}


其中 mongodb-study/blob/master/tools/import.py 只是把資料輸入到 mongodb 中,預設 database = db, collection = test。比較重要的 mapper, reducer 則是寫在 mongodb-study/blob/master/tools/map-reduce-word-count.py 程式中。

而 map-reduce-word-count.py 中,比較重要的則是 mapper 與 reducer 的定義,雖然是用 pymongo ,但這邊仍是用 Javascript 描述的,這大概是 pymongo 的最大缺點吧?印象中翻到 Java 版的 MongoDB 操作,可直接用 Java 來撰寫,這點影響還滿大的,簡言之,用 Pymongo 要撰寫 mapper、reducer 就是得先會一點 Javascript 才行。

使用 pymongo 撰寫 mapper 的方式,有一個小提醒就是 this.field 等於可以取到原先 input 的資料,但是,這邊的 this.field 需要強制轉型,這樣才能接著用 split 切字出來,另外,也可以自定 func 來呼叫使用,如此一來,就變成只剩字串處理的技巧了 :)

mapper = Code (
"""
function() {
var func = {
'author':function() {
return 'changyy';
}
};
(“”+this.field).split(/[\s\[\],\(\)"\.]+/).forEach(function(v){
//emit(func.author(), 1 );
if(v && v.length )
emit(v, {'count':1});
} );
}
"""
)


reducer = Code(
"""
function(key, value) {
var total = 0;
for(var i = 0 ; i < value.length ; ++i ) {
total += value[i].count;
}
return {'count':total};
}
"""
)

2014年1月22日 星期三

[Linux] MongoDB 與 PyMongo 初體驗 @ Ubuntu 12.04, Linode

架設:

http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/

$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
$ echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
$ sudo apt-get update

http://www.mongodb.org/downloads
$ sudo apt-get install mongodb-10gen=2.4.9
$ echo "mongodb-10gen hold" | sudo dpkg --set-selections

$ sudo service mongodb restart
mongodb stop/waiting
mongodb start/running, process ######

$ mongo
MongoDB shell version: 2.4.9
connecting to: test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user


防火牆存取限制:

$ sudo iptables --list-rules
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT

$ sudo vim /etc/init.d/iptables-rule.sh
#!/bin/sh

# BIN
BIN_IPTABLES=`which iptables`

# reset rules
$BIN_IPTABLES -F
$BIN_IPTABLES -X
$BIN_IPTABLES -Z

# init policies
#$BIN_IPTABLES -P INPUT DROP
#$BIN_IPTABLES -P OUTPUT ACCEPT
#$BIN_IPTABLES -P FORWARD ACCEPT

# mongo db
$BIN_IPTABLES -A INPUT -j ACCEPT -p tcp --destination-port 27017 -s 127.0.0.1,IP1,IP2,IP3
# mongo db drop all
$BIN_IPTABLES -A INPUT -j REJECT -p tcp --destination-port 27017

$ sudo chmod 775 /etc/init.d/iptables-rule.sh
$ sudo update-rc.d -f iptables-rule.sh defaults


一些 mongo 常用指令:

顯示所有的 databases (SQL: show databases)
> show dbs

使用指定 collection (SQL: use dbname)
> use dbname

顯示目前 databases 中的所有 collections (SQL: show tables)
> show collections

更多對照指令:
SQL to MongoDB Mapping Chart
PHP: SQL to Mongo Mapping Chart


安裝 pymongo 套件:

$ git clone git://github.com/mongodb/mongo-python-driver.git pymongo
$ cd pymongo
$ sudo python setup.py install


使用 pymongo 新增範例:

from pymongo import MongoClient

client = MongoClient()
database = client[‘dbname’] # SQL: Database Name
collection = database[‘table’]   # SQL: Table Name

item = {"author":"changyy"}
collection.insert(item)