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. Recorriendo Nodos
    con Python
    Martin Alderete
    [email protected]
    Martin Riva
    [email protected]

    View Slide

  2. 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

    View Slide

  3. y muchas mas...
    NoSQL rapidamente

    View Slide

  4. 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)

    View Slide

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

    View Slide

  6. 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!

    View Slide

  7. 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)

    View Slide

  8. 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)

    View Slide

  9. 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

    View Slide

  10. 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!

    View Slide

  11. 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

    View Slide

  12. 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 == $$$

    View Slide

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

    View Slide

  14. Caso de Estudio: Primera Solución
    NDB DATASTORE

    View Slide

  15. 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.

    View Slide

  16. 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

    View Slide

  17. 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

    View Slide

  18. Caso de Estudio: Patch
    NDB DATASTORE

    View Slide

  19. Caso de Estudio: Patch
    NDB DATASTORE

    View Slide

  20. 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.

    View Slide

  21. 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 $$$.

    View Slide

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

    View Slide

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

    View Slide

  24. 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!!!

    View Slide

  25. Solución con grafos: Tecnología

    View Slide

  26. 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...

    View Slide

  27. 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.

    View Slide

  28. 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.

    View Slide

  29. 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.

    View Slide

  30. Arquitectura Final: Completa
    DBManager

    View Slide

  31. Arquitectura Final: DBManager
    GraphManager
    DBManager
    MongoManager

    View Slide

  32. 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)
    }

    View Slide

  33. 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)

    View Slide

  34. Nuestra solución con grafos: Its ALIVE!

    View Slide

  35. 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

    View Slide

  36. 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!!!

    View Slide

  37. 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!

    View Slide

  38. 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...

    View Slide

  39. 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/

    View Slide

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

    View Slide