Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.
Realización de actualizaciones o inserciones eficientes con los pasos mergeV()
y mergeE()
de Gremlin
Una actualización o inserción (o inserción condicional) reutiliza un vértice o un borde si ya existe, o lo crea si no existe. Las actualizaciones o inserciones eficientes pueden marcar una diferencia significativa en el rendimiento de las consultas de Gremlin.
Las actualizaciones o inserciones permiten escribir operaciones de inserción de idempotencia; independientemente del número de veces que se ejecute una operación de este tipo, el resultado general es el mismo. Esto resulta útil en situaciones de escritura muy simultáneas, en las que las modificaciones simultáneas en la misma parte del gráfico pueden obligar a una o más transacciones a revertirse con una ConcurrentModificationException
, por lo que es necesario volver a intentarlas.
Por ejemplo, la siguiente consulta desplaza realiza actualizaciones o inserciones en un vértice utilizando el Map
proporcionado para intentar buscar primero un vértice con un T.id
de "v-1"
. Si se encuentra ese vértice, se devuelve. Si no se encuentra, se crea un vértice con ese id
y una propiedad a través de la cláusula onCreate
.
g.mergeV([(id):'v-1']). option(onCreate, [(label): 'PERSON', 'email': 'person-1@example.org'])
Realización de actualizaciones o inserciones por lotes para mejorar el rendimiento
En escenarios de escritura de alto rendimiento, puede encadenar los pasos mergeV()
y mergeE()
para realizar actualizaciones o inserciones en vértices y bordes por lotes. El procesamiento por lotes reduce la sobrecarga transaccional que supone realizar actualizaciones o inserciones en un gran número de vértices y bordes. A continuación, puede mejorar aún más el rendimiento realizando actualizaciones o inserciones en solicitudes por lotes en paralelo con varios clientes.
Como regla general, recomendamos modificar aproximadamente 200 registros por solicitud en lote. Un registro es una propiedad o etiqueta de un solo vértice o borde. Un vértice con una sola etiqueta y 4 propiedades, por ejemplo, crea 5 registros. Un borde con una etiqueta y una sola propiedad crea 2 registros. Si desea realizar actualizaciones o inserciones en lotes de vértices, cada uno con una sola etiqueta y 4 propiedades, debería empezar con un tamaño de lote de 40, ya que 200 / (1 + 4)
= 40
.
Puede experimentar con el tamaño del lote. Un buen punto de partida son 200 registros por lote, pero el tamaño de lote ideal puede ser mayor o menor en función de la carga de trabajo. Sin embargo, tenga en cuenta que Neptune puede limitar el número total de pasos de Gremlin por solicitud. Este límite no está documentado, pero por si acaso, intente asegurarse de que sus solicitudes no contengan más de 1500 pasos de Gremlin. Neptune puede rechazar solicitudes por lotes grandes con más de 1500 pasos.
Para aumentar el rendimiento, puede realizar actualizaciones o inserciones en los lotes en paralelo utilizando varios clientes (consulte Creación de escrituras de Gremlin eficientes de múltiples subprocesos). El número de clientes debe ser el mismo que el número de subprocesos de trabajo de la instancia de Neptune Writer, que normalmente es 2 veces el número de subprocesos de trabajo del vCPUs servidor. Por ejemplo, una r5.8xlarge
instancia tiene 32 vCPUs y 64 subprocesos de trabajo. En escenarios de escritura de alto rendimiento en los que se usa un r5.8xlarge
, utilizaría 64 clientes que escriben actualizaciones o inserciones por lotes en Neptune en paralelo.
Cada cliente debe enviar una solicitud por lotes y esperar a que se complete antes de enviar otra. Aunque los múltiples clientes se ejecutan en paralelo, cada cliente individual envía las solicitudes en serie. Esto garantiza que el servidor reciba un flujo constante de solicitudes que ocupen todos los subprocesos de trabajadores sin saturar la cola de solicitudes del lado del servidor (consulte Dimensionamiento de las instancias de base de datos en un clúster de base de datos de Neptune).
Intente evitar pasos que generen múltiples recorridos
Cuando se ejecuta un paso de Gremlin, toma un recorrido entrante y emite uno o más recorridos de salida. El número de entrante que emite un paso determina el número de veces que se ejecuta el siguiente paso.
Por lo general, cuando se realizan operaciones por lotes, se desea que cada operación, como el vértice de actualización o inserción A, se ejecute una vez, de modo que la secuencia de operaciones tenga el siguiente aspecto: vértice de actualización o inserción A, vértice de actualización o inserción B, vértice de actualización o inserción C, etc. Mientras que un paso cree o modifique solo un elemento, solo emite un recorrido y los pasos que representan la siguiente operación se ejecutan solo una vez. Si, por otro lado, una operación crea o modifica más de un elemento, emite varios recorridos, lo que a su vez provoca que los pasos siguientes se ejecuten varias veces, una vez por recorrido emitido. Esto puede provocar que la base de datos realice un trabajo adicional innecesario y, en algunos casos, que se creen vértices, bordes o valores de propiedad adicionales no deseados.
Un ejemplo de cómo esto puede ir mal es con una consulta como g.V().addV()
. Esta sencilla consulta añade un vértice por cada vértice que se encuentre en el gráfico, ya que V()
emite un recorrido para cada vértice del gráfico y cada uno de esos recorridos activa una llamada a addV()
.
Consulte en Combinación de actualizaciones o inserciones e inserciones las formas de gestionar las operaciones que pueden emitir varios recorridos.
Actualizaciones o inserciones en vértices
El paso mergeV()
está diseñado específicamente para realizar opciones actualizaciones o inserciones en vértices. Toma como argumento un Map
que representa los elementos que deben coincidir con los vértices existentes en el gráfico y, si no se encuentra un elemento, usa ese Map
para crear un nuevo vértice. El paso también le permite alterar el comportamiento en caso de una creación o una coincidencia, donde el modulador option()
se puede aplicar con los tokens Merge.onCreate
y Merge.onMatch
para controlar esos comportamientos respectivos. Consulte la documentación de TinkerPop referencia
Puede usar un identificador de vértice para determinar si existe un vértice específico. Este es el enfoque preferido, ya que Neptune optimiza los upserts para casos de uso altamente concurrentes. IDs Por ejemplo, la siguiente consulta crea un vértice con un identificador de vértice determinado si aún no existe, o lo reutiliza si ya existe:
g.mergeV([(T.id): 'v-1']). option(onCreate, [(T.label): 'PERSON', email: 'person-1@example.org', age: 21]). option(onMatch, [age: 22]). id()
Tenga en cuenta que esta consulta termina con un paso id()
. Si bien no es estrictamente necesario para realizar una actualización o inserción en el vértice, un paso id()
hasta el final de una consulta de actualización o inserción garantiza que el servidor no vuelva a serializar todas las propiedades del vértice para el cliente, lo que ayuda a reducir el costo de bloqueo de la consulta.
También puede utilizar una propiedad de vértice para identificar un vértice:
g.mergeV([email: 'person-1@example.org']). option(onCreate, [(T.label): 'PERSON', age: 21]). option(onMatch, [age: 22]). id()
Si es posible, utilice los vértices proporcionados por el usuario IDs para crear vértices y utilícelos para determinar si existe un vértice durante una IDs operación de upsert. Esto permite a Neptune optimizar las actualizaciones o inserciones. Una actualización o inserción basada en identificador puede ser considerablemente más eficiente que una actualización o inserción basada en propiedades cuando las modificaciones simultáneas son frecuentes.
Encadenamiento de actualizaciones o inserciones de vértices
Puede encadenar actualizaciones o inserciones de vértices para insertarlos en un lote:
g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org')) .V('v-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-2') .property('email', 'person-2@example.org')) .V('v-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-3') .property('email', 'person-3@example.org')) .id()
También puede utilizar esta sintaxis de mergeV()
:
g.mergeV([(T.id): 'v-1', (T.label): 'PERSON', email: 'person-1@example.org']). mergeV([(T.id): 'v-2', (T.label): 'PERSON', email: 'person-2@example.org']). mergeV([(T.id): 'v-3', (T.label): 'PERSON', email: 'person-3@example.org'])
Sin embargo, dado que esta forma de consulta incluye elementos en los criterios de búsqueda que son superfluos para la búsqueda básica de id
, no es tan eficaz como la consulta anterior.
Actualizaciones o inserciones de bordes
El paso mergeE()
está diseñado específicamente para realizar actualizaciones o inserciones de bordes. Toma como argumento un Map
que representa los elementos que deben coincidir con los bordes existentes en el gráfico y, si no se encuentra un elemento, usa ese Map
para crear un nuevo borde. El paso también le permite alterar el comportamiento en caso de una creación o una coincidencia, donde el modulador option()
se puede aplicar con los tokens Merge.onCreate
y Merge.onMatch
para controlar esos comportamientos respectivos. Consulte la documentación de TinkerPop referencia
Puede utilizar la arista IDs para realzar las aristas de la misma manera que se moldean los vértices con un vértice personalizado. IDs De nuevo, este es el enfoque preferido porque permite a Neptune optimizar la consulta. Por ejemplo, la siguiente consulta crea un borde en función de su identificador de borde si aún no existe, o lo reutiliza si ya existe. La consulta también utiliza los IDs Direction.to
vértices Direction.from
y si necesita crear una arista nueva:
g.mergeE([(T.id): 'e-1']). option(onCreate, [(from): 'v-1', (to): 'v-2', weight: 1.0]). option(onMatch, [weight: 0.5]). id()
Tenga en cuenta que esta consulta termina con un paso id()
. Si bien no es estrictamente necesario para realizar una actualización o inserción del borde, un paso id()
hasta el final de una consulta de actualización o inserción garantiza que el servidor no vuelva a serializar todas las propiedades del borde para el cliente, lo que ayuda a reducir el costo de bloqueo de la consulta.
Muchas aplicaciones utilizan un vértice personalizadoIDs, pero dejan que Neptune genere el borde. IDs Si no conoces el identificador de una arista, pero sí el to
vértice from
yIDs, puedes usar este tipo de consulta para cambiar una arista:
g.mergeE([(from): 'v-1', (to): 'v-2', (T.label): 'KNOWS']). id()
Todos los vértices a los que hace referencia mergeE()
deben existir para que el paso cree el borde.
Encadenamiento de actualizaciones o inserciones de bordes
Al igual que con las actualizaciones o inserciones de vértices, es sencillo encadenar los pasos mergeE()
para las solicitudes por lotes:
g.mergeE([(from): 'v-1', (to): 'v-2', (T.label): 'KNOWS']). mergeE([(from): 'v-2', (to): 'v-3', (T.label): 'KNOWS']). mergeE([(from): 'v-3', (to): 'v-4', (T.label): 'KNOWS']). id()
Combinación de actualizaciones o inserciones de vértices y bordes
A veces, es posible que desee realizar actualizaciones o inserciones en ambos vértices y los bordes que los conectan. Puede combinar los ejemplos de lotes que se presentan aquí. En el siguiente ejemplo, se realizan actualizaciones o inserciones de 3 vértices y 2 bordes:
g.mergeV([(id):'v-1']). option(onCreate, [(label): 'PERSON', 'email': 'person-1@example.org']). mergeV([(id):'v-2']). option(onCreate, [(label): 'PERSON', 'email': 'person-2@example.org']). mergeV([(id):'v-3']). option(onCreate, [(label): 'PERSON', 'email': 'person-3@example.org']). mergeE([(from): 'v-1', (to): 'v-2', (T.label): 'KNOWS']). mergeE([(from): 'v-2', (to): 'v-3', (T.label): 'KNOWS']). id()
Combinación de actualizaciones o inserciones e inserciones
A veces, es posible que desee realizar actualizaciones o inserciones en ambos vértices y los bordes que los conectan. Puede combinar los ejemplos de lotes que se presentan aquí. En el siguiente ejemplo, se realizan actualizaciones o inserciones de 3 vértices y 2 bordes:
Por lo general, las actualizaciones o inserciones se realizan con un elemento a la vez. Si sigue los patrones de actualización o inserción que se presentan aquí, cada operación de actualización o inserción emite un único recorrido, lo que hace que la siguiente operación se ejecute solo una vez.
Sin embargo, a veces es posible que desee mezclar actualizaciones o inserciones con inserciones. Podría ser así, por ejemplo, si utiliza bordes para representar instancias de acciones o eventos. Una solicitud puede usar actualizaciones o inserciones para garantizar que existan todos los vértices necesarios y, a continuación, usar inserciones para añadir bordes. En el caso de solicitudes de este tipo, preste atención a la cantidad potencial de recorridos que emite cada operación.
Examine el siguiente ejemplo, en el que se combinan actualizaciones o inserciones e inserciones para añadir bordes que representen eventos en el gráfico:
// Fully optimized, but inserts too many edges g.mergeV([(id):'v-1']). option(onCreate, [(label): 'PERSON', 'email': 'person-1@example.org']). mergeV([(id):'v-2']). option(onCreate, [(label): 'PERSON', 'email': 'person-2@example.org']). mergeV([(id):'v-3']). option(onCreate, [(label): 'PERSON', 'email': 'person-3@example.org']). mergeV([(T.id): 'c-1', (T.label): 'CITY', name: 'city-1']). V('p-1', 'p-2'). addE('FOLLOWED').to(V('p-1')). V('p-1', 'p-2', 'p-3'). addE('VISITED').to(V('c-1')). id()
La consulta debe insertar 5 aristas: 2 aristas y 3 FOLLOWED aristas. VISITED Sin embargo, la consulta tal como está escrita inserta 8 bordes: 2 FOLLOWED y 6VISITED. Esto se debe a que la operación que inserta las dos FOLLOWED aristas emite dos travesaños, lo que provoca que la siguiente operación de inserción, que inserta tres aristas, se ejecute dos veces.
La solución consiste en añadir un paso fold()
después de cada operación que pueda emitir más de un recorrido:
g.mergeV([(T.id): 'v-1', (T.label): 'PERSON', email: 'person-1@example.org']). mergeV([(T.id): 'v-2', (T.label): 'PERSON', email: 'person-2@example.org']). mergeV([(T.id): 'v-3', (T.label): 'PERSON', email: 'person-3@example.org']). mergeV([(T.id): 'c-1', (T.label): 'CITY', name: 'city-1']). V('p-1', 'p-2'). addE('FOLLOWED'). to(V('p-1')). fold(). V('p-1', 'p-2', 'p-3'). addE('VISITED'). to(V('c-1')). id()
Aquí hemos insertado un fold()
paso después de la operación que inserta las aristas. FOLLOWED Esto da como resultado un único recorrido, lo que hace que la siguiente operación se ejecute solo una vez.
La desventaja de este enfoque es que la consulta ahora no se optimiza por completo, porque fold()
no se optimiza. La siguiente operación de inserción que sigue fold()
ahora tampoco se optimizará.
Si necesita usar fold()
para reducir el número de recorridos para los pasos posteriores, intente ordenar las operaciones de manera que las menos costosas ocupen la parte no optimizada de la consulta.
Establecer la cardinalidad
La cardinalidad predeterminada para las propiedades de los vértices en Neptune está establecida, lo que significa que al usar mergeV (), a todos los valores proporcionados en el mapa se les asignará esa cardinalidad. Para usar una cardinalidad única, debe ser explícito en su uso. A partir de la TinkerPop versión 3.7.0, hay una nueva sintaxis que permite proporcionar la cardinalidad como parte del mapa, como se muestra en el siguiente ejemplo:
g.mergeV([(T.id): '1234']). option(onMatch, ['age': single(20), 'name': single('alice'), 'city': set('miami')])
Como alternativa, puede establecer la cardinalidad como valor predeterminado de la siguiente maneraoption
:
// age and name are set to single cardinality by default g.mergeV([(T.id): '1234']). option(onMatch, ['age': 22, 'name': 'alice', 'city': set('boston')], single)
En las versiones mergeV()
anteriores a la 3.7.0, había menos opciones para establecer la cardinalidad. El enfoque general consiste en volver al property()
paso siguiente:
g.mergeV([(T.id): '1234']). option(onMatch, sideEffect(property(single,'age', 20). property(set,'city','miami')).constant([:]))
nota
Este enfoque solo funcionará mergeV()
cuando se utilice con un paso inicial. Por lo tanto, no podría encadenarse mergeV()
dentro de un único recorrido, ya que el primero mergeV()
después del paso de inicio que utilice esta sintaxis producirá un error si el travesaño entrante es un elemento gráfico. En este caso, querrás dividir las mergeV()
llamadas en varias solicitudes, cada una de las cuales puede ser un paso inicial.