DevilKing's blog

冷灯看剑,剑上几分功名?炉香无需计苍生,纵一穿烟逝,万丈云埋,孤阳还照古陵

0%

Mongodb Cookbook

原文链接

###admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
db.currentOp();

// show queries
db.currentOp({
"active": true,
"secs_running": {"$gt" : 3},
"ns": /^swag\./
});

// queries not using any index
db.adminCommand({
"currentOp": true,
"op": "query",
"planSummary": "COLLSCAN"
});

// operations with high numYields
db.adminCommand({
"currentOp": true,
"ns": /^swag\./,
"numYields": {"$gte": 100}
})

上面可以做为某些调优手段

Find Documents With An Array Field

  • $in: [...] means “intersection” or “any element in”
  • $all: [...] means “subset” or “contain”
  • $elemMatch: {...} means “any element match”
  • $not: {$elemMatch: {$nin: [...]}} means “subset” or “in”
1
2
3
4
5
6
7
8
db.getCollection('message').find({
unlocks: {
$elemMatch: {
_cls: 'PointsUnlock',
user: ObjectId("57f662e727a79d07993faec5")
}
}
})

Find Documents With Computed Values Using $expr

1
2
3
4
5
db.getCollection('user').find({
$expr: {
$eq: [{$size: '$follows'}, {$size: '$blocks'}]
}
})

for loop

1
2
3
4
var oldTags = ['famous', 'newstar', 'featured', 'western', 'recommended', 'popular'];
oldTags.forEach(function(tag) {
db.getCollection('user').updateMany({tags: tag}, {$addToSet: {tags: 'badge:' + tag}});
});

python interface

insert an element into an array at certain postion

1
2
3
4
5
6
7
8
9
slot = 2
Post.objects.filter(id=post_id, media__id__ne=media_id).update_one(__raw__={
'$push': {
'media': {
'$each': [{'id': bson.ObjectId(media_id)}],
'$position': slot,
}
}
})

remove elements

采用pull的操作?

1
2
3
4
5
6
7
8
9
10
11
import bson

user_id = '5a66d5c2af9c462c617ce552'
tags = ['valid_tag_1', 'future_tag']

updated_result = User._get_collection().update_one(
{'_id': bson.ObjectId(user_id)},
{'$pull': {'schedules': {'tag': {'$in': tags}}}},
)
print(updated_result.raw_result)
# {'n': 1, 'nModified': 1, 'ok': 1.0, 'updatedExisting': True}

upsert

1
2
3
4
5
6
7
tag_schedule = TagSchedule.objects \
.filter(user=user_id, tag='vip') \
.modify(
started_at=started_at,
ended_at=ended_at,
upsert=True
)

设置upsert=True

Aggregation Pipeline

  • $match: Filters documents.
  • $project: Modifies document fields.
  • $group: Groups documents by fields.
  • $lookup: Joins another collection.
  • $replaceRoot: Promotes an embedded document field to the top level and replace all other fields.
  • $unwind: Expanses an array field into multiple documents along with original documents.
  • $facet: Processes multiple pipelines within one stage and output to different fields.

match multiple condition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import werkzeug

user_agent = werkzeug.UserAgent('swag/2.25.1 (iPhone; iOS 11.4.1; Scale/2.00; com.live.swag.enterprise; zh-tw)')
user_preferences = ['gender:female', 'gender:male']
user_tags = ['beta', 'vip']
user_platforms = [user_agent.platform]

def stages():
now = utils.utcnow()

yield {'$match': {
'$and': [
{'nbf': {'$lte': now}},
{'exp': {'$gt': now}},
{'requirements': {'$elemMatch': {
'preferences': {'$not': {'$elemMatch': {'$nin': user_preferences}}},
'tags': {'$not': {'$elemMatch': {'$nin': user_tags}}},
'platforms': {'$not': {'$elemMatch': {'$nin': user_platforms}}},
'$or': [
{'$and': [
{'version_major_min': {'$lte': user_agent.version.major}},
{'version_minor_min': {'$lte': user_agent.version.minor}},
]},
{'$and': [
{'version_minor_min': {'$exists': False}},
{'version_minor_min': {'$exists': False}},
]},
],
}}},
],
}}
yield {'$project': {
'name': True,
'nbf': True,
'exp': True,
'positions': {'$objectToArray': '$positions'},
}}
yield {'$unwind': '$positions'}
yield {'$sort': {
'exp': 1,
}}
yield {'$project': {
'_id': False,
'name': True,
'position': '$positions.k',
'url': {'$arrayElemAt': ['$positions.v.urls', 0]},
'startedAt': {'$floor': {'$divide': [{'$subtract': ['$nbf', constants.UNIX_EPOCH]}, 1000]}},
'endedAt': {'$floor': {'$divide': [{'$subtract': ['$exp', constants.UNIX_EPOCH]}, 1000]}},
}}
yield {'$group': {
'_id': '$position',
'items': {'$push': '$$ROOT'},
}}

try:
docs = Promotion.objects.aggregate(*stages())
except StopIteration:
docs = []
else:
docs = list(docs)

project负责显示项,unwind负责展开?

Collect Items With $group And $addToSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def stages():
now = utils.utcnow()

yield {'$match': {
'schedules': {'$elemMatch': {
'nbf': {'$lte': now},
'exp': {'$gte': now}
}}
}}
yield {'$unwind': '$schedules'}
yield {'$match': {
'schedules.nbf': {'$lte': now},
'schedules.exp': {'$gte': now}
}}
yield {'$project': {
'_id': False,
'id': '$_id',
'username': True,
'tag': '$schedules.tag',
'nbf': '$schedules.nbf',
'exp': '$schedules.exp'
}}
yield {'$group': {
'_id': '$id',
'tags': {'$addToSet': '$tag'},
}}

for user_tag_schedule in User.objects.aggregate(*stages()):
print(user_tag_schedule)

# output:
# {'_id': ObjectId('579b9387b7af8e1fd1635da9'), 'tags': ['stats']}
# {'_id': ObjectId('5a66d5c2af9c462c617ce552'), 'tags': ['chat', 'vip']}

Do Advanced $project With $let

If you find youself want to do $project twice to tackle some fields, you should use $let.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def stages():
yield {'$match': {
'purchases.user': g.user.id,
}}
yield {'$project': {
'_id': False,
'id': '$_id',
'name': True,
'image': {
'$ifNull': [{'$arrayElemAt': ['$images', 0]}, None],
},
'purchasedAt': {
'$let': {
'vars': {
'purchase': {
'$arrayElemAt': [
{
'$filter': {
'input': '$purchases',
'as': 'purchase',
'cond': {
'$and': [
{'$eq': ['$$purchase.user', g.user.id]},
],
},
},
},
0,
],
},
},
'in': '$$purchase.timestamp',
},
},
}}

try:
docs = MessagePackProduct.objects.aggregate(*stages())
except StopIteration:
docs = []
else:
for doc in docs:
print(doc)

Query The First Element In An Array Field With $arrayElemAt And $filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def stages():
category_tag = 'category:user'
currency = 'usd'
platform = 'ios'

yield {'$match': {
'active': True,
'tags': category_tag,
}}
yield {'$project': {
'_id': False,
'id': '$_id',
'name': True,
'caption': True,
'description': True,
'image': {
'$ifNull': [{'$arrayElemAt': ['$images', 0]}, None],
},
'preview_message': True,
'metadata': True,
'created_at': True,
'updated_at': True,
'active': True,
'sku': {
'$ifNull': [
{
'$arrayElemAt': [
{
'$filter': {
'input': '$skus',
'as': 'sku',
'cond': {
'$and': [
{'$eq': ['$$sku.currency', currency]},
{'$eq': ['$$sku.attributes.platform', platform]},
]
}
},
},
0
]
},
None
],
},
'tags': True,
'total': True,
'is_bought': {'$in': [g.user.id, {'$ifNull': ['$purchases.user', []]}]},
}}
yield {'$sort': {'is_bought': 1, 'created_at': -1}}

try:
docs = MessagePackProduct.objects.aggregate(*stages())
except StopIteration:
docs = []
else:
for doc in docs:
print(doc)

Join Another Collection Using $lookup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def stages():
yield {'$match': {
'tags': 'pack:prod_CR1u34BIpDbHeo',
}}
yield {'$lookup': {
'from': 'user',
'localField': 'sender',
'foreignField': '_id',
'as': 'sender_data',
}}
yield {'$unwind': '$sender_data'}
yield {'$project': {
'_id': False,
'id': '$_id',
'sender': {
'id': '$sender_data._id',
'username': '$sender_data.username',
},
'caption': True,
'posted_at': True,
}}
yield {'$sort': {'posted_at': -1}}

try:
docs = Message.objects.aggregate(*stages())
except StopIteration:
docs = []
else:
for doc in docs:
print(doc)

lookup作为join操作

Count Documents In Another Collection With $lookup (JOIN)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def stages():
category_tag = f'category:{category}'
yield {'$match': {
'active': True,
'tags': category_tag,
}}
yield {'$addFields': {
'message_pack_id_tag': {'$concat': ['pack:', '$_id']},
}}
yield {'$lookup': {
'from': 'message',
'localField': 'message_pack_id_tag',
'foreignField': 'tags',
'as': 'total',
}}
yield {'$addFields': {
'total': {'$size': '$total'}
}}
yield {'$project': {
'_id': False,
'id': '$_id',
'name': True,
'total': True,
}}

try:
docs = MessagePackProduct.objects.aggregate(*stages())
except StopIteration:
docs = []
else:
for doc in docs:
print(doc)

useful tools

monitoring

1
2
3
4
5
6
7
8
9
10
11
12
13
$ mongotop
$ mongostat


$ pip install mongotail

$ mongotail 127.0.0.1:27017/swag -l 2
$ mongotail 127.0.0.1:27017/swag -f


$ pip install mtools

$ mloginfo mongod.log