1. 程式人生 > >面對Schema free 的MongoDB,如何規範你的schema

面對Schema free 的MongoDB,如何規範你的schema

conda man ould 關聯 tom primary reg 哈哈 persist

http://www.mongoing.com/archives/2282

總的來說,限制MongoDB的Schema,有兩個主要工具:

variety:查看collections中各個字段出現的情況

Document Validation:對於表中數據的字段規則進行限定,Error和Warn等級,Error拒絕插入,Warn寫日誌

正文

大家都知道MongoDB是文檔型數據庫,是Schema Free的。

那麽MongoDB的文檔模型能給我們帶來哪些好處呢,在這簡單列舉幾個:

  • json形式-在MongoDB中,開發人員可以直接將一個json數據存儲進MongoDB,這對於開發人員來說是非常友好額;
  • 讀寫性能高-在關系型數據庫中,我們經常會進行join、子查詢等關聯性需求,這時候往往會帶來較多的隨機IO,而在MongoDB中,我們可以通過合理的數據模型設計來將很多的關聯需求通過內嵌、反範式的方式實現,減少了隨機IO;
  • schema free-MongoDB的數據模型是靈活的,無需為了Online DDL而操心,不同的document也可以有不同的結構。

在這,我們不深入探究如何對於MongoDB 的Schema進行設計、建模,有關這部分內容,推薦大家可以閱讀TJ在開源中國的年終盛典會上分享《MongoDB 進階模式設計》,以及《Retail Reference Architecture Part 1 to 4 》。

在此我們將主要針對進行了初步建模、並正式上線服務後的schema進行巡檢與檢測的方式來進行討論。

Variety

Variety是一個開源的,非常使用的,檢測mongodb表字段類型、分布的一個開源工具。

正如其github readme中第一句所說”Meet Variety, a Schema Analyzer for MongoDB

Variety能夠幫助我們檢測我們MongoDB表中的字段類型、分布,並生產報表,可以讓我們非常直觀的對現有表結構、字段類型進行分析,並找出數據模型中的隱患。

下面我們通過例子來進行講解:

首先,建立一個表

db.users.insert({name: "Tom", bio: "A nice guy.", pets: ["monkey", "fish"], someWeirdLegacyKey: "I like Ike!"});
db.users.insert({name: "Dick", bio: "I swordfight.", birthday: new Date("1974/03/14")});
db.users.insert({name: "Harry", pets: "egret", birthday: new Date("1984/03/14")});
db.users.insert({name: "Geneviève", bio: "?a va?"});
db.users.insert({name: "Jim", someBinData: new BinData(2,"1234")});

我們來看看通過variety獲得的結果

$ mongo test --eval "var collection = ‘users‘" variety.js

+------------------------------------------------------------------+
| key | types | occurrences | percents |
| ------------------ | ------------ | ----------- | -------- |
| _id | ObjectId | 5 | 100.0 |
| name | String | 5 | 100.0 |
| bio | String | 3 | 60.0 |
| birthday | String | 2 | 40.0 |
| pets | Array(4),String(1) | 5 | 40.0 |
| someBinData | BinData-old | 1 | 20.0 |
| someWeirdLegacyKey | String | 1 | 20.0 |
+------------------------------------------------------------------+

test是我們的db名,users是表名。我們可以看到,針對我們之前插入的5條數據,variety跑出的結果是:

所有的document都含有_id,和name字段,60%的document含有bio字段,40%的document含有birthday和pets字段,且pets字段有2個類型的數據(4個array的,1個string的),20%的document含有someBinData和SomeWeirdLegacyKey字段。

然而生產環境中由於我們的數據量較大,比如一個表有10億條數據,全部進行掃描會耗時較長,可能我們僅希望對1000條數據進行分析,這時候就可以使用limit來限定。

$ mongo test --eval "var collection = ‘users‘, limit = 1000" variety.js

+----------------------------------------------------+
| key | types | occurrences | percents |
| ----------- | ----------- | ----------- | -------- |
| _id | ObjectId | 1000 | 100.0 |
| name | String | 1000 | 100.0 |
| someBinData | BinData-old | 1000 | 100.0 |
+----------------------------------------------------+

由於MongoDB的可以通過內嵌來減少聯合查詢的需求,可以通過反範式來減少隨機IO,所以很可能會有嵌套出現在我們的document中。有的時候嵌套的層數太多了,影響我們的統計信息,怎麽辦,我們可以通過maxDepth來限制。請參考下面的例子:

db.users.insert({name:"Walter", someNestedObject:{a:{b:{c:{d:{e:1}}}}}});

$ mongo test --eval "var collection = ‘users‘" variety.js

+----------------------------------------------------------------+
| key | types | occurrences | percents |
| -------------------------- | -------- | ----------- | -------- |
| _id | ObjectId | 1 | 100.0 |
| name | String | 1 | 100.0 |
| someNestedObject | Object | 1 | 100.0 |
| someNestedObject.a | Object | 1 | 100.0 |
| someNestedObject.a.b | Object | 1 | 100.0 |
| someNestedObject.a.b.c | Object | 1 | 100.0 |
| someNestedObject.a.b.c.d | Object | 1 | 100.0 |
| someNestedObject.a.b.c.d.e | Number | 1 | 100.0 |
+----------------------------------------------------------------+

$ mongo test --eval "var collection = ‘users‘, maxDepth = 3" variety.js

+----------------------------------------------------------+
| key | types | occurrences | percents |
| -------------------- | -------- | ----------- | -------- |
| _id | ObjectId | 1 | 100.0 |
| name | String | 1 | 100.0 |
| someNestedObject | Object | 1 | 100.0 |
| someNestedObject.a | Object | 1 | 100.0 |
| someNestedObject.a.b | Object | 1 | 100.0 |
+----------------------------------------------------------+

又或者我們希望指定統計的條件,比如希望caredAbout為true的,可以這樣做:

$ mongo test --eval "var collection = ‘users‘, query = {‘caredAbout‘:true}" variety.js

又或者是希望進行排序:

$ mongo test --eval "var collection = ‘users‘, sort = { updated_at : -1 }" variety.js

同時我們也可以指定分析結果的format:

$ mongo test --quiet --eval "var collection = ‘users‘, outputFormat=‘json‘" variety.js

一般在生產中, 我們不會在primary上進行分析, 我們可以在一個priority為0,且為hidden的secondary上進行分析,這時候需要指定slaveOK:

$ mongo secondary.replicaset.member:31337/somedb --eval "var collection = ‘users‘, slaveOk = true" variety.js

又或者說我們希望將分析結果存在mongo中:

$ mongo test --quiet --eval "var collection = ‘users‘, persistResults=true" variety.js

並且指定存儲的詳細信息:

  • resultsDatabase 分析結果所存儲的db名
  • resultsCollection 分析結果所存儲的collection名
  • resultsUser 分析結果存儲的實例的user
  • resultsPass 分析結果所存儲的實例的password
mongo test --quiet --eval "var collection = ‘users‘, persistResults=true, resultsDatabase=‘db.example.com/variety‘ variety.js

我們為什麽要用Variety呢?

盡管我們MongoDB是Schema Free的,但是絕大多數情況下, 我們都希望字段類型統一。

不一致的字段類型可能會為我們的數據帶來誤差,試想一下,如果某個字段的字段類型不統一,而我們卻不知情,這時候很可能會發現業務查詢有數據丟失,數據不準確。

並且在生產環境中,應用的版本在不斷叠代,需求不斷增多,字段也隨之變化,如果在沒有規範化的上線流程檢查過後,數據庫中可能還會存在部分數據的字段確實,比如有的document有a字段,有的卻沒有,variety也可以幫助我們發現這些問題。

Document Validation

MongoDB 3.2推出了很多給力的功能,在這不得不提及Document Validation,Document Validation的出現我想也是MongoDB官方想表達”schema free but you may need some rules”吧,哈哈,純屬臆測。

簡單介紹下Document Validation:

我們可以為我們schema free的mongodb collection做一些限制。當然這並不是意味著MongoDB變成了關系型數據庫,個人覺得這反而更好的突出了MongoDB Schema free的特性。在正確的地方、需要的地方schema free,在適當的地方要有限制。

假設我們要新建一個表contacts,要有如下約束:

[email protected]om”結尾,或者status為”Unknown”或者”Incomplete”

db.createCollection( "contacts",
{ validator: { $or:
[
{ phone: { $type: "string" } },
{ email: { $regex: [email protected]$/ } },
{ status: { $in: [ "Unknown", "Incomplete" ] } }
]
}
} )

對已經建立了的表,我們可以通過如下方式來做限定:

db.runCommand( {
collMod: "contacts",
validator: { $or: [ { phone: { $type: "string" } }, { email: { $regex: [email protected]$/ } }, { status: { $in: [ "Unknown", "Incomplete" ] } } ] },
validationLevel: "moderate"
} )

這裏可以看到,多了一個validationLevel參數,我們可以在設置validation的時候指定我們的validationLevel級別:

  • 默認級別是strict,對該collection已有的和以後新增的document都進行validation驗證;

  • 可以設置為moderate,僅對已經存在的document進行validation限定;

同時還有validationAction參數來指定當有不符合validation規則的數據進行update或者insert的時候, 我們mongodb實例如何進行處理。

  • 默認級別為error,mongodb將拒絕這些不符合validation規則的insert和update。
  • 可以設置為warn,mongodb會在日誌中記錄,但是允許這類insert和update操作。日誌中如:

2015-10-15T11:20:44.260-0400 W STORAGE [conn3] Document would fail validation collection: example.contacts doc: { _id: ObjectId(‘561fc44c067a5d85b96274e4‘), name: "Amanda", status: "Updated" }

?

validation的限制

  • validation不能對admin、local和config庫中的collection進行設置;
  • 不能對system.*這類collections進行validation設置;

面對Schema free 的MongoDB,如何規範你的schema