Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Recorriendo Nodos con Python

Recorriendo Nodos con Python

Talk about Graph databases and Python given in PyCon Argentina 2013.
This is based on a product we built for one of out customers.

Martin Alderete

October 15, 2013
Tweet

More Decks by Martin Alderete

Other Decks in Programming

Transcript

  1. 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
  2. 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)
  3. 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!
  4. 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)
  5. 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)
  6. 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
  7. 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!
  8. 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
  9. 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 == $$$
  10. 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.
  11. 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
  12. 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
  13. 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.
  14. 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 $$$.
  15. 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!!!
  16. 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...
  17. 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.
  18. 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.
  19. 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.
  20. 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) }
  21. 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)
  22. 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
  23. 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!!!
  24. 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!
  25. 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...
  26. 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/