Serie Application Development Back to Basics – Schema Design
Senior Solutions Architects, MongoDB Inc.
Massimo Brignoli
#MongoDBBasicsIT
Agenda
• Lavorare con I documenti
• Requisiti dell’Applicazione
• Iterazione di Schema design
• Architettura dell’applicazione di esempio ‘myCMS’ e
frammenti di codice
• ‘Genius Bar’ Q&A con il team italiano di MongoDB
Il Linguaggio
RDBMS MongoDB
Database ➜ Database
Table ➜ Collection
Row ➜ Document
Index ➜ Index
Join ➜ Embedded Document
Foreign Key ➜ Reference
Modellare i Dati
Documento di Esempio
{
‘_id’ : ObjectId(..),
‘title’ : ‘Schema design in MongoDB’,
‘author’ : ‘massimobrignoli’,
‘text’ : ‘I dati in MongoDB hanno uno schema flessibile..’,
‘date’ : ISODate(..),
‘tags’ : [‘MongoDB’, ‘schema’],
‘comments’ : [ { ‘text ‘ : ‘Molto utile!!.’, ts: ISODate(..) } ]
}
Requisiti del ‘myCMS’
• E’ composto da diversi tipi di articoli categorizzati.
• Gli Utenti possono registrarsi come memobri, fare login,
modificare il loro profilo e fare logout.
• Gli Utenti possono pubblicare nuovi articoli e commentare gli
articoli.
• Il sistema raccoglie e analizza statistiche di utilizzo –
pubblicazione di articoli, visite e interazioni – da pubblicare sul
sito o per gli admin.
Le Entità del ‘myCMS’
• Articoli
• Diversi tipi: blog, gallerie di immagini, sondaggi
• Contengono file multimediali (immagini, video)
• Sono taggati
• Utenti
• Ognuno avrà un profilo
• Interazioni degli Utenti
• Commenti
• Visite
Tipico ERD (Diagramma Entità-Relazioni)
* Note: this is an illustrative example.
# Python dictionary (or object)
>>> article = { ‘title’ : ‘Schema design in MongoDB’,
‘author’ : ‘massimobrignoli’,
‘section’ : ‘schema’,
‘slug’ : ‘schema-design-in-mongodb’,
‘text’ : ‘I dati in MongoDB hanno uno schema dinamico.’,
‘date’ : datetime.datetime.utcnow(),
‘tags’ : [‘MongoDB’, ‘schema’] }
>>> db[‘articles’].insert(article)
Design schema.. usando codicesorgente
>>> img_data = Binary(open(‘article_img.jpg’).read())
>>> article = { ‘title’ : ‘Schema design in MongoDB’,
‘author’ : ‘massimobrignoli’,
‘section’ : ‘schema’,
‘slug’ : ‘schema-design-in-mongodb’,
‘text’ : ‘I dati in MongoDB hanno uno schema dinamico.’,
‘date’ : datetime.datetime.utcnow(),
‘tags’ : [‘MongoDB’, ‘schema’],
‘headline_img’ : {
‘img’ : img_data,
‘caption’ : ‘Documento di esempio’
}}
>>> db[‘articles’].insert(article)
Aggiungiamo un’immagine…
>>> article = { ‘title’ : ‘Il mio favorito framework per lo sviluppo web’,
‘author’ : ‘massimobrignoli’,
‘section’ : ‘web-dev’,
‘slug’ : ‘web-app-frameworks’,
‘gallery’ : [
{ ‘img_url’ : ‘http://x.com/45rty’, ‘caption’ : ‘Flask’, ..},
..
]
‘date’ : datetime.datetime.utcnow(),
‘tags’ : [‘MongoDB’, ‘schema’],
}
>>> db[‘articles’].insert(article)
E diversi tipi di articolo
>>> user= { ‘user’ : ‘massimobrignoli’,
‘email’ : ‘[email protected]’,
‘password’ : ‘passwordsegretissima’,
‘joined’ : datetime.datetime.utcnow()
‘location’ : { ‘city’ : ‘Cornaredo’ },
}
>>> db[‘users’].insert(user)
Utenti e Profili
Modellazione dei commenti (1)
• Si possono fare 2 collections – articles e
comments
• Usare una reference (i.e. foreign key) per collegarli
• Ma... saranno necessarie N+1 query per restituire
l’articolo e i commenti (sono N è il # di commenti){
‘_id’ : ObjectId(..),
‘title’ : ‘Schema design in MongoDB’,
‘author’ : ‘massimobrignoli’,
‘date’ : ISODate(..),
‘tags’ : [‘MongoDB’, ‘schema’],
‘section’ : ‘schema’,
‘slug’ : ‘schema-design-in-mongodb’,
‘comments’ : [ ObjectId(..), …]
}
{ ‘_id’ : ObjectId(..),
‘article_id’ : 1,
‘text’ : ‘Un grande articolo, mi ha
aiutato a capire lo schema
design’,
‘date’ : ISODate(..),,
‘author’ : ‘johnsmith’
}
Modellazione dei commenti (2)
• Una sola collection degli
articoli. I commenti sono
incapsulati.• Pregi
• Singola query, il documento è
disegnato per l’accesso il
lettura
• Localizzazione dei dato (disco,
shard)
• Difetti• L’array dei commenti e’ senza
limiti. I documenti possono
crescere in dimensione oltre I
16MB
{
‘_id’ : ObjectId(..),
‘title’ : ‘Schema design in MongoDB’,
‘author’ : ‘massimobrignoli’,
‘date’ : ISODate(..),
‘tags’ : [‘MongoDB’, ‘schema’],
…
‘comments’ : [
{
‘text’ : ‘Un grande articolo,
mi ha aiutato a capire lo
schema design’,
‘date’ : ISODate(..),
‘author’ : ‘johnsmith’
},
…
]
}
Modellazione dei commenti (3)
• Un’altra opzione: un ibrido delle proposte (1) e (2): incapsulare i top commenti (per data o popolarità) nel documento dell’articolo.
• Array dei commenti di grandezza fissa (feature 2.4)
• Tutti gli altri commenti sono scritti in una collection di
commenti in gruppi
• Vantaggi:
– Il documento ha la dimensione quasi fissata => meno
spostamenti sul disco
– Una singola query restituisce tutto il documento
– Rimane tutta la storia dei commenti con possibilità di analisi
Modellazione dei commenti (3){
‘_id’ : ObjectId(..),
‘title’ : ‘Schema design in MongoDB’,
‘author’ : ‘mattbates’,
‘date’ : ISODate(..),
‘tags’ : [‘MongoDB’, ‘schema’],
…
‘comments_count’: 45,
‘comments_pages’ : 1
‘comments’ : [
{
‘text’ : ‘A great article, helped me
understand schema design’,
‘date’ : ISODate(..),
‘author’ : ‘johnsmith’
},
…
]
}
Numero totale dei
commenti• Intero aggiornato dall’operazione
di update quando I commenti
sonoaggiunti/rimossi
Numero di pagineIntero aggiornato dall’operazionedi
update quando I commenti sono
aggiunti/rimossi
Array a dimensione fissa
per i commenti:• I 10 più recenti
• Ordinato per data in fase di
inserimento
Modellazione dei commenti (3)
{
‘_id’ : ObjectId(..),
‘article_id’ : ObjectId(..),
‘page’ : 1,
‘count’ : 42
‘comments’ : [
{
‘text’ : ‘A great article, helped me
understand schema design’,
‘date’ : ISODate(..),
‘author’ : ‘johnsmith’
},
…
}
Un gruppo di commenti
contiene fino a 100
commenti
E’ un array di 100 commenti, che
sono subdocumenti
Modellazione delle interazioni
• Interazioni– Visualizzazioni degli articoli
– Commenti
– (Condivisione sui social media)
• Requisiti
– Time serie
– Pre-aggregazione in preparazione della parte analitica
Modellazione delle interazioni
• Documento per articolo per
giorno: raggruppamento
• Contatore giornaliero e contatori
nei sotto-documenti orari delle
interazioni
• Array a dimensione fissa (24 ore)
• Basta una singola query per
avere tutte le interazioni di un
articolo in un giorno.
{
‘_id’ : ObjectId(..),
‘article_id’ : ObjectId(..),
‘section’ : ‘schema’,
‘date’ : ISODate(..),
‘daily’: { ‘views’ : 45, ‘comments’ :
150 }
‘hours’ : {
0 : { ‘views’ : 10 },
1 : { ‘views’ : 2 },
…
23 : { ‘comments’ : 14, ‘views’ : 10
}
}
}
JSON e RESTful API
Client-side
JSON
(eg AngularJS) (BSON)
Le applicazioni reali non sono fatte in una shell – Costruiamo
un’interfaccia RESTful.
Pymongo
driver
Python web
appHTTP(S) REST
Gli esempi che seguiranno: Python RESTful API usando Flask
microframework
myCMS REST punti di accesso
Method URI Action
GET /articles Restituisce tutti gli articoli
GET /articles-by-tag/[tag] Restituisce tutti gli articoli con una tag
GET /articles/[article_id] Restituisce un articolo tramite article_id
POST /articles Aggiunge un nuovo articolo
GET /articles/[article_id]/comments Restituisce tutti i commenti di un
articolo tramite article_id
POST /articles/[article_id]/comments Aggiunge un nuovo commento a un
articolo
POST /users Registra un utente
GET /users/[username] Restituisce il profilo di un utente
PUT /users/[username] Aggiorna il profilo di un utente
$ git clone http://www.github.com/mattbates/mycms-mongodb
$ cd mycms-mongodb
$ virtualenv venv
$ source venv/bin/activate
$ pip install –r requirements.txt
$ mkdir –p data/db
$ mongod --dbpath=data/db –fork --logpath=mongod.log
$ python web.py
($ deactivate)
Partiamo con lo scheletro del codice
@app.route('/cms/api/v1.0/articles', methods=['GET'])
def get_articles():
"""Retrieves all articles in the collection
sorted by date
"""
# query all articles and return a cursor sorted by date
cur = db['articles'].find().sort({'date’:1})
if not cur:
abort(400)
# iterate the cursor and add docs to a dict
articles = [article for article in cur]
return jsonify({'articles' : json.dumps(articles, default=json_util.default)})
Metodi dell’interfaccia RESTful in Python + Flask
@app.route('/cms/api/v1.0/articles/<string:article_id>/comments', methods = ['POST'])
def add_comment(article_id):
"""Adds a comment to the specified article and a
bucket, as well as updating a view counter
"””…
page_id = article['last_comment_id'] // 100…
# push the comment to the latest bucket and $inc the count
page = db['comments'].find_and_modify(
{ 'article_id' : ObjectId(article_id),
'page' : page_id},
{ '$inc' : { 'count' :1 },
'$push' : {
'comments' : comment } },
fields= {'count':1},
upsert=True,
new=True)
Metodi dell’interfaccia RESTful in Python + Flask
# $inc the page count if bucket size (100) is exceeded
if page['count'] > 100:
db.articles.update(
{ '_id' : article_id,
'comments_pages': article['comments_pages'] },
{ '$inc': { 'comments_pages': 1 } } )
# let's also add to the article itself
# most recent 10 comments only
res = db['articles'].update(
{'_id' : ObjectId(article_id)},
{'$push' : {'comments' : { '$each' : [comment],
'$sort' : {’date' : 1 },
'$slice' : -10}},
'$inc' : {'comment_count' : 1}})
…
Metodi dell’interfaccia RESTful in Python + Flask
def add_interaction(article_id, type):
"""Record the interaction (view/comment) for the
specified article into the daily bucket and
update an hourly counter
"""
ts = datetime.datetime.utcnow()
# $inc daily and hourly view counters in day/article stats bucket
# note the unacknowledged w=0 write concern for performance
db['interactions'].update(
{ 'article_id' : ObjectId(article_id),
'date' : datetime.datetime(ts.year, ts.month, ts.day)},
{ '$inc' : {
'daily.views' : 1,
'hourly.{}.{}'.format(type, ts.hour) : 1
}},
upsert=True,
w=0)
Metodi dell’interfaccia RESTful in Python + Flask
$ curl -i http://localhost:5000/cms/api/v1.0/articles
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 20
Server: Werkzeug/0.9.4 Python/2.7.6
Date: Sat, 01 Feb 2014 09:52:57 GMT
{
"articles": "[{\"author\": \"mattbates\", \"title\": \"Schema design in MongoDB\",
\"text\": \"Data in MongoDB has a flexible schema..\", \"tags\": [\"MongoDB\",
\"schema\"], \"date\": {\"$date\": 1391293347408}, \"_id\": {\"$oid\":
\"52ed73a30bd031362b3c6bb3\"}}]"
}
Testiamo l’API– Ritorno di articoli
$ curl -H "Content-Type: application/json" -X POST -d '{"text":"An interesting
article and a great read."}'
http://localhost:5000/cms/api/v1.0/articles/52ed73a30bd031362b3c6bb3/comment
s
{
"comment": "{\"date\": {\"$date\": 1391639269724}, \"text\": \"An interesting
article and a great read.\"}”
}
Testiamo l’API – Inserire un commento
Iterazioni con lo Schema
Avete una nuova feature nella lista di cose da fare?
I Documenti hanno uno schema dinamico, quindi
possiamo semplicemente iterare l’oggetto.
>>> user = { ‘username’ : ‘massimo’,
‘first’ : ‘Massimo’,
‘last’ : ‘Brignoli’,
‘preferences’ : { ‘opt_out’ : True } }
>>> user..save(user)
Scale out with sharding
Conclusioni
• Documenti a schema flessibile con l’abilità di
incapsulare strutture dati ricche e complesse,
ottimizzate per massimizare le performance.
• Lo Schema è disegnato attorno agli schema di
accesso ai dati e non per ottimizzare lo storage.
• Usate il Referencing per maggiore flessibilità
• Sviluppate lo schema con in mente la scalabilità
orizzontale, è importante considerare la sharding
key
Approfondimenti
• Il codice sorgente di ‘myCMS’
:http://www.github.com/mattbates/mycms-mongodb
• Use case - metadata and asset management:
http://docs.mongodb.org/ecosystem/use-
cases/metadata-and-asset-management/
• Use case - storing
comments:http://docs.mongodb.org/ecosystem/use-
cases/storing-comments/
Prossima Sessione – 8 Aprile
Interagire con il database– Il linguaggio delle query e update
– Le interazioni tra l’applicazione e il database
• Esempi di Codice
#MongoDBBasicsIT
Grazie!
Q&A con il team italiano di MongoDB