项目地址: https://github.com/variety/variety
对于MongoDB这种schema-free 的数据库来说,存在数据库里的每一条数据在结构上经常不同,时间长了我们可能就很难去了解库里数据的具体结构了。今天分享一个叫Variety的小脚本,用于对collection中存储的数据情况进行分析。
Variety就是一个js脚本,直接使用mongo客户端执行。
一个简单的示例
创建一个集合:
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")});
让我们用这个脚本分析下:
$ 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”是我们分析的集合所在的数据库)
看起来每个人都有一个“name”和“_id”。大多数,但不是所有人有“bio”。
有趣的是,“pets”可以是array或者string,但是array比string多。这会在应用程序中导致任何问题么?
上面命令就是在test加的users这个collection上进行数据结构分析。
从上面结果我们能够看出,Variety将所有在users中出现过的字段都进行了统计,包括其类型和数量,以及出现在记录中的频率。这对于我们了解一个collection中的数据是很有用的。比如我们可以很容易看到是否在应该是数字的字段上还出现了一些string类型的值。如果是的话,可能要检查一下代码中是否有类型问题。
似乎创建的第一个文档有一个奇怪的遗留键值 -- 那个构建这个属性类型的蠢蛋没有在之后清理掉。如果有数千个早期的文档,我会参阅基础代码去确认它们不再使用,然后全部删除它们。那样就不会让之后的开发者困惑。
结果存储在varietyResults数据库用于之后的使用。
当分析花费很长时间时查看进度
查看日志尾部是很好的方法。MongoDB为你提供了“完成百分比”的度量。这些操作对于巨大的集合会花费很长时间。
只分析当前的文档
也去你真的有一个非常巨大的集合,你不能一整天一直等着Variety的结果。
也许你想忽略集合的最老的文档,只想看看集合的文档结构是怎样的。
可以应用“limit”约束,只分析集合中的最新文档,像这样:
$ mongo test --eval "var collection = 'users', limit = 1" variety.js
让我们更加接近的检查结果:
+----------------------------------------------------+ | key | types | occurrences | percents | | ----------- | ----------- | ----------- | -------- | | _id | ObjectId | 1 | 100.0 | | name | String | 1 | 100.0 | | someBinData | BinData-old | 1 | 100.0 | +----------------------------------------------------+
我们只检查了最后一个文档(“limit = 1”)。它属于Geneviève,只包含_id,name和bio列。因此只有这三个键是合理的。
分析文档到一个最大深度
也许你可能有一个非常深的嵌套对象架构,而你不想深入分析到到一定深度。
可以应用“maxDepth”约束,限制深度,Variety将循环查找以找到新对象。
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 | +----------------------------------------------------------+
如你所见,Variety只是深入了三层。
分析一个文档子集
也许你有一个巨大的集合,或者你只关心文档的某个子集。
你可以应用“query”约束,使用了一个标准的MongoDB查询对象,在分析前过滤需要的文档子集。
$ mongo test --eval "var collection = 'users', query = {'caredAbout':true}" variety.js
输出为JSON易于提取和解析
Variety支持两种不同的输出格式:
. ASCII:很好格式化的表
. JSON:有效的JSON结果用于随后其他工具的处理
默认格式是ASCII。你可以通过提供给Variety的属性outputFormat选择格式类型。有效值为ascii和json。
$ mongo test --quiet --eval "var collection = 'users', outputFormat='json'" variety.js
Quiet选项
MongoDB和Variety输出一些额外信息到标准输出。如果你想移除这个信息,你可以使用--quiet选项提供给mongo可执行命令。Variety也可以读取这个选项并减少不必要的输出。同时连接outputFormat=json时是非常有用的。你会只收到JSON,不带有任何其他字符。
$ mongo test --quiet --eval "var collection = 'users', sort = { updated_at : -1 }" variety.js
辅助成员读取
在一个繁忙的复制集主成员上分析可能花更长时间比从一个辅助成员读取。为了从辅助成员读取,我们通过设置slaveOk属性为true来告诉MongoDB执行辅助成员读:
$ mongo secondary.replicaset.member:31337/somedb --eval "var collection = 'users', slaveOk = true" variety.js
在MongoDB保存结果用于以后使用
默认,Variety只打印结果到标准输出并不存储它们在MongoDB。如果你想自动保存它们在MongoDB用于之后的使用,你可以设置参数persistResults。Variety然后存储结果文档到数据库varietyResults,集合名源自原集合名。如果源集合名是users,Variety将在varietyResults数据库下以集合名usersKeys存储结果。
$ mongo test --quiet --eval "var collection = 'users', persistResults=true" variety.js
保存到另一个数据库,你可以指定以下参数:
. resultsDatabase -- 存储Variety结果的数据库。接受数据库名或者一个host[:port]/database链接地址。
. resultsCollection -- 存储Variety结果的集合。警告:在结果插入前该集合删除。
. resultsUser -- 结果数据库的用户名
. resultsPass -- 结果数据库的密码
$ mongo test --quiet --eval "var collection = 'users', persistResults=true, resultsDatabase='db.example.com/variety' variety.js
需要注意的是,Variety在对数据结构进行分析的时候,实际是用MapReduce来做的,会进行全表扫描操作,所以如果是对线上库进行分析,那么建议最好使用一个不提供服务的备份库或者在业务低峰来做。避免给线上业务造成压力。