style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Neo4J: Base de datos orientada a grafos

El movimiento NoSQL, que ha tomado algo de fuerza en los últimos dos años, propone utilizar bases de datos diferentes a las relacionales, ya sea bases de datos orientadas a columnas en vez de a tuplas, o a documentos, etc. Entre las implementaciones que se pueden usar hoy en día tenemos HBase, Cassandra, Hypertable, CouchDB y una interesante, que además está hecha en Java: Neo4J.

Más allá de la discusión filosófica en torno a la validez de esta propuesta de NoSQL, implementaciones comerciales, aplicaciones prácticas, etc, quiero enfocarme a hablar un poco de Neo4J y la manera en que almacena datos.

Hay ocasiones en que se requiere almacenar no solamente datos "sueltos" sino que una parte importante de dichos datos son las relaciones entre ellos. Por ejemplo en el sitio LinkedIn se pueden dar de alta y empezar a conectarse con otras personas; esas conexiones son importantes, porque no es solamente como en una red social simple tipo Hi5 donde se puede formar una conexión entre dos personas y son "amigos", sino que aquí se puede decir que A es colega de B, que a su vez es cliente de C, quien es proveedor de D, quien estudió con A. Otro ejemplo es Facebook, donde puede definirse la naturaleza de una conexión entre dos personas: A es mi amigo, B es mi esposa, fui a la escuela con C, fui novio de D, trabajé con E, conozco a F a través de A, etc.

Todo lo anterior se puede almacenar en una base de datos relacional. Lo interesante es cuando se quieren hacer búsquedas no solamente por los datos sino por las relaciones entre ellos. Las relaciones pueden ser bidireccionales, o pueden ser unidireccionales, por ejemplo en Twitter, donde puedes seguir a una persona que no te sigue a ti, y puede haber personas siguiéndote a las cuales no sigues.

Siguiendo con el ejemplo de Facebook, vemos cómo se complica la cosa cuando empezamos a ver relaciones no solamente entre las personas sino entre sus actividades. A X le gustó mi status, Z comentó sobre mi foto, a W le gusta un video que publiqué, F etiquetó a G en una foto que subió H donde yo aparezco, etc.

Si empezamos a pensar en términos relacionales, veremos que se empieza a complicar el esquema muy rápidamente. Tenemos la tabla Usuario, RelacionUsuario, Status, LikedStatus (para guardar cuando a alguien le gusta el status de alguien más), Foto, TagFoto (relación entre Usuario y Foto), LikeFoto, etc. Esto no es muy natural que digamos, cuando mentalmente podemos visualizar de manera muy sencilla a los usuarios por un lado, los status de dichos usuarios unidos a sus dueños, las fotos unidas a sus dueños, y luego lineas que unen usuarios con status (por el "like"), usuarios con fotos (por "like" o porque los etiquetaron), y líneas entre usuarios indicando si son amigos, novios, pareja, colegas, compañeros de clase, etc.

Pues bien, aquí es donde Neo4J entra en acción, porque es justamente para lo que sirve. Neo4J almacena dos cosas: Nodos y Relaciones. Un Nodo puede ser un usuario, una foto, un status; las Relaciones son tal y como ya las mencioné, cuando un usuario le gusta el status de otro usuario, o es amigo de otro usuario, o está etiquetado en una foto, o es dueño de la foto, etc.

Cómo se logra esto? Pues lo primero es tener una base de datos, a la cual le debemos indicar el directorio donde residen sus archivos (si el directorio no existe, lo crea y es una nueva base; si el directorio ya existe, lee los datos encontrados ahí):

EmbeddedGraphDatabase neodb = new EmbeddedGraphDatabase("/tmp/prueba");

/* Trabajamos con la base de datos */

//Al final siempre hay que cerrarla
neodb.shutdown();

Debemos definir los tipos de relación que queremos almacenar. En este caso vamos a crear unos enums de Java para los tipos de relación que queremos manejar:

public enum UserRels implements RelationshipType {

  ROOT, //Para pegar nodos especiales al nodo Raiz
  Amigo,
  Conyuge,
  Colega,
  Estudiante
}

public enum LikeRels implements RelationshipType {
  Owns,
  Likes,
  Tagged
}

Todo lo que se hace en Neo4J debe ser dentro de una transacción. Cada base de datos tiene un nodo raíz, que nos sirve para ir pegando de ahí los nodos que necesitemos. Vamos a crear nodos especiales pegados al nodo raíz para guardar Usuarios, Fotos y Status.

Una transacción cuando se crea, está lista para dar rollback a todo lo que ocurra dentro de ella; al final de las operaciones, debemos llamar su método success() para que se haga commit y no rollback.

Transaction tx = neodb.beginTx();

try {
  //Creamos los tres nodos
  Node usersNode = neodb.createNode();
  Node fotosNode = neodb.createNode();
  Node statusNode = neodb.createNode();

  //Les ponemos titulo
  usersNode.setProperty("title", "Usuarios");
  fotosNode.setProperty("title", "Fotos");
  statusNode.setProperty("title", "Status");

  //Los relacionamos con el nodo raiz
  neo.getReferenceNode().createRelationshipTo(usersNode, UserRels.ROOT);
  neo.getReferenceNode().createRelationshipTo(fotosNode, UserRels.ROOT);
  neo.getReferenceNode().createRelationshipTo(statusNode, UserRels.ROOT);
 
  neodb.success();
} finally {
  tx.finish();
}

Con eso tenemos la estructura básica de nuestra red social "chafanet". Ahora solamente tenemos que ir creando usuarios, y relacionándolos entre ellos. Por simplicidad, omito el código de manejo de la transacción de Neo4J pero recuerden que todo debe correr dentro de una transacción:

//Creamos usuarios
Node u1 = neodb.createNode();
Node u2 = neodb.createNode();
Node u3 = neodb.createNode();

//Pongamosles nombres
u1.setProperty("nombre", "Usuario Uno");
u2.setProperty("nombre", "Usuario Dos");
u3.setProperty("nombre", "Usuario Tres");

//Pongamos lazos del nodo de Usuarios hacia ellos
usersNode.createRelationshipTo(u1, UserRels.ROOT);
usersNode.createRelationshipTo(u2, UserRels.ROOT);
usersNode.createRelationshipTo(u2, UserRels.ROOT);

//Y ahora lazos entre ellos
u1.createRelationshipTo(u2, UserRels.Amigo);
u2.createRelationshipTo(u3, UserRels.Conyuge);
u2.createRelationshipTo(u1, UserRels.Colega);

Ya definimos tres usuarios; el primero es amigo del segundo, el segundo está casado con el tercero y además trabaja con el primero. Entre el primero y el tercero no definimos ninguna relación.

Podemos ahora ponerle Status a uno de ellos, y que otro diga que le gusta:

//Crear el status y ponerle datos
Node st1 = neodb.createNode();

st1.setProperty("status", "Este es el status del usuario 1");
st1.setProperty("fecha", System.currentTimeMillis());
statusNode.createRelationshipto(st1, UserRels.ROOT);

//Marcar al usuario como propietario de este status
u1.createRelationshipTo(st1, LikeRels.Owns);
//Otro usuario marca ese status como que le gusta
u2.createRelationshipTo(st1, LikeRels.Likes);

Con fotos es algo similar; creamos una foto, le ponemos el dueño, agregamos relaciones con los usuarios que aparecen etiquetados en dichas fotos, o que les gusta la foto, etc.

Búsquedas?
Hasta ahora todo parece muy sencillo, pero falta una parte importantísima: cómo buscar en los datos? En la segunda parte de este artículo entraré en detalles de las búsquedas. Por ahora llego hasta aquí, solamente la parte de estar almacenando datos con las relaciones que hay entre ellos.

Ya está disponible la segunda parte de la introducción.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

Ejemplo gráfico

@nawroth (quien por cierto está involucrado directamente en el proyecto de Neo4J) hizo favor de ilustrar el ejemplo que puse, ecutando el código del ejemplo y visualizándolo con Neoclipse, el plugin para ver bases de Neo4J en Eclipse: http://twitpic.com/1alezf

Imagen de luxspes

Neo4J vs Rel

Interesante... aunque me cuesta un poco de trabajo ver los beneficios del modelo de Neo4J sobre un modelo como el de Rel. (creo que ya va siendo hora que publique la segunda parte de ese blog post). Por otro lado me gusta el ejemplo que planteas basado en Facebook... tal vez lo use para la parte 2 de mi blog post (asi los que lean ambos artículos podrán comparar de un modo mas directo ambos enfoques (relacional y grafos) )

Imagen de ezamudio

Buena idea

Yo ya casi tengo lista la segunda parte de este artículo, donde ya presento algo de búsquedas.

La diferencia es que Rel se supone que es una base de datos verdaderamente relacional, mientras que Neo4J es una base de datos orientada a grafos. Hay aplicaciones muy específicas, por ejemplo mapas y cosas así donde puede servir, o cuando tienes datos que van interconectados de varias maneras. No sé cómo almacena FB sus datos (sé que usan Cassandra para algunas cosas) pero se me ocurrió el ejemplo porque es algo que todo mundo conoce.

Imagen de luxspes

Hay Restricciones/Constraints en Neo4J?

Marcar al usuario como propietario de este status

u1.createRelationshipTo(st1, LikeRels.Owns);

En Rel, es posible expresar que no puede haber mas de un Autor (Owner) para un Status usando restricciones. Ejemplo:

CONSTRAINT AUTOR_STATUSES_USUARIOS  IS_EMPTY(SUMMARIZE STATUSES_USUARIOS where Rol = 'Autor'  BY {IdStatus,Rol} ADD (COUNT() as Autores)  where Autores > 1);

De esa forma podemos evitar cometer errores mas adelante (si por alguna razon la regamos e intentamos guardar 2 autores (owners) para un Status, Rel nos lo advertirá. ¿Tiene NeoJ un mecanismo de restricciones/constraints equivalente?

Imagen de ezamudio

No

Estás comparando peras y manzanas. Si te digo que Rel es lo mejor que existe en el planeta, infinitamente mejor que Neo4J quedes satisfecho? A mi no me importa; Neo4J es una herramienta más, para resolver ciertos tipos de problemas. En la introducción, como es una introducción, para mucha gente que nunca ha trabajado con otra cosa que no sea una RDBMS, utilicé un ejemplo muy sencillo, y que tal vez de hecho para el cual Neo4J no sea la mejor solución. Pero es más sencillo que ponerme a hacer un ejemplo con puntos en un mapa para encontrar la ruta más corta o para determinar la centralidad de un nodo dentro de una red, etc; un problema que Neo4J pueda resolver de manera sencilla pero que con una RDBMS tradicional sea muy complicado (o con Rel, o bases de datos orientadas a columnas, etc).

Tal vez todo lo que se pueda hacer con Neo4J se pueda hacer con Rel y más fácil, con menos código, mejor desempeño, más vitaminas y menos colesterol. Habría que hacer tal vez una comparación punto por punto, pero no veo de qué pueda servir, dado que estaríamos comparando una base de datos relacional con una base de datos orientada a gráficos (ya un amigo matemático me corrigió y me explicó de la larga lucha que han tenido contra la palabra "grafos" que es un pochismo, y que debe ser "gráficos" y "vértices", no "grafos" y "nodos", pero eso es otra historia).

Imagen de luxspes

Comparar ayuda a entender las diferencias: Ayudame a comparar

Primero que nada, estoy comparando a NeoJ con Rel, por que es me ayuda a entender las diferencias (no se si Rel sea "mejor" o "peor" que NeoJ, solo se que son diferentes, y compararlos me ayuda a entender de que manera son diferentes)

Estás comparando peras y manzanas. Si te digo que Rel es lo mejor que existe en el planeta, infinitamente mejor que Neo4J quedes satisfecho?

Claro que no quedaría satisfecho (seria aburridisimo, y ninguno de los que leyeran eso aprendería nada), yo no dije en ningún lado que creyera que Rel fuera infinitamente mejor que Neo4J... (apenas conozco superficialmente a ambos, es muy pronto para hacer una afirmación asi... y es posible que no pueda hacerlo jamas) :-P

A mi no me importa; Neo4J es una herramienta más, para resolver ciertos tipos de problemas. En la introducción, como es una introducción, para mucha gente que nunca ha trabajado con otra cosa que no sea una RDBMS, utilicé un ejemplo muy sencillo, y que tal vez de hecho para el cual Neo4J no sea la mejor solución.

A mi si me importa, quiero entender cuando es bueno aplicar NeoJ y cuando no. Se que es una introducción, se que el ejemplo es sencillo, pero no sabia que NeoJ no era la mejor solución hasta que no contestaste a mi comparación... igual y me presentabas un modo de definir restricciones con NeoJ!

Pero es más sencillo que ponerme a hacer un ejemplo con puntos en un mapa para encontrar la ruta más corta o para determinar la centralidad de un nodo dentro de una red, etc; un problema que Neo4J pueda resolver de manera sencilla pero que con una RDBMS tradicional sea muy complicado

Tal ves seria muy complicado, tal vez no, ojala tengas oportunidad de plantear un ejemplo así mas adelante, para que así pueda intentar hacerlo en Rel y ver que tanto ayuda (o no) NeoJ a atacar ese tipo de problemas. Quiza en una "Parte 3"?

Tal vez todo lo que se pueda hacer con Neo4J se pueda hacer con Rel y más fácil, con menos código, mejor desempeño, más vitaminas y menos colesterol.

Jajaja, ojala así fuera! Pero ya sabemos que nunca es tan simple... No hay balas de plata

Habría que hacer tal vez una comparación punto por punto, pero no veo de qué pueda servir, dado que estaríamos comparando una base de datos relacional con una base de datos orientada a gráficos

Bueno, para empezar, al menos yo, jamas he trabajado con una base de datos verdaderamente relacional como Rel (apenas estoy aprendiendo) y definitivamente nunca había trabajado con NeoJ (aunque había oído que existían bases de datos orientadas a grafos, nunca había visto ejemplos concretos de código manipulando una). Yo pienso que una comparación punto por punto si podría ser muy valiosa: Como saber si uno de los 2 enfoques es mejor para un determinado tipo de problema si no hacemos la prueba con ambos?

Imagen de ezamudio

En parte 2

Me equivoqué y la respuesta a este comentario la puse en la parte 2 en vez de aquí.

Imagen de luxspes

"Pensar en terminos de SQL"!="pensar en terminos relacionales"

Pienso que es importante divulgar a Rel (aunque estoy de acuerdo que en terminos de soporte e infraestructura otros productos como Neo4J son mejores), por que creo que es importante evitar confusiones como esta:

Si empezamos a pensar en términos relacionales, veremos que se empieza a complicar el esquema muy rápidamente. Tenemos la tabla Usuario, RelacionUsuario, Status, LikedStatus (para guardar cuando a alguien le gusta el status de alguien más), Foto, TagFoto (relación entre Usuario y Foto), LikeFoto, etc. Esto no es muy natural que digamos, cuando mentalmente podemos visualizar de manera muy sencilla a los usuarios por un lado, los status de dichos usuarios unidos a sus dueños, las fotos unidas a sus dueños, y luego lineas que unen usuarios con status (por el "like"), usuarios con fotos (por "like" o porque los etiquetaron), y líneas entre usuarios indicando si son amigos, novios, pareja, colegas, compañeros de clase, etc.

Básicamente aquí estas diciendo que "Si empezamos a pensar en términos relacionales" necesitamos tablas "RelacionUsuario, Status, LikedStatus, Foto, TagFoto, LikeFoto", y eso no es correcto, al menos "no si pensamos en términos relacionales", es correcto si pensamos "en términos de SQL", pero "SQL!=Relacional", de hecho "Relacional>=SQL" (SQL solo cubre un parte de todo el enfoque relacional). Hasta donde he llegado con mi serie de posts, el modelo esta tomando mas bien una forma tipo: "Usuarios, Relaciones_Usuarios, Statuses, Status_Usuarios" y si agregamos las fotos: "Fotos_Usuario" y "Fotos". No necesito poner relvars para "Tag Foto/Status" y otra para "Like Foto/Status", por que con Rel puedo definir restricciones que es muy difícil (o quizá casi imposible, como veremos próximamente en la parte IV) en SQL. Sin embargo, hacemos (yo también lo hago) afirmaciones como "pensar en términos relacionales" cuando en realidad estamos pensando en términos de lo que SQL puede hacer para determinada DBMS, creando así que la impresión de que las limitaciones que encontramos son intrínsecas del modelo relacional y no del producto que estamos utilizando.

Por eso creo que es importante discutir y comparar, para que quien pase por aquí se lleve un conocimiento un poco mas preciso de lo que es (y no es) el modelo relacional (o el modelo de grafos: hay que tener cuidado de no decir "en una base de datos de grafos no se tienen lenguajes de consultas" y decir mejor "en Neo4J no se tiene un lenguaje de consultas", igual con las restricciones: No hay en Neo4J por que el modelo de grafos no lo soporta? o es mas bien que Neo4J esta limitado en ese sentido?).

Imagen de luxspes

Respuesta en Parte 2

Me equivoqué y la respuesta a este comentario la puse en la parte 2 en vez de aquí.

Ya te conteste allá.

Imagen de ezamudio

OK, RDBMS

OK debí haber dicho entonces "si empezamos a pensar en términos de una RDBMS tradicional"

Imagen de luxspes

RDBMS: La R es por Relacional? Recuerda R>SQL ;-)

Seria mas preciso: "si empezamos a pensar en términos de una DBMS tradicional basada en SQL" (el si tienen o no el derecho de llamarse RDBMS es precisamente la cuestión que se plantea al compararlas con Rel o con el el Business System 12... ¿si se merecen esa R? o deberían ser mas bien: PseudoR-DBMS o simplemente "DBMS basadas en SQL").

H

H

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">