2016/1/25

如何在 CentOS 安裝 R 語言,如何產生 PDF Report

要在 CentOS 安裝 R 語言執行環境,如果已經安裝好了 EPEL,直接以 yum 安裝就好了。


首先搜尋 R-project 相關的套件


yum search R-project

然後安裝需要的套件


yum -y install R.x86_64 R-devel.x86_64 R-java.x86_64 R-java-devel.x86_64

如果要自己編譯 R,必須要先下載套件(必須有 --enable-R-shlib,否則安裝 RStudio-server 會遇到問題)


yum -y install readline-common readline-devel.x86_64 texinfo texlive

tar zxvf R-3.2.2.tar.gz
cd R-3.2.2
./configure --prefix=/opt/R-3.2.2 --enable-R-shlib --with-libpng --with-jpeglib --with-libtiff --with-x
make
make install

執行 configure 時,會抱怨


configure: WARNING: neither inconsolata.sty nor zi4.sty found: PDF vignettes and package manuals will not be rendered optimally

簡單測試一下,首先編輯一個檔案 test.r


#!/usr/bin/Rscript
print("HelloR")

修改執行的權限: chmod 755 test.r,有兩種方式可以執行 R Script


# 方法1
Rscript test.r
# 方法2
./test.r

在 windows 安裝 R 並測試產生 PDF


我們先在 windows 環境測試如何產生 PDF。


為了要處理文件中以及圖表中的中文顯示問題,還必須做一些額外的設定,在設定過程中,如果有遇到 MiKTeX 或是 RStudio 需要更新套件,就直接接受更新就可以了。


  1. 安裝 R 以及 RStudio
  2. 安裝 rmarkdown, devtools, showtext


    install.packages("rmarkdown")
    install.packages("devtools")
    install.packages("showtext")
  3. 安裝 rticles


    devtools::install_github("rstudio/rticles")
  4. 安裝 MiKTeX
    安裝後要到選單 (MilkTex 2.9 -> Maintenance(Admin) -> Update(Admin) 更新套件

  5. 打開 RStudio
    Tools -> Global Options -> Sweave
    將 "Weave Rnw Files using" 改為 "knitr"
    將 "Typeset LaTex into PDF using" 改為 "XeLaTex"

  6. File -> New File -> R Markdown
    Default Ouput Format 選擇 PDF
    假設存檔是存成 test.Rmd,在該檔案的同一個目錄中,建立一個新檔案 header.tex,編碼為 UTF-8,以下為內容,如果把 標楷體 改成 simsun.ttc 就會變成宋體


    \usepackage{xeCJK}
    \setCJKmainfont{標楷體}    % 可換字型 NSimSun
  7. 在 test.Rmd 中,調整檔案的內容如下


    ---
    title: "test"
    output:
      pdf_document:
        keep_tex: yes
        latex_engine: xelatex
        includes:
          in_header: header.tex
      html_document: default
      word_document: default
    ---
    
    file content: 中文
    ```{r fig.showtext=TRUE}
    summary(cars)
    print("中文")
    plot(cars,main='中文issue')
    plot(cars,main='中文issue2')
    ```
    
    ```{r, echo=FALSE}
    plot(cars)
    ```
  8. 為了控制報表輸出的目錄及檔案名稱,可以搭配 setwd 及 rmarkdown::render 的一些參數使用


    library("rmarkdown")
    setwd("D:/git/project/r")
    rmarkdown::render(input="test.Rmd", output_format="all", output_file="20151201", output_dir="output", encoding="UTF-8")

Ref: showtext package integration


在 CentOS 7 測試產生 PDF


為了在 CentOS 7 產生 PDF Report 我們必須先安裝一些套件


  1. 安裝 rstudio-server,因為 pandoc 是產生 PDF 常用的指令,所以直接在 /usr/bin/pandoc 產生一個連結


    wget https://download2.rstudio.org/rstudio-server-rhel-0.99.489-x86_64.rpm
    yum install --nogpgcheck rstudio-server-rhel-0.99.489-x86_64.rpm
    ln -s /usr/lib/rstudio-server/bin/pandoc/pandoc /usr/bin/pandoc
  2. 安裝 textlive 及相關套件


    yum -y install texlive texlive-latex texlive-xetex
    yum -y install texlive-collection-latex
    yum -y install texlive-collection-latexrecommended
    yum -y install texlive-xetex-def

    因為在測試過程中,一直發生一些套件缺少的錯誤訊息,所以直接把所有 textlive 的相關套件都安裝進去。


    yum -y install 'texlive-*'
  3. 因為測試時發生以下的 titling.sty 錯誤,但在 CentOS 卻沒有這個套件,所以根據 Missing *.sty files when generating PDFs #359 的建議,直接安裝 titling


    wget http://mirrors.ctan.org/macros/latex/contrib/titling.zip
    unzip titling.zip
    cd titling
    latex titling.ins
    sudo mkdir -p /usr/share/texlive/texmf-dist/tex/latex/titling
    sudo cp titling.sty /usr/share/texlive/texmf-dist/tex/latex/titling/
    sudo texhash
  4. 處理 linux 的 fonts
    通常習慣使用 windows 字型,我們可以將 c:\windows\fonts 裡面的幾個常用字型上傳到 linux


    mkdir /usr/share/fonts

    然後將 simsun.ttc, msyh.ttf, msyhbd.ttf, sumhei.ttf 上傳到這個資料夾


    用以下指令更新 linux fonts


    fc-cache -f -v

    如果有更新成功,可以在這個指令的輸出結果中,看到中文字型


    fc-list
  5. 將剛剛測試的 header.tex 以及 test.Rmd 上傳到 linux 任意一個資料夾
    但要注意的是,header.tex 只能填寫剛剛有上傳的字型名稱,不能填寫為字型的檔案名稱


    \usepackage{xeCJK}
    \setCJKmainfont{NSimSun}
  6. 進入 R console
    在 text.Rmd 以及 header.tex 的目錄中,進入 R console 的命令列中,安裝 rmarkdown以及 showtext,然後用 rmarkdown render PDF


    install.packages("rmarkdown")
    install.packages("showtext")
    
    library("rmarkdown")
    rmarkdown::render("test.Rmd")

    測試過程如下:


    > rmarkdown::render("test.Rmd")
    processing file: test.Rmd
      |...........                                                      |  17%
      ordinary text without R code
    
      |......................                                           |  33%
    label: unnamed-chunk-1 (with options) 
    List of 1
     $ fig.showtext: logi TRUE
    
      |................................                                 |  50%
      ordinary text without R code
    
      |...........................................                      |  67%
    label: unnamed-chunk-2 (with options) 
    List of 1
     $ echo: logi FALSE
    
      |......................................................           |  83%
      ordinary text without R code
    
      |.................................................................| 100%
    label: unnamed-chunk-3 (with options) 
    List of 1
     $ echo: logi FALSE
    
    output file: test.knit.md
    
    /usr/bin/pandoc +RTS -K512m -RTS test.utf8.md --to latex --from markdown+autolink_bare_uris+ascii_identifiers+tex_math_single_backslash-implicit_figures --output test.tex --template /usr/lib64/R/library/rmarkdown/rmd/latex/default.tex --highlight-style tango --latex-engine xelatex --include-in-header header.tex --variable 'geometry:margin=1in' 
    /usr/bin/pandoc +RTS -K512m -RTS test.utf8.md --to latex --from markdown+autolink_bare_uris+ascii_identifiers+tex_math_single_backslash-implicit_figures --output test.pdf --template /usr/lib64/R/library/rmarkdown/rmd/latex/default.tex --highlight-style tango --latex-engine xelatex --include-in-header header.tex --variable 'geometry:margin=1in' 
    
    Output created: test.pdf
  7. 最後我們就能在 linux 得到裡面有中文字的 PDF Report

  8. 如果在 render 時加上 "all" 的參數,就可以同時取得 html, docx 及 pdf 三種格式的文件


    rmarkdown::render("test.Rmd", "all")

2016/1/18

Geospatial Index in MongoDB

MongoDB 自 2.6 版開始就支援儲存座標點的欄位,並能為該欄位建立 Geospatial Indexes,提供使用者快速搜尋附近的座標點資料的資料查詢功能。例如最簡單的應用:找到我附近的夥伴或是找到家裡附近的餐廳。


座標點 [longitude, latitude]


座標點的資料是兩個浮點數字組成的,先寫 longitude 經度,再寫 latitude 緯度,在思考座標點資料時,必須要用平面座標來看,原點在正中央,x軸往右為正數,y軸往上為正數,這跟跟矩陣的元素表示方法不同,先列後行。


[longitude, latitude]

基本 2d 平面查詢


要測試最基本的平面查詢,我們先產生 100 個座標點的測試資料,其實座標點資料就只是單純的兩個數字而已,重要的是,在建立 index 時,要指定用 2d 的 index:


db.ex.drop()
for( var i=0; i<100; i++ ) {
    db.ex.insert({pos:[i%10, Math.floor(i/10)]})
}

db.ex.ensureIndex({pos:"2d"})

用 $near 查詢時,回傳的資料點會依照距離遠近的順序傳送回來。


> db.ex.find({pos:{$near:[5,5]}})
{ "_id" : ObjectId("565e634fbd3fd37d48c0a691"), "pos" : [ 5, 5 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a690"), "pos" : [ 4, 5 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a69b"), "pos" : [ 5, 6 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a687"), "pos" : [ 5, 4 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a692"), "pos" : [ 6, 5 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a686"), "pos" : [ 4, 4 ] }
.....

以下這個圖形將所有座標點都以一個圓點表示,原點[0,0]位於左下角。


以下是有距離範圍的查詢,但回傳的資料點,並不保證依照距離由小到大排列


position:{$within:[$center, $box, $ploygon, $centerSphere]}

首先是 $center 的測試,我們測試一個半徑為 2 的圓形涵蓋的範圍:


> db.ex.find( {pos:{$within:{$center:[[5,5],2]}}} , {_id:0} )
{ "pos" : [ 4, 4 ] }
{ "pos" : [ 3, 5 ] }
{ "pos" : [ 4, 5 ] }
{ "pos" : [ 5, 3 ] }
{ "pos" : [ 5, 4 ] }
{ "pos" : [ 5, 5 ] }
{ "pos" : [ 4, 6 ] }
{ "pos" : [ 5, 6 ] }
{ "pos" : [ 5, 7 ] }
{ "pos" : [ 6, 4 ] }
{ "pos" : [ 6, 5 ] }
{ "pos" : [ 7, 5 ] }
{ "pos" : [ 6, 6 ] }


$box 是一個矩形


> db.ex.find( {pos:{$within:{$box:[[5,5],[6,6]]}}} , {_id:0} )
{ "pos" : [ 5, 5 ] }
{ "pos" : [ 5, 6 ] }
{ "pos" : [ 6, 5 ] }
{ "pos" : [ 6, 6 ] }


$polygon 則是一個多邊形


> db.ex.find( {pos:{$within:{$polygon:[[3,4],[5,7], [7,3]]}}} , {_id:0} )
{ "pos" : [ 3, 4 ] }
{ "pos" : [ 4, 4 ] }
{ "pos" : [ 4, 5 ] }
{ "pos" : [ 5, 4 ] }
{ "pos" : [ 5, 5 ] }
{ "pos" : [ 5, 6 ] }
{ "pos" : [ 5, 7 ] }
{ "pos" : [ 6, 4 ] }
{ "pos" : [ 7, 3 ] }
{ "pos" : [ 6, 5 ] }


Geospatial Indexes


剛剛的 2d 查詢,看起來還蠻簡單的,但 MongoDB 的 Geospatial Indexes 不光只支援這個功能而已。


MongoDB 支援兩種平面的模型 surface type
1. Spherical: 2dsphere index
座標點是放在像地球一樣的球面上,由於是球面的關係,距離跟相對關係的計算,跟一般的平面座標是不同的
2. Flat: 2d index
傳統的平面座標


如果是一般的 2d index,座標點的儲存只支援 legacy coordinate pairs的表示方式,也就是 [x,y],前面是 longitude 經度,後面是 latitude 緯度。


但如果是 2dsphere index,除了支援 legacy coordinate pairs 的資料外,還可以用 GeoJSON object 的表示方式儲存,預設是以 WGS84 datum,也就是 Degrees, Minutes, Seconds 的地理位置表示方式。


WGS 84 座標點表示系統有五種,可以用 PGC Coordinate Converter 進行轉換的計算,比較常見的有兩種 DMS 及 DD,但要注意的是 GeoJSON 預設是先寫緯度,再寫經度,但 MongoDB 卻是前面是 longitude 經度,後面是 latitude 緯度。


  1. DMS (Degrees, Minutes, Seconds)
    ex: 24° 15' 28.0800" N, 120° 53' 36.6000" E
  2. DD (Decimal Degrees)
    ex: 24.2578, 120.8935

GeoJSON 是以 JSON 表示地理資料結構的格式,由兩個部份組成:


  1. type:
    有以下這些類型 Point, LineString, Polygon, MultiPoint, MultiLineString, and MultiPolygon, GeometryCollection

  2. coordinates
    是這個類型資料的座標點的集合


我們可以透過 http://geojsonlint.com/ 測試 GeoJSON,並將結果顯示在地圖上。


// Point
{
  "type": "Point",
  "coordinates": [
    120.8935,  
    24.2578 
  ]
}

// LineString
{
  "type": "LineString",
  "coordinates": [
    [120.7000,24.2000],
    [120.6900,24.1900],
    [120.6815,24.1833],
    [120.5900,24.1500]
  ]
}


透過 GeoJSON Editor 用線跟多邊形,把文心森林公園框起來:


{
  "type" : "GeometryCollection",
  "geometries" : [
    {
      "type" : "Polygon",
      "coordinates": [
          [
            [
              120.64390238374472,
              24.146822125175643
            ],
            [
              120.64660605043173,
              24.146743804632557
            ],
            [
              120.64653094857931,
              24.14385570109112
            ],
            [
              120.64319428056479,
              24.144188570589225
            ],
            [
              120.64390238374472,
              24.146822125175643
            ]
          ]
        ]
    },
    {
      "type" : "LineString",
      "coordinates": [
          [
            120.64390238374472,
            24.14681233511038
          ],
          [
            120.64656313508749,
            24.146753594703075
          ],
          [
            120.64652021974325,
            24.14385570109112
          ],
          [
            120.64322646707296,
            24.144188570589225
          ],
          [
            120.64390238374472,
            24.14681233511038
          ]
        ]
     }
  ]
}


geospatial query operators


geospatial index 有兩種,geospatial index 不能當作 shard key index,而且在 sharded collection 中,不能使用 $near, $nearSphere,可以用 $geoNear aggregation 取代。


  • 2d
    支援平面幾何的計算
    legacy coordinate pairs [longitude, latitude]

  • 2dsphere
    支援球面的計算
    可儲存 GeoJSON 或是 legacy coordinate pairs


MongoDB 的 Query Selectors 有以下這4種


  1. $geoWithin
    Inclusion
    在 GeoJSON geomerty 的裡面的圖形,2d 及 2dsphere indexes 都有支援

  2. $geoIntersects
    Intersect
    跟 GeoJSON geomerty 的相交的圖形, 只有 2dsphere index 支援

  3. $near
    Proximity
    接近某個點的 geospatial objects,2d 及 2dsphere indexes 都有支援

  4. $nearSphere
    Proximity
    在球面上,接近某個點的 geospatial objects,2d 及 2dsphere 都支援


Geometry Specifiers 有以下這幾種


  1. $geometry
    以 GeoJSON 格式指定的 geometry
  2. $minDistance
    最短距離,用在 $near, $nearSphere,只能用在 2dsphere index
  3. $maxDistance
    最長距離,用在 $near, $nearSphere, 2d 及 2dsphere 都支援
  4. $center
    圓形,用在 $geoWithin queries 裡面, 2d 支援
  5. $centerSphere
    圓形,用在 $geoWithin queries 裡面,可用 [x,y] 或 GeoJSON 格式,2d 及 2dsphere 都支援
  6. $box
    矩形,用在 $geoWithin queries 裡面, 2d 支援
  7. $polygon
    用 [x,y] 描述的多邊形,用在 $geoWithin queries 裡面, 2d 支援

References


Geospatial Indexes and Queries
Geospatial Index Tutorials
Geospatial Indexing with MongoDB Presentation


如何基於mongodb來實現用戶當前位置距離顯示順序功能


MongoDB – Geospatial Queries


Spring Data – Part 4: Geospatial Queries with MongoDB

2016/1/11

Production Cluster Testing in MongoDB

基於 MongoDB 正式環境的一些基本要求,應該是要用三台機器來測試 MongoDB,但為了測試 mongodb driver 的 cluster 機制,又沒有那麼多實體的機器,這次先用單機來了解 client 由遠端連接 MongoDB 的一些狀況。


環境設定


一個由三個 replica set 組成的 shard server,三個 config server,三個 route server。


shard1: 3 replica sets
192.168.1.11:27017
192.168.1.11:28017
192.168.1.11:29017

3 config server
192.168.1.11:30000
192.168.1.11:31000
192.168.1.11:32000

3 route server
192.168.1.11:40000
192.168.1.11:41000
192.168.1.11:42000

shard1 的設定以及啟動:


mkdir -p /home/mongodb/shard/data/s1_1
mkdir -p /home/mongodb/shard/data/s1_2
mkdir -p /home/mongodb/shard/data/s1_3
mkdir -p /home/mongodb/shard/logs

/usr/share/mongodb/bin/mongod --port 27017 --fork --dbpath /home/mongodb/shard/data/s1_1 --logpath /home/mongodb/shard/logs/s1_1.log --logappend --shardsvr --replSet shard1 --directoryperdb

/usr/share/mongodb/bin/mongod --port 28017 --fork --dbpath /home/mongodb/shard/data/s1_2 --logpath /home/mongodb/shard/logs/s1_2.log --logappend --shardsvr --replSet shard1 --directoryperdb

/usr/share/mongodb/bin/mongod --port 29017 --fork --dbpath /home/mongodb/shard/data/s1_3 --logpath /home/mongodb/shard/logs/s1_3.log --logappend --shardsvr --replSet shard1 --directoryperdb

連接其中一台,並設定 shard1 的三個 replica set 成員


mongo --port 27017

config_shard1={
    _id: 'shard1',
    members: [
        {_id:0, host:'192.168.1.11:27017'},
        {_id:1, host:'192.168.1.11:28017'},
        {_id:2, host:'192.168.1.11:29017'}
    ]
};

rs.initiate(config_shard1);
rs.status();
rs.isMaster();

建立管理帳號 admin 以及連接 test database 的測試帳號 test


db.createUser({
    user: "admin",
    pwd: "pass",
    roles: [ { role: "root", db: "admin" } ]
});

use test
db.createUser(
    {
      user: "test",
      pwd: "pass",
      roles: [
         { role: "readWrite", db: "test" }
      ]
    }
);
db.getUsers();

啟動三個 config servers


mkdir -p /home/mongodb/shard/data/config1
mkdir -p /home/mongodb/shard/data/config2
mkdir -p /home/mongodb/shard/data/config3

/usr/share/mongodb/bin/mongod --configsvr --fork --port 30000 --dbpath /home/mongodb/shard/data/config1 --logpath /home/mongodb/shard/logs/config1.log --logappend

/usr/share/mongodb/bin/mongod --configsvr --fork --port 31000 --dbpath /home/mongodb/shard/data/config2 --logpath /home/mongodb/shard/logs/config2.log --logappend

/usr/share/mongodb/bin/mongod --configsvr --fork --port 32000 --dbpath /home/mongodb/shard/data/config3 --logpath /home/mongodb/shard/logs/config3.log --logappend

啟動三個 route servers


/usr/share/mongodb/bin/mongos --port 40000 --configdb 192.168.1.11:30000,192.168.1.11:31000,192.168.1.11:32000 --fork  --logpath /home/mongodb/shard/logs/route1.log --logappend --chunkSize 1

/usr/share/mongodb/bin/mongos --port 41000 --configdb 192.168.1.11:30000,192.168.1.11:31000,192.168.1.11:32000 --fork  --logpath /home/mongodb/shard/logs/route2.log --logappend --chunkSize 1

/usr/share/mongodb/bin/mongos --port 42000 --configdb 192.168.1.11:30000,192.168.1.11:31000,192.168.1.11:32000 --fork  --logpath /home/mongodb/shard/logs/route3.log --logappend --chunkSize 1

連接到 route server,設定 shard1 並設定 sharding 的 database


mongo --host 192.168.1.11 --port 40000

use admin
db.runCommand({addshard:"shard1/192.168.1.11:27017,192.168.1.11:28017,192.168.1.11:29017"});
db.runCommand({enablesharding:"test"});
db.runCommand({shardcollection:"test.users", key:{_id:1}});
db.runCommand({shardcollection:"test.doc", key:{_id:1}});

直接測試新增 500000 筆資料


for(var i=1;i<500000; i++) {
    db.users.insert({
        userid:"user_"+i,
        username:"name_"+i,
        age: NumberInt(_rand()*100)
    })
}

use test
db.users.stats();

MongoDB Client Drivers


MongoDB 官方提供的 client driver libary 很完整地支援了多種語言:C, C++, C#, Java, Node.js, Perl, PHP, Python, Motor, Ruby, Scala,另外有兩個社群提供的 driver:Go, Erlang。


使用 driver 之前,最重要的是知道連接 MongoDB 的 connection string。


格式為:


mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

參考 node.js 的 mongoclient 說明頁面,有比較完整的 options 的設定項目。


因為我們建立了三個 route server,因此連線的 uri 填寫為以下的 connection string。


mongodb://192.168.1.11:40000,192.168.1.11:41000,192.168.1.11:42000/test?maxPoolSize=20

我們就利用 scala driver 的 QuickTour Sample,記得要同時把 Helpers.scala 以及 QuickTour.scala 都取回來編譯。


首先建立一個 scala sbt project,然後在 build.sbt 裡面加上一行 mongodb scala driver,接下來把剛剛的 Helper.scala 及 QuickTour.scala 放進 scala soruce 裡面,就可以了。


name := "mongodb"

version := "1.0"

scalaVersion := "2.11.7"

libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "1.0.0"

接下來只擷取部份的程式碼,也就是我們修改的部份:


首先我們修改 mongodb 的 connection string,連接到三個 route server


    val mongoClient:MongoClient = MongoClient("mongodb://192.168.1.11:40000,192.168.1.11:41000,192.168.1.11:42000/test?maxPoolSize=20")

剛剛我們把 database:test, collection: doc 加入 shardcollection 中


    // get handle to "mydb" database
    val database: MongoDatabase = mongoClient.getDatabase("test")

    // get a handle to the "test" collection
    val collection: MongoCollection[Document] = database.getCollection("doc")

然後是清除 collection,並測試插入一筆資料


    collection.drop().results()

    // make a document and insert it
    val doc: Document = Document("_id" -> 0, "docid" -> "test", "type" -> "database",
      "count" -> 1, "page" -> Document("x" -> 100, "y" -> 200))
    collection.insertOne(doc).results()
    // get it (since it's the only one in there since we dropped the rest earlier on)
    collection.find.first().printResults()

讓 Thread 暫停 30 秒,因為我們想要測試在執行到一半,把其中一台 route server 關掉的狀況。


    Thread sleep 30*1000

當我們執行 QuickTour.scala 的時候,console 會列印以下的資訊:


資訊: Monitor thread successfully connected to server with description ServerDescription{address=192.168.1.11:41000, type=SHARD_ROUTER, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 0, 7]}, minWireVersion=0, maxWireVersion=3, electionId=null, maxDocumentSize=16777216, roundTripTimeNanos=39787239}
十一月 20, 2015 5:25:53 下午 com.mongodb.diagnostics.logging.JULLogger log
資訊: Opened connection [connectionId{localValue:4}] to 192.168.1.11:40000
十一月 20, 2015 5:25:53 下午 com.mongodb.diagnostics.logging.JULLogger log
資訊: Opened connection [connectionId{localValue:5}] to 192.168.1.11:42000
{ "_id" : 0, "name" : "MongoDB", "type" : "database", "count" : 1, "axis" : { "x" : 100, "y" : 200 } }

這時候程式會因為 Thread sleep 30*1000 而暫停下來。
接下來有30s 的時間,可以連線到 port 40000 的 route server,並把這個 route server 關掉。


mongo --port 40000

use admin
db.shutdownServer();

當我們把 route server 關掉的瞬間,scala 程式的 console 就會馬上發現,192.168.1.11:40000 這個 route server 現在有問題,沒辦法使用了。


十一月 20, 2015 5:26:04 下午 com.mongodb.diagnostics.logging.JULLogger log
資訊: Exception in monitor thread while connecting to server 192.168.1.11:40000
com.mongodb.MongoException: java.io.IOException: 遠端電腦拒絕網路連線。

    at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:125)
    at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:141)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.IOException: 遠端電腦拒絕網路連線。

    at sun.nio.ch.Iocp.translateErrorToIOException(Iocp.java:309)
    at sun.nio.ch.Iocp.access$700(Iocp.java:46)
    at sun.nio.ch.Iocp$EventHandlerTask.run(Iocp.java:399)
    ... 1 more

接下來的程式中,當我們要繼續使用 mongodb,client driver 會自動選擇下一個 route server: 192.168.1.11:41000,然後繼續執行後面的程式。


十一月 20, 2015 5:26:23 下午 com.mongodb.diagnostics.logging.JULLogger log
資訊: Opened connection [connectionId{localValue:8}] to 192.168.1.11:41000
total # of documents after inserting 100 small ones (should be 101):  101

MongoDB 的 driver 真的做得很完整,不但有內建 connection pool 還有自動 fail over 的能力。

2016/1/4

How to Backup & Retore in MangoDB


以下測試如何備份(mongodump)以及還原(mongorestore) MongoDB 資料庫。


備份 mongodump


在備份資料前,必須先讓在記憶體中的資料同步到磁碟中(fsync),並將資料庫上鎖(lock),避免備份過程中,還有資料異動。


use admin
db.runCommand({fsync:1, lock:1})

查詢 DB 的狀態,看看是不是上鎖了


db.currentOp()

然後以 mongodump 進行資料備份


mkdir -p /home/mongodb/project/data/backup
mongodump -d testdatabase -o /home/mongodb/project/data/backup

也可以直接備份遠端的 DB


mongodump -h 192.168.1.11 -p 37017 -d testdatabase -o /home/mongodb/project/data/backup

備份完成後,要解鎖資料庫


db.fsyncUnlock()

執行過程如下:


> use admin
switched to db admin
> db.runCommand({fsync:1, lock:1})
{
    "info" : "now locked against writes, use db.fsyncUnlock() to unlock",
    "seeAlso" : "http://dochub.mongodb.org/core/fsynccommand",
    "ok" : 1
}


> db.currentOp()
{
    "inprog" : [ ],
    "fsyncLock" : true,
    "info" : "use db.fsyncUnlock() to terminate the fsync write/snapshot lock"
}

在另一個 terminal 進行備份


[root@server backup]# /usr/share/mongodb/bin/mongodump -d testdatabase -o /home/mongodb/project/data/backup
2015-11-30T14:43:18.585+0800    writing testdatabase.tracker to /home/mongodb/project/data/backup/testdatabase/tracker.bson
2015-11-30T14:43:18.585+0800    writing testdatabase.user to /home/mongodb/project/data/backup/testdatabase/user.bson
2015-11-30T14:43:18.594+0800    writing testdatabase.user metadata to /home/mongodb/project/data/backup/testdatabase/user.metadata.json
2015-11-30T14:43:18.612+0800    done dumping testdatabase.user (1000 documents)
2015-11-30T14:43:18.612+0800    writing testdatabase.system.indexes to /home/mongodb/project/data/backup/testdatabase/system.indexes.bson
2015-11-30T14:43:18.612+0800    writing testdatabase.event to /home/mongodb/project/data/backup/testdatabase/event.bson
2015-11-30T14:43:18.612+0800    writing testdatabase.event metadata to /home/mongodb/project/data/backup/testdatabase/event.metadata.json
2015-11-30T14:43:18.612+0800    done dumping testdatabase.event (0 documents)
2015-11-30T14:43:18.612+0800    writing testdatabase.eventlog to /home/mongodb/project/data/backup/testdatabase/eventlog.bson
2015-11-30T14:43:18.613+0800    writing testdatabase.eventlog metadata to /home/mongodb/project/data/backup/testdatabase/eventlog.metadata.json
2015-11-30T14:43:18.614+0800    done dumping testdatabase.eventlog (0 documents)
2015-11-30T14:43:21.120+0800    writing testdatabase.tracker metadata to /home/mongodb/project/data/backup/testdatabase/tracker.metadata.json
2015-11-30T14:43:21.121+0800    done dumping testdatabase.tracker (759461 documents)

最後 unlock db


> db.fsyncUnlock()
{ "ok" : 1, "info" : "unlock completed" }

還原 mongorestore


還原指令如下


/usr/share/mongodb/bin/mongorestore -d testdatabase --drop /home/mongodb/project/data/backup/testdatabase

--drop 的部份,就是先將這裡的 database 刪除,然後再進行 restore


執行過程如下


[root@server backup]# /usr/share/mongodb/bin/mongorestore -d testdatabase --drop /home/mongodb/project/data/backup/testdatabase
2015-11-30T14:52:45.306+0800    building a list of collections to restore from /home/mongodb/project/data/backup/testdatabase dir
2015-11-30T14:52:45.306+0800    reading metadata file from /home/mongodb/project/data/backup/testdatabase/tracker.metadata.json
2015-11-30T14:52:45.306+0800    restoring testdatabase.tracker from file /home/mongodb/project/data/backup/testdatabase/tracker.bson
2015-11-30T14:52:45.308+0800    reading metadata file from /home/mongodb/project/data/backup/testdatabase/user.metadata.json
2015-11-30T14:52:45.309+0800    restoring testdatabase.user from file /home/mongodb/project/data/backup/testdatabase/user.bson
2015-11-30T14:52:45.310+0800    reading metadata file from /home/mongodb/project/data/backup/testdatabase/event.metadata.json
2015-11-30T14:52:45.812+0800    restoring testdatabase.event from file /home/mongodb/project/data/backup/testdatabase/event.bson
2015-11-30T14:52:45.812+0800    restoring indexes for collection testdatabase.event from metadata
2015-11-30T14:52:45.813+0800    reading metadata file from /home/mongodb/project/data/backup/testdatabase/eventlog.metadata.json
2015-11-30T14:52:45.813+0800    restoring testdatabase.eventlog from file /home/mongodb/project/data/backup/testdatabase/eventlog.bson
2015-11-30T14:52:45.815+0800    finished restoring testdatabase.event (0 documents)
2015-11-30T14:52:45.815+0800    restoring indexes for collection testdatabase.eventlog from metadata
2015-11-30T14:52:45.817+0800    finished restoring testdatabase.eventlog (0 documents)
2015-11-30T14:52:45.821+0800    restoring indexes for collection testdatabase.user from metadata
2015-11-30T14:52:45.822+0800    finished restoring testdatabase.user (1000 documents)
2015-11-30T14:52:48.306+0800    [#####...................]  testdatabase.tracker  76.7 MB/342.5 MB  (22.4%)
2015-11-30T14:52:51.306+0800    [############............]  testdatabase.tracker  184.9 MB/342.5 MB  (54.0%)
2015-11-30T14:52:54.306+0800    [####################....]  testdatabase.tracker  293.1 MB/342.5 MB  (85.6%)
2015-11-30T14:52:57.025+0800    restoring indexes for collection testdatabase.tracker from metadata
2015-11-30T14:52:57.025+0800    finished restoring testdatabase.tracker (759461 documents)
2015-11-30T14:52:57.025+0800    done

2015/12/28

Production Cluster Architecture in MongoDB: Replica Sets + Sharding

為了建立一個完整的高可用性的 MongoDB 資料庫,我們必須要整合使用 Replica Sets 跟 Sharding。


  1. Shard Server: 2 個以上的 shard server,以 replica sets 建立 shard server,確保每一個節點都有備份,自動容錯及復原的能力
  2. Config Server: 設定 3 個 config server,一定要剛好 3個
  3. Route Server: 1 個以上的 query routers,通常可以在每一個 application server 都配置一個 mongos instance


MongoDB in Trend


在這篇文章中 趨勢科技導入MongoDB 追蹤管理全球10萬個行動裝置,MongoDB 的架構圖其實跟官方提出的架構概念是一致的。



如果是以 Application 的角度來看 MongoDB 的架構,概念上是接近以下這個架構圖。



3 physical servers


因為三個 config server 的基本要求,因此我們最低的硬體要求,是需要三台實體的 server。另外考量到整個 cluster 環境的完整的備援。
MongoDB的Replica Sets+Sharding架構 這篇文章提出了一個架構。



Production Note


MongoDB 官方在 Production Notes 提供了一些在正式環境應該要注意的事項。


  • Storage Engine
    有兩種 MMAPv1 及 WiredTiger,預設是使用 MMAPv1。 mongod 在啟動時,會檢查 dbPath 裡面既有的資料,是不是指定了不同的 storage engine。

  • OS
    Mac OS X, Linux, Windows Server 2012, Windows Server 2008 R2 64bit, Windows 7 (64 bit), Windows Vista, and Solaris 都可以用,但 Production 環境建議要使用 64bits OS。

  • Concurrency
    MMAPv1 提供 collection-level locking,所有的 collections 都有一個唯一的 read-write lock,允許多個 clients 同時在不同的 collections 裡面修改文件。


    WiredTiger 在一個 collection 裡面 readers 與 writers 同時存取文件的機制,clients 可在其他 write operations 進行過程中,還能讀取文件,多個 threads 可以同時修改在某一個 collection中不同的文件。

  • Data Consistency


    1. Journaling: MongoDB 採取了 write ahead logging to an on-disk journal 的機制,可在 mongod crash 時,還能處理尚未儲存到 data files 的資料。最好要保持 journaling 的功能開啟的狀態。

    2. Write Concern: 當 MongoDB 處理 write operation 時,可設定 guarantee 的強度,當以 weak write concern 執行 insert,update,delete 時,這些 operation 會快速完成,但風險是當發生錯誤時,就有可能會發生資料錯誤的狀況。

  • Networking


    1. Use Trusted Networking Environments
      MongoDB 預設沒有啟用 authorization 機制,MongoDB 是以 Role-Based Access Control 的方式,處理 client 端授權的問題。

    2. Disable HTTP Interfaces
      MongoDB 有提供透過 HTTP 查詢 server status 的機制,正式環境要把這個功能關掉。

    3. Manage Connection Pool Sizes
      避免在單一個 mongod 或 mongos,使用太多 connection resources,確保 client 只使用了合理的 connection pool sizes,通常設定為 concurrent db requests 總量的 110%~115% 就可以了。connPoolStats 指令可查詢 open connections 的資訊。

  • HW Considerations


    1. MongoDB 核心是使用 litte-endian 硬體,主要是 x86/x86_64 的 CPU,而 client 可以使用 big 或 little endian 系統

    2. 配置足夠的 RAM 及 CPU
      MMAPv1 不需要太多 CPU cores,增加 RAM 可減少 MongoDB 產生 page faults 的頻率。


      WiredTiger 需要很多 CPU cores 支援,active threads(concurrent operations) 跟 CPU 總數有關係。


      storage.wiredTiger.engineConfig.cacheSizeGB 會限制 cache 的上限。


      可透過 mongostat 指令查看 ar|aw 欄位,得知 active reads/writes 的數量。

    3. 使用 Solid State Disk (SSD)

    4. NUMA (Non-Uniform Access Memory) HW
      NUMA 是一種記憶體的實作方式,CPU 存取 local memory 的速度會比存取 shard memory 還快。MongoDB 在 NUMA 環境中使用,會遇到很多問題,可能會越來越慢。

  • Disk and Storage System


    • Swap


    1. 為系統設定 swap,當記憶體不夠用時,就不會發生OOM(Out of Memoty) killer 把 mongod 刪除的問題
    2. 因為 MMAPv1 是以 map files to memory 的方式,確保 OS 不會將 MongoDB 資料存在 swap space 裡面。


    • Raid


    1. 大多數的 MongoDB deployments 應該使用 Raid-10
    2. Raid-5 與 Raid-6 的效能表現不是很好
    3. 不要用 Raid-0,Raid-0 的 write operation 表現好,但 read operation 就比較差,在 Amazon EBS volumes 上會發生這個問題。


    • Remote File System


    1. MMAPv1 不建議使用 NFS(Network File System),data 與 journal 都可能會有效能問題,建議將 jornal journal 放在 local or iscsi 硬碟會比較快。

    2. 如果真的要使用 NFS,建議在 /etc/fstab 檔案中加上 bg, nolock, noatime 這些設定。


    • Separate Components onto Different Storage Devices


    1. 為了改善效能,可考慮將 data, journal, log 分開放在不同的 stoage devices上。
    2. WiredTiger 可將 indexes 放在不同的 storage device 上。
  • Architecture


    • Replica Sets

    • Shared Clusters

  • Compression
    WiredTiger 可使用 snappy 或 zlib 壓縮 data,snappy 壓縮比例較低,但效能耗費較少,zlib 壓縮比例高,但效能耗費也高。


    WiredTiger 預設使用 snappy,可修改設定值 storage.wiredTiger.collectionConfig.blockCompressor

  • Platform Specific Considerations
    MongoDB 使用 GNU C Library(glibc),建議使用 glibc-2.12-1.2.el6 以上的版本

  • Kernel and File System
    建議使用 kernel 2.6.36 以上的 kernel


    MMAPv1 中,MongoDB 會 preallocate db files,產生比較大的 data files,建議使用 XFS 與 EXT4 file system。


    WiredTiger 強烈建議要使用 XFS,避免效能問題。


    XFS file system 至少使用 linux kernal 2.6.25 以上的版本。
    EXT4 file system 至少使用 linux kernal 2.6.23 以上的版本。


    以下是一些 Linux Distribution建議的 files sytem 及 kernel 版本


Linux Distribution Filesystem Kernel Version
CentOS 5.5 ext4, xfs 2.6.18-194.el5
CentOS 5.6 ext4, xfs 2.6.18-3.0.el5
CentOS 5.8 ext4, xfs 2.6.18-308.8.2.el5
CentOS 6.1 ext4, xfs 2.6.32-131.0.15.el6.x86_64
RHEL 5.6 ext4 2.6.18-3.0
RHEL 6.0 xfs 2.6.32-71
Ubuntu 10.04.4 LTS ext4, xfs 2.6.32-38-server
Amazon Linux AMI release 2012.03 ext4 3.2.12-3.2.4.amzn1.x86_64

  • fsync() on Directories
    MongoDB 需要 file system 支援 fsync on directories 的功能,HGFS 與 Virtual Box 並不支援這個功能。

  • Recommended Configuration


    1. 在儲存 data files 的硬碟,要關掉 atime
    2. 設定 file descriptor limit (-n) 以及 user process limit(ulimit) 超過 20000
    3. disable Transparent Huge Pages,MongoDB 在 4096 bytes virtual memory pages 的效能較好


      echo never > /sys/kernel/mm/redhat_transparent_hugepage/defrag
      echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled
      echo never > /sys/kernel/mm/transparent_hugepage/enabled
      echo never > /sys/kernel/mm/transparent_hugepage/defrag
    4. disable NUMA in BIOS

    5. 設定 SELinux


    在 MMAPv1
    1. 檢查針對 block device 的 readahead 設定。一般設定為 32(16kb) 就可以了


    注意:所有 MongoDB 系統都要用 NTP 同步時間。

  • Linux System
    mongod 及 mongos 在他們自己的 socket 裡面限制 TCP keep alive setting 最大值為 300 seconds。


    查詢設定


    sysctl net.ipv4.tcp_keepalive_time
    cat /proc/sys/net/ipv4/tcp_keepalive_time

    修改設定


    sudo sysctl -w net.ipv4.tcp_keepalive_time=<value>
    echo <value> | sudo tee /proc/sys/net/ipv4/tcp_keepalive_time

    永久修改設定


    vi /etc/sysctl.conf
    net.ipv4.tcp_keepalive_time = <value>

noPadding


mysql遷移到mongodb shared架構的過程中踩到的一個坑
MongoDB 2.x 在儲存資料時,會自動預留一些空間,所以原本在 mysql 259GB 的資料,移到 MongoDB 變成了 1197.416 GB。這個問題在 MongDB 3.x 採用了 WiredTiger 以後,已經有改善了。


就算是使用預設的 MMAPv1 Storage Engine 在 3.0 以後已經可以在建立 collection 時,就加上 noPadding 選項設定,避免 MongoDB 用掉太多預留空間。