MongoDB Basics
Banana's edition
Agenda
- Overview
- CRUD Operations
- Aggregation Framework
- Indexes
- Lessons learned
Overview
What is MongoDB?
Why MongoDB?
- Document Oriented Storage
- Index on any attribute
- Replication and high availability
- Auto-sharding
- Rich queries
- Fast in-place updates
- Professional support by MongoDB
Terminology and Concepts
Teorema CAP
ACID vs BASE
CRUD Operations
Create Operations
- db.collection.insertOne()
- db.collection.insertMany()
Read Operations
- db.collection.find()
- db.collection.findOne()
Read Operations - findOne
db.getCollection('users')
.findOne({username: 'kimkardashian'})
############################ VS ############################
db.getCollection('users')
.find({username: 'kimkardashian'}).limit(1)
Read Operations- find
# state = California AND user_type = celebrity
db.users.find(
{state: 'California', user_type: 'celebrity'}
)
# select all - show only username and followers_count
db.users.find(
{},
{username: 1, followers_count: 1}
)
# select all - show all fields except _id and user_id
db.useres.find(
{},
{user_id : 0, _id: 0}
)
Read Operations - Query criteria
########### $gt
# users with more than 1m of followers
db.users.find({followers_count: {$gt: 1000000}})
########### $in
# users related with zara
db.users.find({related_brands: {$in: ['zara']}})
Comparison query operators
$eq, $neq, $gt, $gte, $lt, $lte, $in, $nin
############ exists
# users with the "state" field
db.users.find({state: {$exists: 1}})
############ type
# users with text in the biography
db.users.find({biography: {$type: 2}})
Read Operations - Query criteria
Element query operators (exists, type)
############ nor
# documents that:
# price field not equal to 1.99 and sale field not equal to true
# or price field not equal to 1.99 but do not contain the sale field
# or do not contain the price field but sale field not equal to true
# or do not contain the price field and do not contain the sale field
db.inventory.find(
{
$nor: [
{ price: 1.99 },
{ sale: true } ]
}
)
Read Operations - Query criteria
Logical query operators (or, and, not, nor)
Read Operations - Query criteria
Logical query operators
$and ????
Read Operations - Query criteria
Logical query operators
$and ????
# users with # followers between 1M and 2M
db.users.find(
{
followers_count: {$gt: 1000000},
followers_count: {$lt: 2000000}
})
db.users.find(
{
$and: [
{followers_count: {$gt: 100000}},
{followers_count: {$lt: 200000}}
]
})
Read Operations - Query criteria
Logical query operators
$and ????
# brands with more than 1M of followers
# or celebrities with more than 200k followers
db.getCollection('users_copy').find(
{$or:
[
{$and: [
{user_type: 'brand'},
{followers_count: {$gte: 1000000}}
]},
{$and: [
{user_type: 'celebrity'},
{followers_count: {$gte: 200000}}
]}
]
})
Read Operations - Query criteria
Array query operators
############# all
db.users.find(
{related_brands:
{$all: ['zara', 'mango']}
}
)
############# in
db.users.find(
{related_brands:
{$in: ['zara', 'mango']}
}
)
############# size
db.users.find({related_brands: {$size: 5}})
Read Operations - Cursor modifiers
############# count
db.users.find({country:"USA"}).count()
############# limit
db.users.find({country:"USA"}).limit(5)
############# sort
db.users.find({country:"USA"})
.sort({'username':1, followers_count: -1})
############# pretty
b.users.find({username: 'kimkardashian'}).pretty()
The order of keys matters in MongoDB
Read Operations
db.books.find().pretty()
Read Operations
The order of keys matters in MongoDB
{
"_id" : ObjectId("58b4af99245cbeab4ab25b51"),
"title" : "Woe from Wit",
"meta" : {
"author" : "A. Griboyedov",
"year" : 1823
}
}
The order of keys matters in MongoDB
db.books.find(
{ meta:
{ year: 1823, author: "A. Griboyedov" }
}
).count()
db.books.find(
{ meta:
{ author: "A. Griboyedov", year: 1823 }
}
).count()
0
1
Read Operations
Update Operations
- db.collection.updateOne()
- db.collection.updateMany()
- db.collection.replaceOne()
Update Operations - upsert
db.people.updateMany(
{ name: "Andy" },
{
name: "Andy",
rating: 1,
score: 1
},
{ upsert: true }
)
If set to true, creates a new document when no document matches the query criteria.
Create/Update Operations - save
Updates an existing document or inserts a new document, depending on its document parameter.
db.products.save( { item: "book", qty: 40 } )
> WriteResult({ "nInserted" : 1 })
db.inventory.updateMany(
{ _id: 1 },
{ $addToSet: { tags: ["accessories", "camera"] } }
)
{ _id: 1, item: "polarizing_filter", tags: [ "electronics", "camera" ] }
db.inventory.updateMany(
{ _id: 2 },
{ $addToSet: {
tags: {
$each: [ "camera", "electronics", "accessories" ] }
}
}
)
{ "_id" : 1, "item" : "polarizing_filter",
"tags" : [ "electronics", "camera", [ "accessories", "camera" ] ] }
{ "_id" : 1, "item" : "polarizing_filter",
"tags" : [ "electronics", "camera", "accessories" ] }
Update Operations - arrays
Delete Operations
db.characters.bulkWrite(
[
{ insertOne :
{
"document" :
{
"_id" : 4, "char" : "Dithras", "class" : "barbarian", "lvl" : 4
}
}
},
{ insertOne :
{
"document" :
{
"_id" : 5, "char" : "Taeln", "class" : "fighter", "lvl" : 3
}
}
},
{ updateOne :
{
"filter" : { "char" : "Eldon" },
"update" : { $set : { "status" : "Critical Injury" } }
}
},
{ deleteOne :
{ "filter" : { "char" : "Brisbane"} }
},
{ replaceOne :
{
"filter" : { "char" : "Meldane" },
"replacement" : { "char" : "Tanys", "class" : "oracle", "lvl" : 4 }
}
}
]
);
Bulk Write Operations
{
"acknowledged" : true,
"deletedCount" : 1,
"insertedCount" : 2,
"matchedCount" : 2,
"upsertedCount" : 0,
"insertedIds" : {
"0" : 4,
"1" : 5
},
"upsertedIds" : {
}
}
Bulk Write Operations
Bulk Write Operations
- Ordered list of operations: executes the operations serially.
- Unordered list of operations: can execute the operations in parallel, but is not guaranteed.
- By default, bulkWrite() performs ordered operations.
To specify unordered write operations, setordered : false in the options document.
Aggregation
Aggregation Pipeline
Aggregation Expressions
- Data is processed in RAM
-
You can use indexes to improve performance in the various stages. In addition, it has an internal optimization phase.
-
It runs in native code written in C ++
- "Flexible, functional and easy to learn"
db.people.aggregate(
{
$match: {
isActive: true
}
},
{
$project: {
isActive: 1,
name: 1,
mainAddress: "$address.primary"
}
});
Aggregation Expressions
db.people.aggregate(
{
$group: {
_id: {
gender: "$gender"
},
averageAge: {
$avg: "$age"
},
count: {
$sum: 1
}
}
});
Aggregation Expressions
db.inventory.aggregate( [ { $unwind : "$sizes" } ] )
Aggregation Expressions - $unwind
{ "_id" : 1, "item" : "ABC1", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "L" }
{ "_id" : 1, "item" : "ABC1", sizes: [ "S", "M", "L"] }
{ "_id" : 1, "item" : "abc", "price" : 12, "quantity" : 2 }
{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1 }
{ "_id" : 3 }
Aggregation Expressions - $lookup
{ "_id" : 1, "sku" : "abc", description: "product 1", "instock" : 120 }
{ "_id" : 2, "sku" : "def", description: "product 2", "instock" : 80 }
{ "_id" : 3, "sku" : "ijk", description: "product 3", "instock" : 60 }
{ "_id" : 4, "sku" : "jkl", description: "product 4", "instock" : 70 }
{ "_id" : 5, "sku": null, description: "Incomplete" }
{ "_id" : 6 }
Orders
Inventory
db.orders.aggregate([
{
$lookup:
{
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
])
Aggregation Expressions - $lookup
Aggregation Expressions - $lookup
{
"_id" : 1,
"item" : "abc",
"price" : 12,
"quantity" : 2,
"inventory_docs" : [
{ "_id" : 1, "sku" : "abc", description: "product 1", "instock" : 120 }
]
}
{
"_id" : 2,
"item" : "jkl",
"price" : 20,
"quantity" : 1,
"inventory_docs" : [
{ "_id" : 4, "sku" : "jkl", "description" : "product 4", "instock" : 70 }
]
}
{
"_id" : 3,
"inventory_docs" : [
{ "_id" : 5, "sku" : null, "description" : "Incomplete" },
{ "_id" : 6 }
]
}
Map Reduce
While the custom JavaScript provide great flexibility compared to the aggregation pipeline, in general, map-reduce is less efficient and more complex than the aggregation pipeline.
Map Reduce
Map Reduce - Example
{
_id: ObjectId("50a8240b927d5d8b5891743c"),
cust_id: "abc123",
ord_date: new Date("Oct 04, 2012"),
status: 'A',
price: 25,
items: [ { sku: "mmm", qty: 5, price: 2.5 },
{ sku: "nnn", qty: 5, price: 2.5 } ]
}
Return the Total Price Per Customer
Map Reduce - Example
Return the Total Price Per Customer
# 1- Define the map function to process each input document:
var mapFunction1 = function() {
emit(this.cust_id, this.price);
};
# 2- Define the reduce function:
var reduceFunction1 = function(keyCustId, valuesPrices) {
return Array.sum(valuesPrices);
};
# 3- Perform the map-reduce on all documents
db.orders.mapReduce(
mapFunction1,
reduceFunction1,
{ out: "map_reduce_example" }
)
Indexes
Before creating indexes
-
Understand your data
-
How your queries will be
-
Create as few indexes as possible
-
Put your highly selective fields first in the index
-
Check your current indexes before creating new ones
Create index
B-Tree
Index types
Single Field
{
"_id": ObjectId("570c04a4ad233577f97dc459"),
"score": 1034,
"location": { state: "NY", city: "New York" }
}
db.records.createIndex( { score: 1 } )
db.records.find( { score: 2 } )
db.records.find( { score: { $gt: 10 } } )
The sort order (i.e. ascending or descending) of the index key does not matter
Index types
Compound Index
{
"_id": ObjectId(...),
"item": "Banana",
"category": ["food", "produce", "grocery"],
"location": "4th Street Store",
"stock": 4,
"type": "cases"
}
db.products.createIndex( { "item": 1, "stock": 1 } )
db.products.find( { item: "Banana" } )
db.products.find( { item: "Banana", stock: { $gt: 5 } } )
The order of fields has significance
Index types
Multikey Index: index the content stored in arrays
{ _id: 5, type: "food", item: "aaa", ratings: [ 5, 8, 9 ] }
{ _id: 6, type: "food", item: "bbb", ratings: [ 5, 9 ] }
{ _id: 7, type: "food", item: "ccc", ratings: [ 9, 5, 8 ] }
{ _id: 8, type: "food", item: "ddd", ratings: [ 9, 5 ] }
{ _id: 9, type: "food", item: "eee", ratings: [ 5, 9, 5 ] }
db.inventory.createIndex( { ratings: 1 } )
db.inventory.find( { ratings: [ 5, 9 ] } )
Index types
Text Indexes: supports searching for string content in a collection
- A collection can have at most one text index
- You can index multiple fields for the text index
- Starting in MongoDB 3.2, MongoDB introduces a version 3 of the text index (improved case insensitivity, diacritic insensitivity & additional delimiters for tokenization)
Index types
Text Indexes - example
db.articles.insert(
[
{ _id: 1, subject: "coffee", author: "xyz", views: 50 },
{ _id: 2, subject: "Coffee Shopping", author: "efg", views: 5 },
{ _id: 3, subject: "Baking a cake", author: "abc", views: 90 },
{ _id: 4, subject: "baking", author: "xyz", views: 100 },
{ _id: 5, subject: "Café Con Leche", author: "abc", views: 200 },
{ _id: 6, subject: "Сырники", author: "jkl", views: 80 },
{ _id: 7, subject: "coffee and cream", author: "efg", views: 10 },
{ _id: 8, subject: "Cafe con Leche", author: "xyz", views: 10 }
]
)
db.articles.createIndex( { subject: "text" } )
Index types
Text Indexes - example
# Search string of coffee
db.articles.find( { $text: { $search: "coffee" } } )
# Search string of three terms delimited by space, "bake coffee cake"
db.articles.find( { $text: { $search: "bake coffee cake" } } )
# Searches for the phrase coffee shop
db.articles.find( { $text: { $search: "\"coffee shop\"" } } )
# Searches for documents that contain the words coffee
# but do not contain the term shop
db.articles.find( { $text: { $search: "coffee -shop" } } )
# The following query specifies es, i.e. Spanish, as the language that
# determines the tokenization, stemming, and stop words
db.articles.find(
{ $text: { $search: "leche", $language: "es" } }
)
# Case sensitive search for the phrase Café Con Leche
db.articles.find( {
$text: { $search: "\"Café Con Leche\"", $caseSensitive: true }
} )
# Diacritic sensitive text search on the term CAFÉ,
# or more precisely the stemmed version of the word
db.articles.find( { $text: { $search: "CAFÉ", $diacriticSensitive: true } } )
Index Properties
Sparse Indexes
Only contain entries for documents that have the indexed field, even if the index field contains a null value
db.scores.createIndex(
{ score: 1 } ,
{ sparse: true }
)
Index Properties
Sparse Indexes
# is using the index...
db.scores.find( { score: { $lt: 90 } } )
# is not using the index...
db.scores.find().sort( { score: -1 } )
# you can explicitly specify the index...
db.scores.find().sort( { score: -1 } ).hint( { score: 1 } )
db.scores.createIndex(
{ score: 1 } ,
{ sparse: true }
)
Index Properties
Partial Indexes (New in version 3.2)
Only index the documents in a collection that meet a specified filter expression
db.restaurants.createIndex(
{ cuisine: 1, name: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } }
)
Index Properties
Partial Indexes (New in version 3.2)
db.restaurants.createIndex(
{ cuisine: 1, name: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } }
)
# is using the index...
db.restaurants.find( { cuisine: "Italian", rating: { $gte: 8 } } )
# is not using the index...
db.restaurants.find( { cuisine: "Italian", rating: { $lt: 8 } } )
db.restaurants.find( { cuisine: "Italian" } )
Partial indexes offer a superset of the functionality of sparse indexes. If you are using MongoDB 3.2 or later, partial indexes should be preferred over sparse indexes.
Index Properties
Unique Indexes
Reject duplicate values for the indexed field
db.members.createIndex(
{ groupNumber: 1, lastname: 1, firstname: 1 },
{ unique: true }
)
Unique Partial Indexes - New in version 3.2.
If you specify both the partialFilterExpression and a unique constraint, the unique constraint only applies to the documents that meet the filter expression.
Index Properties
TTL Indexes (Time-To-Live)
Automatically remove documents from a collection after a certain amount of time or at a specific clock time
db.eventlog.createIndex(
{ "lastModifiedDate": 1 },
{ expireAfterSeconds: 3600 }
)
Covered Query
An index covers a query when both of the following apply:
- all the fields in the query are part of an index, and
- all the fields returned in the results are in the same index
# index
db.inventory.createIndex( { type: 1, item: 1 } )
# cover query
db.inventory.find(
{ type: "food", item:/^c/ },
{ item: 1, _id: 0 }
)
It doesn't work with multi-key and field-embedded indexes
Explain
db.collection.find().explain()
Verbose options:
- queryPlanner: default
- executionStats: stats for the winner plan
- allPlansExecution: stats for all candidate plans
Explain
# without index...
db.getCollection('posts').find(
{username: 'kimkardashian', timestamp: {$gte: 1483228800}}
).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "instagram.posts",
[...]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 49,
"executionTimeMillis" : 2956,
"totalKeysExamined" : 0,
"totalDocsExamined" : 2644172,
[...]
},
}
Explain
# with 2 simple index...
db.getCollection('posts').createIndex({username: 1})
db.getCollection('posts').createIndex({timestamp: -1})
# query
db.getCollection('posts').find(
{username: 'kimkardashian', timestamp: {$gte: 1483228800}}
).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "instagram.posts",
[...]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 49,
"executionTimeMillis" : 38,
"totalKeysExamined" : 3670,
"totalDocsExamined" : 3670,
[...]
},
}
Explain
# with a compound index...
db.getCollection('posts').createIndex({username: 1, timestamp: -1})
# query
db.getCollection('posts').find(
{username: 'kimkardashian', timestamp: {$gte: 1483228800}}
).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "instagram.posts",
[...]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 49,
"executionTimeMillis" : 124,
"totalKeysExamined" : 49,
"totalDocsExamined" : 49,
[...]
},
}
Explain
# with a compound index...
db.getCollection('posts').createIndex({username: 1, timestamp: -1})
# with a covered query
db.getCollection('posts').find(
{username: 'kimkardashian', timestamp: {$gte: 1483228800}},
{username: 1, timestamp: 1}
).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "instagram.posts",
[...]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 49,
"executionTimeMillis" : 0,
"totalKeysExamined" : 49,
"totalDocsExamined" : 49,
[...]
},
}
Not Indexable Queries
Lessons learned
"sometimes you win,
sometimes you learn"
The document model - very important!!
The document model - very important!!
MongoDB as attribute of PostgreSQL...
MongoDB in production needs maintenance tasks
Thank you!
MongoDB Basics
By aliciapj
MongoDB Basics
- 1,696