Pregunta

Necesito insertar una gran cantidad de nodos con relaciones entre ellos en Neo4J a través del punto final por lotes de REST API, aproximadamente 5k registros/s (aún aumentando).

Esto será una inserción continua 24x7. Cada registro puede requerir crear un solo nodo, pero otros pueden requerir dos nodos y una relación que se crean.

¿Puedo mejorar el rendimiento de los insertos cambiando mi procedimiento o modificando la configuración de Neo4J?

Mi progreso hasta ahora:

1. He estado probando con Neo4J por un tiempo, pero no pude obtener el rendimiento que necesitaba

Test Server Box: 24 núcleos + 32 GB de RAM

Neo4J 2.0.0-M06 instalado como un servicio independiente.

Ejecutando mi aplicación Java en el mismo servidor (la aplicación Neo4J y Java deberá ejecutarse en su propio servidor en el futuro, por lo que no se puede usar el modo incrustado)

REST API Endpoint: /DB /Data /Batch (Target: /Cypher)

Uso del índice de esquema, restricciones, fusiones, crea único.

2. Mi esquema:

neo4j-sh (0)$ schema
==> Indexes
==>   ON :REPLY(created_at)   ONLINE                             
==>   ON :REPLY(ids)          ONLINE (for uniqueness constraint) 
==>   ON :REPOST(created_at) ONLINE                             
==>   ON :REPOST(ids)        ONLINE (for uniqueness constraint) 
==>   ON :Post(userId)      ONLINE                             
==>   ON :Post(postId)    ONLINE (for uniqueness constraint) 
==> 
==> Constraints
==>   ON (post:Post) ASSERT post.postId IS UNIQUE
==>   ON (repost:REPOST) ASSERT repost.ids IS UNIQUE
==>   ON (reply:REPLY) ASSERT reply.ids IS UNIQUE

3. Mis consultas Cypher y JSON solicitan

3.1. Cuando un registro requiere la creación de un solo nodo, la descripción del trabajo se ve a continuación

{"method" : "POST","to" : "/cypher","body" : {"query" : "MERGE (child:Post {postId:1001, userId:901})"}}

3.2. Cuando un registro requiere dos nodos con una relación para crear, la descripción del trabajo se ve a continuación

{"method" : "POST","to" : "/cypher","body" : {"query" : "MERGE (parent:Post {postId:1002, userId:902}) MERGE (child:Post {postId:1003, userId:903}) CREATE UNIQUE parent-[relationship:REPOST {ids:'1002_1003', created_at:'Wed Nov 06 14:06:56 AST 2013' }]->child"}}

3.3. Normalmente envío 100 descripciones de trabajo (mixtas 3.1 y 3.2) por lote que toma alrededor de 150 ~ 250 ms para hacerlo.

4. Problemas de rendimiento

4.1. Concurrencia:

/DB/Data/Batch (Target:/Cypher) parece no seguro, probado con dos o más subprocesos concurrentes que provocaron el servidor Neo4J en el segundo (s) minuto (s).

4.2. Fusionar con restricciones no siempre funciona.

Al crear dos nodos y una relación con una sola consulta (mencionada anteriormente en 3.2.), A veces funciona como un encanto; Pero en algún momento falla con una cypherexecutionException y diciendo que uno de los nodos XXXX ya existe con la etiqueta AAAA y la propiedad "BBBBB" = [CCCCC]; Según tengo entendido, la fusión no supone que devuelva ninguna excepción, sino que devuelva el nodo si ya existe.

Como resultado de la excepción, todo el lote fallará y la retirada, lo que afectará mi tasa de inserto.

He abierto un problema en GitHub para este problema, https://github.com/neo4j/neo4j/issues/1428

4.3. Crear único con restricciones no siempre funciona para la creación de relaciones.

Esto también se menciona en el mismo problema de GitHub.

4.4. Actuación:

En realidad, antes de usar Batch con Cypher, he probado la indexación heredada con get_or_create (/db/data/index/node/post? Uniceness = get_or_create &/db/data/index/relación/xxxxx? Uniqueness = get_or_create))

Debido a la naturaleza de esos puntos finales del índice heredado (devuelven la ubicación de los datos en el índice en lugar de la ubicación de los datos en el almacenamiento de datos real), por lo que no pude usarlos dentro del lote (necesitaba la característica del nodo de referencia creado anteriormente en el mismo lote )

Sé que podría habilitar auto_indexing y tratar con el almacenamiento de datos directamente en lugar del índice heredado, pero mencionaron a partir de 2.0.0, se recomienda el índice de esquema sobre el índice heredado, por lo que decido cambiar al enfoque de índice de esquema por lotes + Cypher +.

Sin embargo, con Batch + Cypher, solo puedo obtener alrededor de 200 descripciones de trabajo por segunda tasa de inserción, habría sido mucho mayor si la fusión con restricciones siempre funcionó, digamos alrededor de 600 ~ 800/s, pero aún es mucho más bajo que 5K /s. También probé el índice de esquema sin ninguna restricción, terminó un rendimiento aún más bajo en términos de tasa de inserción.

¿Fue útil?

Solución

Con 2.0 usaría el punto final transaccional para crear sus declaraciones en lotes, por ejemplo, 100 o 1000 por solicitud HTTP y aproximadamente 30k-50k por transacción (hasta que se comprometa).

Vea esto para el formato de la nueva transmisión, punto final transaccional:

http://docs.neo4j.org/chunked/milestone/rest-api-transactional.html

También para un punto final de inserción continua de tan alto rendimiento, recomiendo que escribir una extensión de servidor que se ejecute con la API integrada y pueda insertar fácilmente 10k o más nodos y relaciones por segundo, consulte aquí para obtener la documentación:

http://docs.neo4j.org/chunked/milestone/server-unmanaged-extensions.html

Para insertos puros no necesita Cypher. Y para concurrencia, simplemente tome un bloqueo en un nodo bien conocido (por subgrafio que está insertando) para que las inserciones concurrentes no sean ningún problema, puede hacerlo con tx.acquireWriteLock() o eliminando una propiedad inexistente de un nodo (REMOVE n.__lock__).

Para otro ejemplo de escribir una extensión no administrada (pero que usa Cypher), consulte este proyecto. Incluso tiene un modo que podría ayudarlo (publicar archivos CSV en el punto final del servidor que se ejecutará utilizando una instrucción Cypher por fila).

https://github.com/jexp/cypher-rs

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top