If you can't read please download the document
Upload
yboussard
View
4.778
Download
0
Embed Size (px)
Citation preview
Prsentation d'un nouveau produit
Les bases NoSQL et Python
Youenn Boussard
Les bases de donnes
Avant 1960 : organisation classique sous forme de fichier
1960 : 1er base de donne : militaire , hirarchique, sous forme d'arbre
1970 : Thorie sur l'algbre relationnelle de Codd
1980 : troisime gnration des sgbd : les bases de donnes orientes objets
Les donnes sont apparues avec les programmes. L'exploitation des ces donnes est la raison d'tre de l'informatique.Avec la gestion automatique des donnes, l'entreprise et les tats peuvent voluer et rivaliser avec leur concurrent l'aide de nouveaux moyens. Base de donne relationnelle : donnes dans de simple tables deux dimentions Suffisant pour toutes les applications ? Milieu des annes 80 : conviction que tout les problemes seront rsolue par les bases de donne avec leur structure logique et physique, indpendante des applications.Anne 90 avenement des technologies objets : Des estimations montrent en effet que les dveloppeurs de programmes OO utilisant des bases de donnes relationnelles passent entre 25 et 40 % de leur temps crire un code mappant les objets aux tables relationnelles.
Le dveloppement Internet
1962 Premier concept d'internet
1969 RFC
1974 Mise au point de la norme IP
1981 213 ordinateurs connects
1989 Naissance du World wide Web
2004 Web 2.0, Facebook
2006 twitter
186,7 millions de sites web
Lhistoire de lInternet remonte au dveloppement des premiers rseaux de tlcommunication. Lide dun rseau informatique, permettant aux utilisateurs de diffrents ordinateurs de communiquer, se dveloppa par de nombreuses tapes successives. 1 Licklider prsente l'ordinateur comme un outil de communication et de partage des ressources. C'est le concept de l'Internet.2 Request for comment mise en place des norme qui rgiront internet4 Ds les annes 1980, les techniques que nous reconnaissons maintenant comme les fondements de lInternet moderne commencrent se rpandre autour du globe5 - La tendance depuis 2004 est l'apparition d'applications web 2.0 pour lesquelles l'internaute joue un rle participatif.
Web 2.0 et les limites des modles relationnels
Beaucoup de donnes Digg 3TB
Facebook 50TB
Ebay 2PB
Beaucoup d'utilisateurs
Beaucoup de trafic
Et les bases relationnelles pour grer tout cela ?
Digg est un site Internet communautaire qui a pour but de faire voter les utilisateurs pour une page web intressante et propose par un utilisateurFacebook
Le cas de
Partition verticale matre-esclave avec
Lorsque l'on veux scaler une base de donnes,une premire solution peut tre le partitionnement :Vertical : une base pour les livres, une base pour les CDs, etcHorizontal : une base pour les livre de A-M et une pour les livres de N-Z,
Matre esclave
Rplication Matre esclaveUn unique matre
Un ou plusieurs esclave
Toute les critures vont sur le matre, rpliquer sur les esclaves
Les lectures sont rparties entre les diffrents matres et esclaves
Cela impliqueLe matre est critique
Le matre est le point d'engorgement du systme
Partition Horizontale
Rparties sur plusieurs noeuds
Les jointures sont dportes au niveau de l'application
Cela impliqueOn n'est plus relationnel
Le fonctionnel devient compliqu
La maintenance aussi !!
On renonce la normalisation des donnes Frais de gestion importanteLa scabilit a un coup qui devient important
Et la disponibilit !!
Table Diggs Id
Itemid
Userid
Digdate
Table Friends Id
UserId
UserName
FriendId
FriendName
Mutual
Date_created
Des centaines de millions de lignesTable Diggs Id
Itemid
Userid
Digdate
Des millions de lignesProbleme : Afficherles amis qui on cliqu sur l'un de mes diggs (fonctionnalitdu green badge)
14sec
Sytme distribu : Trois tats
C comme Consistence
A comme Availability (Disponibilit)
P comme Partition tolrance
Dr. Eric Brewer
CAP THEOREM =On ne peut avoir que 2 tats en simultane dans un
systme
distribuconsistence -> tout les noeuds ont les meme donnes en
meme tempsdisponible -> le systeme est toujours disponible tant
qu'il y a un noeudtolrance la partition -> le systeme continue
fonctionner malgr la perte de message
ACID contre BASE
Atomique
Cohrente
Isole
Durable
Basically Available
Soft state
Eventually consistent
Certaines donnes nous paraissent logiques d'tre consistantes mais cela est souvent du notre inertie d'esprit. Prenons le cas d'une valeur indiquant le stock d'un article. Si deux serveurs ont des valeurs non consistantes, il peut arriver qu'un serveur considre qu'il en reste un alors qu'un autre qui a tait mis jour, sait qu'il n'en reste plus. Lorsque vous faites un achat et si par malchance vous acheter un livre qui n'est plus en stock, le site marchand deux possibilits : vous rembourser ou rapprovisionner le stock (je ne sais pas vous, mais cela m'est dj arriv de recevoir un mail en disant que le produit tait puis,aprs l'avoir command)les sites ont fait leur choix ! Amazon a d'ailleurs constat qu'un dixime de seconde de latence leur fait diminuer les ventes de 1%, et Google a remarqu qu'une demi seconde de latence fait chuter le trafic d'un cinquime.
Non relationnelle
Distribue
Open source
Scalable horizontablement
Une nouvelle Gnration de base de donne
Nosql : not only sql
schema-free, easy replication support, simple API, eventually consistent / BASE (not ACID)
Les diffrents types de base de donne :Relationnel ACIDKey-value support get, put, delete sur leur cleOriente colonne utilise des tables mais pas de joins, les donnes sont stockes par colonne oppos aux traditionnels donnes stock en ligneOriente document stocke des documents structure en JSON ou en XML : facil de mapper avec des langages oriente objets
CA : ont des difficults avec les partitions et doivent rpliquer leurs donnes (base de donne)CP : ont des difficults avec la disponibilits en gardant consistante leurs donnes sur l'ensemble des noeudsAP : eventuellement consitant avec la replication et les verifications.
CouchDB est
Orient document Pas de schma, reprsentation des donnes telle quelle
Accessible via RESTful
Distribu, rplication incrmental, rsolution de conflit bi directionnel
Donne indexable et requetable suivant des vues
Ecrit en
Elle est conue avant tout pour les applications web
Chaque document est compos de proprits. Il ny a aucune contrainte sur le nombre de proprits dun document, sur le type de ces proprits...
Le protocole permettant daccder un serveur CouchDB est bas sur HTTP, avec une interface REST.
Erlang est langage fonctionnel concurrent, temps rel et distribu bas sur le modle d'acteur. Il possde des fonctionnalits de tolrance aux pannes et de mise jour du code chaud permettant le dveloppement d'applications haute disponibilit.
Un document
Un document est un objet contenant un ensemble de cls valeurs
{"_id": "j_aime_le_mouvement_no_sql" "_rev": "1-1" "Subject": "J'aime le mouvement no sql" "Author": "Youyou" "PostedDate": "5/23/2006" "Tags": ["database", "nosql"]"Body": "Je prsente aujourd'hui une prsentation!!."Une base de donnes couchdb est une collection plat de ces documents
Il y a trois cls qui sont imposes par CouchDB : _id : lidentifiant unique du document ; _rev : le numro de rvision du document, gr automatiquement par le moteur CouchDB ; _attachments : les ventuels fichiers attachs ce document.Stocke sous la forme de texte json ( Javascript Standard Object Notation) srialisation d'objetune chane de caractres doit tre mise entre quotes : "ceci est un string". un objet (qui supporte des donnes de type "cl" => valeur") est reprsent par des accolades : { "key1": "value1", "key2":"value2" }. une liste de valeurs (un tableau) est reprsent par des crochets : [ "value1","value2"]. JSON supporte les boolens : true et false ainsi que null. JSON supporte les nombres flottants : 21.34576.MVCC : multiversion concurrency control
RESTFul API
REST est bas sur le protocole HTTPLecture : GET /somedatabase/some_doc_id
Ecriture / update : PUT
Crer : POST
Supprimer : DELETE
PUT /somedatabase/some_doc_id HTTP/1.0Content-Length: 245Content-Type: application/json
Body
REST : Representational State Transfer, est connu comme une alternative simple au protocole SOAP, pour interfacer des web services. Rest nest pas un protocole, cest un style darchitecture.
Robuste
N'crase pas une donne commit
Si le serveur tombe, il faut juste redmarrer CouchDB pas de repair
On peut prendre des snapshots avec des simples cp
Plusieurs niveaux de durabilit : Choix entre synchroniser toutes les mises jours ou la demande
CouchDB uses an "optimistic concurrency" modelIe Couchdb rejete le document si celui si n'est pas celui que vous avez envoyer !Retrieve the document, take note of the _rev property that CouchDB sends alongDecrement the quantity field, if it's greater than zeroSend the updated document back, using the _rev propertyIf the _rev matches the currently stored number, be done!If there's a conflict (when _rev doesn't match), retrieve the newest document version
Vues
Mthodes pour aggrger et requeter les documents de la base de donne
Les vues sont dfinies par des documents spciaux les designs documents
Les vues sont crites en javascript.
Pour maintenir des performances sur les vues, le moteur de vues maintient des indexes sous forme btree
MapReduce
Introduit par Google
2 tapeEtape Map itre sur une liste d'lments indpendants et accomplit une opration spcifique sur chaque lment
function(doc){}
Etape Reduce prend une liste et combine les lments selon un algorithme particulier
fonction(keys, values, rereduce){}
MapPar exemple, considrons une liste de notes d'examen, o chaque note est 1 point trop leve. Une fonction map de s - 1 pourrait tre applique sur chaque note s.
Reduce comment faire si l'on souhaite connatre la moyenne de la classe ? On peut dfinir une fonction de rduction qui diminue de moiti la liste par ajout d'une entre dans la liste des voisins, rcursivement, on continue jusqu' ce qu'il y ait seulement une (grosse) entre, et divise la somme totale par l'entre originale d'lments pour avoir la moyenne).
{ "_id":"_design/company", "_rev":"12345", "language": "javascript", "views": { "all": { "map": "function(doc) { if (doc.Type == 'customer') emit(null, doc) }" }, "by_lastname": { "map": "function(doc) { if (doc.Type == 'customer') emit(doc.LastName, doc) }" }, "total_purchases": { "map": "function(doc) { if (doc.Type == 'purchase') emit(doc.Customer, doc.Amount) }", "reduce": "function(keys, values) { return sum(values) }" } }}
Exemple d'une vue
Exemple de map/reduce
couchdb-python
Ct clientcouchdb.client client pour s'interfacer avec des servers couchdb
couchdb.design pour grer les design documents
couchdb.mapping fournit des mappings entre les document json et les objets python
Cot serveurcouchdb.view pour crire des vues en python plutt qu'en javascript
couchdb.client.Server
Reprsentation d'un server CouchDB>>> server = Server('http://localhost:5984/')
Pour crer une base de donnes : create >>> server.create('python-tests')
Pour accder une base de donnes >>> server['mabase']
Pour supprimer une base de donnes>>> del server['mabase']
couchdb.client.Database
Cration d'un nouveau document>>> server = couchdb.client.Server()>>> db = server.create('test')>>> doc_id, doc_rev = db.save({'type' : 'contact', 'name': 'yo'})
Rcuperation d'un document>>> db[doc_id]
Un document est comme dictionnaire>>> db[doc_id]['type']'contact'
couchdb.client.ViewResults
Reprsentation d'une vue (permanent ou temporaire)>>> map_fun = '''function(doc) {emit([doc.type, doc.name], doc.name); }'''>>> results = db.query(map_fun)
En option, on peut envoyer tous les paramtres d'une vue>>> db.query(map_fun, count = 10 , skip = 5, descending = true)
On peut utiliser les slices python pour positionner des startkeys et les endkeys>>> results[keystart:keyend]
Mapper les documents Couchdb
Permet de mapper des structures json avec du python et vice versa>>> Class Person(couchdb.mapping.Document): name = couchdb.mapping.TextField() age = couchdb.mapping.IntegerField() modified = couchdb.mapping.DateTimeField(default=datetime.now)>>> person = Person(name='youyou', age = 32)>>> person.store(db)>>> Person.load(db, 'youyou').rev....
couchdb.mapping.ViewField
Pour associer une vue un attribut d'une classeclass Person(Document): by_name = ViewField('people', '''\
... function(doc) { ... emit(doc.name, doc); ... }''')
>>> Person.by_name(db, count=3)
couchdbkit
Bas sur restkit , librairie http qui n'est pas bas sur urllib2 ou httplib
couchdbkit.client API client vers couchdb
Mapping dynamique des documents
Extension django
couchdbkit.consumer Ecoute les changements effectus sur la base de donnes
couchdbkit.loaders pousse des fichiers de vues sur couchdb
CouchApp
Utilitaire de dploiement d'application pour couchDB
Ecrit en python
Cre un squellete d'application
Gnre du code l'aide de macros
Dploie les applications sur des serveurs CouchDB
Introduction
Utiliser en production par Digg, FaceBook, Twitter, Reddit
Tolrant la panne
Dcentraliser
Sous contrle
Modle de donnes efficient et efficace
Elastique
Durable
1 Le plus gros cluster cassandra utilise 100 TB de donne rpartie sur 150 machine2 Les donne sont automatiquement repliqus dur des mutliples noeud . La replication entre plusieurs data center est supporter .Les noeuds mort sont remplacer sans rupture de service3 Tout les noeuds sont identiques.Pas de point de failure4 Vous avez le choix entre une replication assynchrone et synchone pour chaque mis jour5 Modele oriente colonne5 les lectures et les critures augmente lineairement lorsqu'on ajoute des machines6 Procedure via un commitlog de ne perdre aucune donne
Modle de donnes
ColonneLa plus petite unit de donnes dans cassandra
C'est un triplet
{ // this is a column name: "mailAddress", value: "[email protected]", timestamp: 123456789}
Ou plus simplement
emailAdress : "[email protected]"
Modle de donnes
Super ColonneC'est un tuple qui a un nom et une valeur (comme la colonne)
Mais la valeur est une liste de colonnes
{ // this is a SuperColumn name: "homeAddress", // with an infinite list of Columns value: { // note the keys is the name of the Column street: {name: "street", value: "1234 x street", timestamp: 123456789}, city: {name: "city", value: "san francisco", timestamp: 123456789}, zip: {name: "zip", value: "94107", timestamp: 123456789}, }}
Famille de colonnes
Une famille de colonnes est une structure qui contient une liste de lignes (comme les bases de donnes)
UserProfile = { // this is a ColumnFamily phatduckk: { // this is the key to this Row inside the CF // now we have an infinite # of columns in this row username: "phatduckk", email: "[email protected]", phone: "(900) 976-6666" }, // end row ieure: { // this is the key to another row in the CF // now we have another infinite # of columns in this row username: "ieure", email: "[email protected]", phone: "(888) 555-1212" age: "66", gender: "undecided" },}
Modle de donnes
Super Famille de colonnesUne Famille de colonne peut tre super ou standard
Super c'est que les lignes contiennent des super colonnes aka cle : list( colonne)
Une ligne est une liste de colonnes ou de super colonnes identifies par une cl
Super famille de colonne
AddressBook = { // this is a ColumnFamily of type Super phatduckk: { // this is the key to this row inside the Super CF // the key here is the name of the owner of the address book
// now we have an infinite # of super columns in this row // the keys inside the row are the names for the SuperColumns // each of these SuperColumns is an address book entry friend1: {street: "8th street", zip: "90210", city: "Beverley Hills", state: "CA"},
// this is the address book entry for John in phatduckk's address book John: {street: "Howard street", zip: "94404", city: "FC", state: "CA"}, Kim: {street: "X street", zip: "87876", city: "Balls", state: "VA"}, Tod: {street: "Jerry street", zip: "54556", city: "Cartoon", state: "CO"}, Bob: {street: "Q Blvd", zip: "24252", city: "Nowhere", state: "MN"}, ... // we can have an infinite # of ScuperColumns (aka address book entries) }, // end row ieure: { // this is the key to another row in the Super CF // all the address book entries for ieure joey: {street: "A ave", zip: "55485", city: "Hell", state: "NV"}, William: {street: "Armpit Dr", zip: "93301", city: "Bakersfield", state: "CA"}, },}
L'ensemble des familles de colonnes et des supers familles de colonnes constituent un espace de cls (keyspace)
Modle de donnes
Les cls sont tries lorsqu'elle sont insres dans le modle
Ce tri est respect quand on recupre les lments le model doit tre conu en fonction de cela
Les lignes sont tries par leur nom
Les options de tri se font au niveau des familles de colonnes
Twissandra : un twitter like
http://github.com/ericflo/twissandra
Cassandra par l'exemple en python
La faon de structurer les donnes doit tre proche de la faon pour lesquelles on doit les rcuprer pour les afficher
Twissandra : un twitter like
User colonne family
La cl de la ligne est l'id de l'utilisateur
User = { 'a4a70900-24e1-11df-8924-001ff3591711': { 'id': 'a4a70900-24e1-11df-8924-001ff3591711', 'username': 'ericflo', 'password': '****', },}
Twissandra : un twitter like
Les amis et les adeptes sont indexs par le user id
Friends = { 'a4a70900-24e1-11df-8924-001ff3591711': { # friend id: timestamp of when the friendship was added '10cf667c-24e2-11df-8924-001ff3591711': '1267413962580791', '343d5db2-24e2-11df-8924-001ff3591711': '1267413990076949', '3f22b5f6-24e2-11df-8924-001ff3591711': '1267414008133277', },}
Twissandra : un twitter like
Les tweets sont stokes comme les utilisateurs
Tweet = { '7561a442-24e2-11df-8924-001ff3591711': { 'id': '89da3178-24e2-11df-8924-001ff3591711', 'user_id': 'a4a70900-24e1-11df-8924-001ff3591711', 'body': 'Trying out Twissandra. This is awesome!', '_ts': '1267414173047880', },}
Twissandra : un twitter like
Les tweets sont stokes comme les utilisateurs
Tweet = { '7561a442-24e2-11df-8924-001ff3591711': { 'id': '89da3178-24e2-11df-8924-001ff3591711', 'user_id': 'a4a70900-24e1-11df-8924-001ff3591711', 'body': 'Trying out Twissandra. This is awesome!', '_ts': '1267414173047880', },}
Twissandra : un twitter like
La ligne de temps et la ligne des utilisateurs doivent afficher les tweets par ordre d'arrive
Timeline = { 'a4a70900-24e1-11df-8924-001ff3591711': { # timestamp of tweet: tweet id 1267414247561777: '7561a442-24e2-11df-8924-001ff3591711', 1267414277402340: 'f0c8d718-24e2-11df-8924-001ff3591711', 1267414305866969: 'f9e6d804-24e2-11df-8924-001ff3591711', 1267414319522925: '02ccb5ec-24e3-11df-8924-001ff3591711', },}Userline = { 'a4a70900-24e1-11df-8924-001ff3591711': { # timestamp of tweet: tweet id 1267414247561777: '7561a442-24e2-11df-8924-001ff3591711', 1267414277402340: 'f0c8d718-24e2-11df-8924-001ff3591711', 1267414305866969: 'f9e6d804-24e2-11df-8924-001ff3591711', 1267414319522925: '02ccb5ec-24e3-11df-8924-001ff3591711', },}
Twissandra : un twitter like
Twissandra utilise pycassa , API de haut niveau pour accder cassandra
Pycassa utilise thrift, un framework d'appel de procdure distance
Thrift gere 12 languages dont python
pycassa.columnfamily.ColumnFamily
Opration sur la famille de colonne
>>> cf = pycassa.ColumnFamily(client, 'UserName')
## insertion d'une colonne>>> cf.insert('user1', {'id': 'User'})1261349837816957
>>> cf.get('foo'){'column1': 'val1'}
## inserion de plusieurs colonne>>> cf.insert('user2', {'id': 'User', 'name': 'Name User'})
## recuperation des lignes>>> list(cf.get_range())
pycassa.columnfamilymap.ColumnFamilyMap
Pour mapper des objets avec des colonnes
>>> class Test(object):... string_column = pycassa.String(default='Your Default')... int_str_column = pycassa.IntString(default=5)... float_str_column = pycassa.FloatString(default=8.0)... float_column = pycassa.Float64(default=0.0)... datetime_str_column = pycassa.DateTimeString() # default=None
>>> Test.objects = pycassa.ColumnFamilyMap(Test, cf)
>>> t = Test()>>> t.key = 'maptest'>>> t.string_column = 'string test'>>> t.int_str_column = 18>>> t.float_column = t.float_str_column = 35.8>>> from datetime import datetime>>> t.datetime_str_column = datetime.now()>>> Test.objects.insert(t)1261395560186855
Les autres librairies python
pour cassandra
Tragedy: http://github.com/enki/tragedy/
Lazy Boy: http://github.com/digg/lazyboy/tree/master
Telephus: http://github.com/driftx/Telephus/tree/master (Twisted)
Neo4J
Modle de donnes sous la forme de graphe
Embarqu
Stock sur disque
Scalable
Framework de traverse
API simple et pratique
1 Au lieu de travailler sur des statc et rigide table avec des lignes et des colonnes vous travailler sur un graph flexible qui s'adapte a vos besoin en ayant a votre disposition des noeud , des relation et des proprits.
2 C'est l'application qui embarque la base de donne neo4j
3 Une gestion de stokage optimiser pour stocker plusieurs billion de noeud, de relaction , de proprietes. La base de donne peut etre rpartie sur plusieurs disque
4 framework performant de travers.
Le modele de donnes : Un graphe
orient
Reprsentation sous la forme de:Noeuds
Relations entre les noeuds
De proprits (au niveau des relations et noeuds)
Exemple : modlisation des familles de produits
Rgle mtierUne famille peut tre une sous-famille
Dans une famille il peut y avoir des produits
Chaque famille de produits peut avoir des proprits
Exemple d'une instance de la base
Neo4j.py : binding python pour Neo4j
Peut tre utilis indiffrement avecJython
Cpython
Ouvrir une base de donnes neo4j
graphdb = neo4j.GraphDatabase("/neo/db/path", classpath=["/a/newer/kernel.jar"], jvm="/usr/lib/jvm.so")
Neo4j.py : binding python pour Neo4j
Crer une noeud
>>> mac_node = graphdb.node(Name='MacBook')## ajouter des pro>>> mac_node['Name']'MacBook'Crer une relation entre les noeuds
>>> laptop_node = graphdb.node(Name='Laptop'')## la mac est un produit de la famille des laptop_node>>> laptop_node.PRODUCTS(mac_node)
Neo4j.py : binding python pour Neo4
TraversalsSont dfinis en crant une classe qui hrite de neo4j.Traversal
Un objet Traversal doit dfinir les attributs suivants:Types : la liste des relations qui vont tre traverses pendant la travers
Order : l'ordre de traverse
Stop : la condition de d'arrt
Returnable : La dfinition des noeuds qui vont tre retourns
Neo4j.py : binding python pour Neo4
class SubCategoryProducts(neo4j.Traversal): "Traverser that yields all products in a category and its sub categories." types = [neo4j.Outgoing.SUBCATEGORY, neo4j.Outgoing.PRODUCT] def isReturnable(self, pos): if pos.is_start: return False return pos.last_relationship.type == 'PRODUCT'
## get products of laptop_node>>> p = [x for x in SubCategoryProducts(laptop_node)]
Rfrences
NoSQLhttp://www.julianbrowne.com/article/viewer/brewers-cap-theorem
http://natishalom.typepad.com/nati_shaloms_blog/2009/12/the-common-principles-behind-the-nosql-alternatives.html
http://horicky.blogspot.com/2009/11/nosql-patterns.html
References
CouchDBhttp://www.unixgarden.com/index.php/web/couchdb-la-base-de-donnees-qui-change-tout
http://davidwatson.org/2008/02/python-couchdb-rocks.html
http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views
http://labs.mudynamics.com/wp-content/uploads/2009/04/icouch.html
http://horicky.blogspot.com/2008/10/couchdb-cluster.html
Rfrences
Cassandrahttp://www.slideshare.net/Eweaver/cassandra-presentation-at-nosql
http://spyced.blogspot.com/2009/03/why-i-like-cassandra.html
http://arin.me/blog/wtf-is-a-supercolumn-cassandra-data-model
http://wiki.apache.org/cassandra/ThriftExamples#Python
ttp://www.slideshare.net/stuhood/cassandra-talk-austin-jug
http://www.rackspacecloud.com/blog/2010/05/12/cassandra-by-example/
Rfrences
Neo4Jhttp://www.slideshare.net/thobe/persistent-graphs-in-python-with-neo4j
http://python.mirocommunity.org/video/1597/pycon-2010-persistent-graphs-i
http://blog.neo4j.org/2010/03/modeling-categories-in-graph-database.html