Slide 1

Slide 1 text

Recorriendo Nodos con Python Martin Alderete [email protected] Martin Riva [email protected]

Slide 2

Slide 2 text

Temario ● Introducción al mundo NoSQL. ● Introducción a los grafos. ● Graph DB’s. ● Modelando y conectándonos al grafo con Python. ● Caso de estudio: “App para citas” ○ Problemática. ○ Modelo IDEAL ○ Primera solución. ○ Avances ○ Nuestra solución usando Python, Django y grafos! ○ Tecnologias aplicadas ● Arquitectura Final ● Conclusiones y consideraciones

Slide 3

Slide 3 text

y muchas mas... NoSQL rapidamente

Slide 4

Slide 4 text

G = (V, A) V: Conjunto de Vertices A: Conjunto de Aristas Basicamente... “Un grafo indica que algo está relacionado a otra cosa” Intro a grafos No Dirigido Dirigido (Digrafo)

Slide 5

Slide 5 text

Intro a grafos: “Property Graph” ● Nodos ● Relaciones ● ID unico y Propiedades en ambos.

Slide 6

Slide 6 text

Graph DB’s Permiten almacenar grafos, es decir, nodos aristas y propiedades. Permiten realizar TRAVERSAL "Visitar los nodos de un grafo de una manera específica” Ejemplo: A*, Dijkstra, Floyd-Warshall, BFS, DFS, etc... Lenguajes de consultas específicos ( “EL SQL de los grafos”) Gremlin, Cypher, Thinkerpop, etc... Generalmente no es facil visualizar los datos del grafo, existen herramientas de terceros para facilitarlo. (Gephi, D3.js) Neo4j, TitanDB, OrientDB, FlockDB y varias más!

Slide 7

Slide 7 text

Grafos con Python: Bulbflow Overview 1. Es un ORM-Like para base de datos de grafos (NoSQL) 2. Permite abstraer el “modelo” del motor que usemos. 3. MUY simple de usar ! 4. MUY parecido al ORM de Django !!!! 5. Permite realizar consultas BASICAS, generalmente usando indices !!! Bulbflow (http://bulbflow.com/) pip install bulbflow (usen virtualenv)

Slide 8

Slide 8 text

Bulbflow: Ejemplo Nodo from bulbs.model import Node from bulbs.property import String, Integer, DateTime from bulbs.utils import current_datetime class Person(Node): element_type = "person" id = Integer(nullable=False, indexed=True) first_name = String(nullable=False, indexed=True) last_name = String(nullable=False, indexed=True) gender = String(nullable=False, indexed=True) created_on = DateTime(default=current_datetime) birth_date = String(indexed=True)

Slide 9

Slide 9 text

Bulbflow: Ejemplo Relationship from bulbs.model import Relationship from bulbs.utils import current_datetime class Friend(Relationship): label = "friend" created_on = DateTime(default=current_datetime) Ejemplo completo: g = Graph(...) g.add_proxy(“persons”, Person) g.add_proxy(“friends”, Friend) martin = g.persons.create(**data1) cacho = g.persons.create(**data2) g.friends.create(martin, cacho) martin = g.vertices.index.lookup(id=19) martin.bothE() # tomamos las aristas desde martin martin.bothV() # tomamos los nodos desde martin

Slide 10

Slide 10 text

Caso de Estudio: Problemática “Desarrollar una aplicación de citas que permita encontrar personas potencialmente interesantes para los usuarios, utilizando los datos de facebook para buscar entre los amigos y amigos de amigos” En criollo… Una aplicación para buscar pareja escarbando amigos de facebook!

Slide 11

Slide 11 text

Caso de Estudio: Requerimientos ● La aplicacion debe ser como un “juego” ● Aprovechar los datos de facebook (friends, likes) ● Facil de mantener ● Facil de poner en producción/actualizar ● Debe andar “rápido” ● Debe escalar ● Debe ser BARATO ($$$) ● ... ● etc

Slide 12

Slide 12 text

Caso de Estudio: La Realidad ● Gran volumen de datos ● Muchos datos “basura” ● Muchos updates/inserts ● Muchas request a Facebook ● MUCHO procesamiento ● Algunos datos cambian “bastante” ● Escalar es complicado ● La API de Facebook es “rara” ● ... ● Escalar muchas veces == $$$

Slide 13

Slide 13 text

Caso de Estudio: El IDEAL Problemática de grafos sin dudas!!!

Slide 14

Slide 14 text

Caso de Estudio: Primera Solución NDB DATASTORE

Slide 15

Slide 15 text

Caso de Estudio: Primera Solución BASICAMENTE TENEMOS… class User(ndb.Model): fbid = models.StringProperty() friends = model.StringProperty(repeated=True) friends_of_friends = model.KeyProperty() member = model.BooleanProperty(default=False) #Muchos mas Se necesita mucho procesamiento y recursos para que la app funcione “correctamente”. Todos los algoritmos se hacen en memoria.

Slide 16

Slide 16 text

Caso de Estudio: Primera Solución Problemas ● MUY difícil de mantener ● MUY difícil mantener los datos sincronizados entre facebook y la app ● MUY tedioso, complicado, lento generar las “potenciales citas” ● Se torna inusable =( ● MUCHAS queries al datastore para los procesos principales ● MUCHAS request a Facebook ● MUCHO procesamiento offline (taskqueue de appengine) ● Tenemos datos viejos la mayoría del tiempo =( ● MUCHO $$$ en appengine ● IMPOSIBLE escalar

Slide 17

Slide 17 text

Caso de Estudio: Primera Solución Problemas ● MUY difícil de mantener ● MUY difícil mantener los datos sincronizados entre facebook y la app ● MUY tedioso, complicado, lento generar los “potenciales” citas” ● Se torna inusable =( ● MUCHAS queries al datastore para los procesos principales ● MUCHAS request a Facebook ● MUCHO procesamiento offline (taskqueue de appengine) ● Tenemos datos viejos la mayoría del tiempo =( ● MUCHO $$$ en appengine ● IMPOSIBLE escalar

Slide 18

Slide 18 text

Caso de Estudio: Patch NDB DATASTORE

Slide 19

Slide 19 text

Caso de Estudio: Patch NDB DATASTORE

Slide 20

Slide 20 text

Caso de Estudio: Patch Agregamos más servicios porque somos COOL! Se agregó una instancia de AWS para correr un servicio (API REST) que se encarga del trabajo “pesado” relacionado a los merge entre las listas de amigos y amigos de amigos…. También sirve como un cache de listas. Básicamente realizaba operaciones de intersecciones entre conjuntos usando REDIS y mantenía todo en memoria. Con esto buscamos aliviar el trabajo a la instancia de AppEngine y tratar de escalar el cuello de botella con la generacion de “potenciales” usuarios.

Slide 21

Slide 21 text

Caso de Estudio: Patch Conclusión El stack de tecnología creció un poco más, programar fué MUY divertido y entretenido y aprendimos mucho! El testing….horrible/imposible/impredecible/etc, posta que fue duro! Comenzamos a tener problemas nuevos pero no solucionamos los viejos referidos al “core” de la app (generar citas, buscar amigos de amigos). Realmente no escaló, comenzamos a tener más problemas de cache que otra cosa y sumado a eso aumentaron las request, antes Facebook y ahora AWS, esto es $$$.

Slide 22

Slide 22 text

Caso de Estudio: Reflexión Volvamos a empezar, vamos a la pizarra!

Slide 23

Slide 23 text

Caso de Estudio: El IDEAL Problemática de grafos sin dudas!!! Si, SIN DUDAS!

Slide 24

Slide 24 text

Solución con grafos: Research Volvimos al comienzo: “El problema requiere grafos” ● Evaluamos alternativas (tecnología, infraestructura, algoritmos, etc) ● Encontramos las DB orientadas a GRAFOS !!! ● Comenzamos a investigar, instalar, comparar, probar con distintas DB orientadas a Grafos (Neo4j, TitanDB, alguna otra). ● Usamos vagrant para hacer VM’s de test. ● Contacto con la comunidad (Gracias Javier de la Rosa!) y nos empezamos a emocionar!!!

Slide 25

Slide 25 text

Solución con grafos: Tecnología

Slide 26

Slide 26 text

Solución con grafos: Procesos Decidimos separar el problema en dos partes: Procesos críticos: La generacion de “potenciales citas” para los usuarios. Descubrir amigos de amigos (2 niveles) Descubrir amigos de amigos de amigos (3 niveles) Descubrir amigos en común etc... Procesos NO críticos: Obtener los datos de Facebook Actualizar datos Crear nuevos usuarios Mantener datos relacionados con la app. etc...

Slide 27

Slide 27 text

Solución con grafos La aplicación ahora utiliza Mongodb como storage para todo lo relacionado a la lógica de la aplicación. users, cities, potentials, user_stats, user_preferences, etc Y se utiliza Neo4j para almacenar nodos que representan a cada usuario y amigos dentro del sistema y relaciones que indican quien es amigo de quien. Utilizando Redis para encolar tareas que se ejecutan en background, como por ej. TRAVERSAL del grafo para buscar “potenciales citas” o “buscar amigos en común” para un usuario.

Slide 28

Slide 28 text

Solución con grafos: Ejemplo Ejemplo: “Registrar un usuario” Se busca el usuario en Mongodb, se obtienen datos del profile de Facebook, si existe pero no esta marcado como miembro entonces se convierte en miembro y se actualizan los datos en Neo4j y Mongodb sino se inserta en Neo4j y Mongodb un nodo, document nuevo. Se obtienen los amigos y se hace un Insert/update en batch tanto a Neo4j como a Mongodb la info que se tiene de cada amigo es reducida. Luego se hace insert/update/delete de las aristas (relaciones) necesarias en Neo4j. Se encola un job en Redis para generar “potenciales citas” para el user que se registro. El worker usa Gremlin.

Slide 29

Slide 29 text

Solución con grafos: Ejemplo Ejemplo: “Generar potenciales citas” En background por medio de REDIS se encolan trabajos. Un worker (Python) llama al script de Neo4j escrito en Gremlin (“SQL de los grafos”) y le pasa el fbid del usuario y las preferences. El script se para en el nodo del fbid y hace un TRAVERSAL en profundidad hasta 3 niveles (amigos, amigos de amigos, amigos de amigos de amigos). Por cada uno de los nodos que va visitando se fija si cumple las preferences que se le pasaron, si las cumple lo colecta sino lo ignora y pasa al próximo. El worker toma cada uno de esos nodos e inserta en Mongodb en la collection potentials. Más tarde con Python se aplica un nivel de filtro más como por ejemplo GeoLocation usando GeoSpatial index.

Slide 30

Slide 30 text

Arquitectura Final: Completa DBManager

Slide 31

Slide 31 text

Arquitectura Final: DBManager GraphManager DBManager MongoManager

Slide 32

Slide 32 text

Traversal: Gremlin Ejemplo: “Convertir ids de nodos en fbid” def convertNodeIdsToFBIDS(node_ids) { _convertEm = {List u_node_ids -> node_map = [:] for (node_id in u_node_ids) { node = g.v(node_id) if (node != null) { node_map[node_id] = node.fbid } } return node_map } return _convertEm(node_ids) }

Slide 33

Slide 33 text

Traversal: Gremlin Llamar a Gremlin desde Python g = Graph() # Cargar archivos en el grafo (fullpath) g.scripts.update('gremlin_scripts.grm') # Agarrar una función de los scripts func = g.scripts.get('convertNodeIdsToFBIDS') # Formatear el dict de params params = dict(node_ids=[1, 2, 4, 6]) # Llamar al script items = g.gremlin.query(func, params) # Nuestro shortcut GraphManager._execute_gremlin_call(**params)

Slide 34

Slide 34 text

Nuestra solución con grafos: Its ALIVE!

Slide 35

Slide 35 text

Algunos números Server Neo4j 1.9.3 en EC2 XLarge 4 cores, dedicado al grafo Miembros ~20K DB Size 13.9 M Nodos 32.4 M Aristas (un solo tipo “friends”) 62.7 M Propiedades Registrar un usuario Insertar un user y todos sus amigos Promedio de amigos por usuario: ~700 Tiempo promedio de inserts/updates: ~715.5/seg

Slide 36

Slide 36 text

Algunos números TRAVERSAL del grafo Nodos visitados Media: 14K Promedio 117.4K Max 1.3M Tiempos Media: 0.89 seg Promedio: 8.4seg Max: 60 seg (si en 60 seg no retorna se cancela) Los “TRAVERSAL” sin el grafo podían tardar HSSSS!!!

Slide 37

Slide 37 text

Conclusiones Las Graph DB’s son muy buenas para resolver problemas de grafos! Estan emergiendo y mejorando mucho y rápido. Al pasar el tiempo la cantidad de nodos que se insertan debería tender a cero. Podemos hablar de tener un grafo “estático” en algún momento. Hacer esta aplicación con Pytho, Django y Neo4j estuvo BUENISIMO! Sólo tuvimos que escribir código “raro” para el TRAVERSAL el resto TODO PYTHON!! NO LE ESCAPEN A ESTAS DB’s si las necesitan!

Slide 38

Slide 38 text

Consideraciones Las Graph DB’s son muy buenas para resolver problemas de grafos! Los inserts no son baratos siempre. Los TRAVERSAL son excelentes! Las Graph DB’s como cualquier otro motor necesitan tunning y análisis. Se necesita MUCHA RAM para que el grafo esté en memoria y las consultas sean más rápidas. Guardar en los nodos y relaciones lo mínimo e indispensable para resolver el problema. Utilizar el grafo para problemáticas de grafos, generalmente cuando necesitamos TRAVERSAL o tenemos mucha interrelación de datos. Algo mas me debo olvidar...

Slide 39

Slide 39 text

Links Python http://python.org Neo4j http://www.neo4j.org/ Gremlin https://github.com/tinkerpop/gremlin/wiki/Getting-Started Bulbflow http://bulbflow.com Mongodb http://www.mongodb.org/ MongoEngine http://mongoengine.org/ Django-rq https://github.com/ui/django-rq Redis http://redis.io/ Vagrant http://vagrantup.com Thinkerpophttp://www.tinkerpop.com/ Heroku https://www.heroku.com/ Gephi https://gephi.org D3.js http://d3js.org/

Slide 40

Slide 40 text

Muchas Gracias! ¿Preguntas? Martin Alderete [email protected] Martin Riva [email protected] while not manos.dormidas: applause()