Créer des bouleversements efficaces à l'aide de Gremlin - Amazon Neptune

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Créer des bouleversements efficaces à l'aide de Gremlin

Des upserts efficaces peuvent faire une différence significative dans les performances des requêtes Gremlin.

Un upsert (ou insert conditionnel) réutilise un sommet ou une arête s'il existe déjà, mais le crée s'il n'existe pas.

Les upserts vous permettent d'écrire des opérations d'insertion idempotentes : quel que soit le nombre de fois que vous exécutez une telle opération, le résultat global est le même. Cela est utile dans les scénarios d'écriture hautement simultanés où des modifications simultanées apportées à la même partie du graphique peuvent forcer une ou plusieurs transactions à revenir en arrière avec unConcurrentModifcationException, ce qui a nécessité une nouvelle tentative.

Par exemple, la requête suivante bouleverse un sommet en recherchant d'abord le sommet spécifié dans l'ensemble de données, puis en regroupant les résultats dans une liste. Lors de la première traversée fournie aucoalesce()étape 2, la requête dévoile ensuite cette liste. Si la liste dépliée n'est pas vide, les résultats sont émis parcoalesce(). Si, toutefois,unfold()renvoie une collection vide car le sommet n'existe pas actuellement,coalesce()passe à l'évaluation de la deuxième traversée avec laquelle elle a été fournie, et dans cette seconde traversée, la requête crée le sommet manquant.

g.V('v-1').fold() .coalesce( unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org') )

Utilisez une forme optimisée decoalesce()pour les bouleversés

Neptune peut optimiserfold().coalesce(unfold(), ...)idiome pour effectuer des mises à jour à haut débit, mais cette optimisation ne fonctionne que si les deux parties ducoalesce()renvoie soit un sommet, soit une arête mais rien d'autre. Si vous essayez de renvoyer quelque chose de différent, tel qu'une propriété, depuis n'importe quelle partie ducoalesce(), l'optimisation de Neptune ne se produit pas. La requête peut réussir, mais elle ne fonctionnera pas aussi bien qu'une version optimisée, en particulier pour les grands ensembles de données.

Comme les requêtes upsert non optimisées augmentent les temps d'exécution et réduisent le débit, il vaut la peine d'utiliser le Gremlinexplainpoint de terminaison pour déterminer si une requête upsert est entièrement optimisée. Lors de l'examenexplainplans, recherchez les lignes qui commencent par+ not converted into Neptune stepsetWARNING: >>. Par exemple :

+ not converted into Neptune steps: [FoldStep, CoalesceStep([[UnfoldStep], [AddEdgeSte... WARNING: >> FoldStep << is not supported natively yet

Ces avertissements peuvent vous aider à identifier les parties d'une requête qui empêchent son optimisation complète.

Parfois, il n'est pas possible d'optimiser complètement une requête. Dans ces situations, vous devez essayer de placer les étapes qui ne peuvent pas être optimisées à la fin de la requête, afin de permettre au moteur d'optimiser autant d'étapes que possible. Cette technique est utilisée dans certains exemples d'upserts par lots, où tous les upserts optimisés pour un ensemble de sommets ou d'arêtes sont effectués avant que des modifications supplémentaires, potentiellement non optimisées, ne soient appliquées aux mêmes sommets ou arêtes.

Traitement par lots des perturbations pour améliorer le débit

Pour les scénarios d'écriture à haut débit, vous pouvez enchaîner des étapes ascendantes pour obtenir des sommets et des arêtes par lots. Le traitement par lots réduit les frais de transaction liés à la perturbation d'un grand nombre de sommets et d'arêtes. Vous pouvez ensuite améliorer encore le débit en annulant les requêtes par lots en parallel à l'aide de plusieurs clients.

En règle générale, nous recommandons de modifier environ 200 enregistrements par demande de lot. Un enregistrement est une étiquette ou une propriété à sommet ou arête unique. Un sommet avec une seule étiquette et 4 propriétés, par exemple, crée 5 enregistrements. Une arête avec une étiquette et une seule propriété crée 2 enregistrements. Si vous souhaitez insérer des lots de sommets, chacun avec une seule étiquette et 4 propriétés, vous devez commencer par une taille de lot de 40, car200 / (1 + 4) = 40.

Vous pouvez tester la taille du lot. 200 enregistrements par lot est un bon point de départ, mais la taille de lot idéale peut être supérieure ou inférieure en fonction de votre charge de travail. Notez toutefois que Neptune peut limiter le nombre total d'étapes Gremlin par demande. Cette limite n'est pas documentée, mais par mesure de sécurité, essayez de vous assurer que vos demandes ne contiennent pas plus de 1 500 étapes Gremlin. Neptune peut rejeter les demandes par lots volumineuses comportant plus de 1 500 étapes.

Pour augmenter le débit, vous pouvez transférer des lots en parallel à l'aide de plusieurs clients. Le nombre de clients doit être identique au nombre de threads de travail sur votre instance Neptune Writer, qui est généralement deux fois plus élevé que le nombre de vCPUs sur le serveur. Par exemple, unr5.8xlargeL'instance possède 32 vCPUs et 64 threads de travail. Pour les scénarios d'écriture à haut débit utilisant unr5.8xlarge, vous utiliseriez 64 clients qui écrivent des upserts par lots sur Neptune en parallel.

Chaque client doit soumettre une demande par lots et attendre que la demande soit terminée avant de soumettre une autre demande. Bien que les multiples clients fonctionnent en parallel, chaque client soumet des demandes en série. Cela garantit que le serveur reçoit un flux constant de demandes qui occupent tous les threads de travail sans inonder la file d'attente de demandes côté serveur.

Essayez d'éviter les étapes qui génèrent plusieurs traverseurs

Lorsqu'une étape Gremlin s'exécute, elle prend un traverseur entrant et émet un ou plusieurs traverseurs de sortie. Le nombre de traverseurs émis par une étape détermine le nombre de fois que l'étape suivante est exécutée.

Généralement, lorsque vous effectuez des opérations par lots, vous souhaitez que chaque opération, telle que le sommet amont A, soit exécutée une seule fois, de sorte que la séquence des opérations ressemble à ceci : sommet amont A, puis sommet amont B, puis sommet amont C, etc. Tant qu'une étape crée ou modifie un seul élément, elle n'émet qu'un seul traverseur et les étapes qui représentent l'opération suivante ne sont exécutées qu'une seule fois. Si, par contre, une opération crée ou modifie plusieurs éléments, elle émet plusieurs traverseurs, ce qui entraîne l'exécution des étapes suivantes plusieurs fois, une fois par traverseur émis. Cela peut amener la base de données à effectuer des tâches supplémentaires inutiles et, dans certains cas, à créer des sommets, des arêtes ou des valeurs de propriété supplémentaires indésirables.

VoirMélange de renversements et d'insertspour savoir comment gérer les opérations qui peuvent émettre plusieurs traverseurs.

Renversement de sommets

Vous pouvez utiliser un identifiant de sommet pour déterminer si un sommet correspondant existe. Il s'agit de l'approche préférée, car Neptune optimise les upserts pour les cas d'utilisation très simultanés liés aux identifiants. Par exemple, la requête suivante crée un sommet avec un ID de sommet donné s'il n'existe pas déjà, ou le réutilise si c'est le cas :

g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org')) .id()

Notez que cette requête se termine parid()Étape. Bien que cela ne soit pas strictement nécessaire pour renverser le sommet, l'ajout d'unid()étape jusqu'à la fin d'une requête upsert garantit que le serveur ne sérialise pas toutes les propriétés du sommet vers le client, ce qui contribue à réduire le coût de verrouillage de la requête.

Vous pouvez également utiliser une propriété de sommet pour déterminer si le sommet existe :

g.V() .hasLabel('Person') .has('email', 'person-1@example.org') .fold() .coalesce(unfold(), addV('Person').property('email', 'person-1@example.org')) .id(

Si possible, utilisez vos propres identifiants fournis par l'utilisateur pour créer des sommets et utilisez ces identifiants pour déterminer si un sommet existe lors d'une opération upsert. Cela permet à Neptune d'optimiser les perturbations liées aux identifiants. Un upsert basé sur l'ID peut être nettement plus efficace qu'un upsert basé sur les propriétés dans des scénarios de modification hautement simultanés.

Enchaînement de sommets

Vous pouvez enchaîner des sommets pour les insérer dans un lot :

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

Bords renversés

Vous pouvez utiliser des ID d'arêtes pour inverser des arêtes de la même manière que vous inversez des sommets à l'aide d'identifiants de sommets personnalisés. Encore une fois, il s'agit de l'approche préférée car elle permet à Neptune d'optimiser la requête. Par exemple, la requête suivante crée un tronçon en fonction de son ID d'arête s'il n'existe pas déjà, ou le réutilise s'il existe. La requête utilise également les identifiants dufromettodes sommets s'il doit créer une nouvelle arête.

g.E('e-1') .fold() .coalesce(unfold(), addE('KNOWS').from(V('v-1')) .to(V('v-2')) .property(id, 'e-1')) .id()

De nombreuses applications utilisent des identifiants de sommet personnalisés, mais laissent Neptune générer des identifiants de périphérie. Si vous ne connaissez pas l'identifiant d'un avantage, mais que vous connaissez lefromettoVertex ID, vous pouvez utiliser cette formulation pour renverser une arête :

g.V('v-1') .outE('KNOWS') .where(inV().hasId('v-2')) .fold() .coalesce(unfold(), addE('KNOWS').from(V('v-1')) .to(V('v-2'))) .id()

Notez que l'étape du sommet danswhere()la clause doit êtreinV()(ououtV()si vous avez utiliséinE()pour trouver l'avantage), pasotherV(). Ne pas utiliserotherV(), ici, sinon la requête ne sera pas optimisée et les performances en souffriront. Par exemple, Neptune n'optimiserait pas la requête suivante :

// Unoptimized upsert, because of otherV() g.V('v-1') .outE('KNOWS') .where(otherV().hasId('v-2')) .fold() .coalesce(unfold(), addE('KNOWS').from(V('v-1')) .to(V('v-2'))) .id()

Si vous ne connaissez pas les identifiants des arêtes ou des sommets à l'avance, vous pouvez les modifier à l'aide des propriétés du sommet :

g.V() .hasLabel('Person') .has('name', 'person-1') .outE('LIVES_IN') .where(inV().hasLabel('City').has('name', 'city-1')) .fold() .coalesce(unfold(), addE('LIVES_IN').from(V().hasLabel('Person') .has('name', 'person-1')) .to(V().hasLabel('City') .has('name', 'city-1'))) .id()

Comme pour les upserts de sommets, il est préférable d'utiliser des upserts de bord basés sur l'ID en utilisant soit un Edge ID, soitfromettodes identifiants de sommets, plutôt que des upserts basés sur des propriétés, afin que Neptune puisse optimiser complètement l'upsert.

Vérification defromettoexistence d'un sommet

Notez la construction des étapes qui créent une nouvelle arête : addE().from().to(). Cette construction garantit que la requête vérifie l'existence à la fois defromet letosommet. Si l'un de ces éléments n'existe pas, la requête renvoie une erreur comme suit :

{ "detailedMessage": "Encountered a traverser that does not map to a value for child... "code": "IllegalArgumentException", "requestId": "..." }

S'il est possible quefromou letole sommet n'existe pas, vous devez essayer de les renverser avant de bouleverser le bord qui les sépare. Consultez Combinaison de renversements de sommets et d'arêtes.

Il existe une construction alternative pour créer une arête que vous ne devez pas utiliser : V().addE().to(). Cela ajoute un avantage uniquement si lefromun sommet existe. Si l'icônetole sommet n'existe pas, la requête génère une erreur, comme décrit précédemment, mais sifromLe sommet n'existe pas, il échoue silencieusement à insérer une arête, sans générer d'erreur. Par exemple, le renversement suivant se termine sans renverser de bord sifromle sommet n'existe pas :

// Will not insert edge if from vertex does not exist g.V('v-1') .outE('KNOWS') .where(inV().hasId('v-2')) .fold() .coalesce(unfold(), V('v-1').addE('KNOWS') .to(V('v-2'))) .id()

Renversement des arêtes d'enchaînement

Si vous souhaitez enchaîner des aspérités d'arêtes pour créer une demande par lots, vous devez commencer chaque upsert par une recherche de sommets, même si vous connaissez déjà les identifiants des arêtes.

Si vous connaissez déjà les identifiants des arêtes que vous souhaitez modifier, ainsi que les identifiants desfromettosommets, vous pouvez utiliser cette formulation :

g.V('v-1') .outE('KNOWS') .hasId('e-1') .fold() .coalesce(unfold(), V('v-1').addE('KNOWS') .to(V('v-2')) .property(id, 'e-1')) .V('v-3') .outE('KNOWS') .hasId('e-2').fold() .coalesce(unfold(), V('v-3').addE('KNOWS') .to(V('v-4')) .property(id, 'e-2')) .V('v-5') .outE('KNOWS') .hasId('e-3') .fold() .coalesce(unfold(), V('v-5').addE('KNOWS') .to(V('v-6')) .property(id, 'e-3')) .id()

Le scénario de bouleversement de bord par lots le plus courant est peut-être que vous connaissez lefromettoles identifiants des sommets, mais vous ne connaissez pas les identifiants des arêtes que vous souhaitez inverser. Dans ce cas, utilisez la formulation suivante :

g.V('v-1') .outE('KNOWS') .where(inV().hasId('v-2')) .fold() .coalesce(unfold(), V('v-1').addE('KNOWS') .to(V('v-2'))) .V('v-3') .outE('KNOWS') .where(inV().hasId('v-4')) .fold() .coalesce(unfold(), V('v-3').addE('KNOWS') .to(V('v-4'))) .V('v-5') .outE('KNOWS') .where(inV().hasId('v-6')) .fold() .coalesce(unfold(), V('v-5').addE('KNOWS').to(V('v-6'))) .id()

Si vous connaissez les identifiants des arêtes que vous souhaitez modifier, mais que vous ne connaissez pas les identifiants dufromettosommets (c'est inhabituel), vous pouvez utiliser cette formulation :

g.V() .hasLabel('Person') .has('email', 'person-1@example.org') .outE('KNOWS') .hasId('e-1') .fold() .coalesce(unfold(), V().hasLabel('Person') .has('email', 'person-1@example.org') .addE('KNOWS') .to(V().hasLabel('Person') .has('email', 'person-2@example.org')) .property(id, 'e-1')) .V() .hasLabel('Person') .has('email', 'person-3@example.org') .outE('KNOWS') .hasId('e-2') .fold() .coalesce(unfold(), V().hasLabel('Person') .has('email', 'person-3@example.org') .addE('KNOWS') .to(V().hasLabel('Person') .has('email', 'person-4@example.org')) .property(id, 'e-2')) .V() .hasLabel('Person') .has('email', 'person-5@example.org') .outE('KNOWS') .hasId('e-1') .fold() .coalesce(unfold(), V().hasLabel('Person') .has('email', 'person-5@example.org') .addE('KNOWS') .to(V().hasLabel('Person') .has('email', 'person-6@example.org')) .property(id, 'e-3')) .id()

Combinaison de renversements de sommets et d'arêtes

Parfois, vous pouvez avoir envie de renverser à la fois les sommets et les arêtes qui les relient. Vous pouvez mélanger les exemples de lots présentés ici. L'exemple suivant bouleverse 3 sommets et 2 arêtes :

g.V('p-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-1') .property('email', 'person-1@example.org')) .V('p-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-2') .property('name', 'person-2@example.org')) .V('c-1') .fold() .coalesce(unfold(), addV('City').property(id, 'c-1') .property('name', 'city-1')) .V('p-1') .outE('LIVES_IN') .where(inV().hasId('c-1')) .fold() .coalesce(unfold(), V('p-1').addE('LIVES_IN') .to(V('c-1'))) .V('p-2') .outE('LIVES_IN') .where(inV().hasId('c-1')) .fold() .coalesce(unfold(), V('p-2').addE('LIVES_IN') .to(V('c-1'))) .id()

Mélange de renversements et d'inserts

Parfois, vous pouvez avoir envie de renverser à la fois les sommets et les arêtes qui les relient. Vous pouvez mélanger les exemples de lots présentés ici. L'exemple suivant bouleverse 3 sommets et 2 arêtes :

Les bouleversements se produisent généralement un élément à la fois. Si vous vous en tenez aux modèles upsert présentés ici, chaque opération upsert émet un seul traverseur, ce qui entraîne l'exécution de l'opération suivante une seule fois.

Cependant, il se peut que vous souhaitiez parfois mélanger des renversements avec des inserts. Cela peut être le cas, par exemple, si vous utilisez des arêtes pour représenter des instances d'actions ou d'événements. Une demande peut utiliser des upserts pour s'assurer que tous les sommets nécessaires existent, puis des inserts pour ajouter des arêtes. Dans le cas de demandes de ce type, faites attention au nombre potentiel de traverseurs émis lors de chaque opération.

Prenons l'exemple suivant, qui combine des renversements et des insertions pour ajouter des arêtes qui représentent des événements dans le graphique :

// Fully optimized, but inserts too many edges g.V('p-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-1') .property('email', 'person-1@example.org')) .V('p-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-2') .property('name', 'person-2@example.org')) .V('p-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-3') .property('name', 'person-3@example.org')) .V('c-1') .fold() .coalesce(unfold(), addV('City').property(id, 'c-1') .property('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 requête doit insérer 5 arêtes : 2 arêtes SUIVIES et 3 arêtes VISITÉES. Cependant, la requête telle qu'elle est écrite insère 8 arêtes : 2 SUIVIS et 6 VISITÉS. La raison en est que l'opération qui insère les 2 arêtes SUIVIES émet 2 traverseurs, ce qui entraîne l'exécution de l'opération d'insertion suivante, qui insère 3 arêtes, deux fois.

La solution consiste à ajouter unfold()étape après chaque opération pouvant potentiellement émettre plus d'un traverseur :

g.V('p-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-1') .property('email', 'person-1@example.org')) .V('p-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-2'). .property('name', 'person-2@example.org')) .V('p-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-3'). .property('name', 'person-3@example.org')) .V('c-1') .fold(). .coalesce(unfold(), addV('City').property(id, 'c-1'). .property('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()

Ici, nous avons inséré unfold()étape après l'opération qui insère les arêtes SUIVIES. Il en résulte un seul traverseur, ce qui entraîne l'exécution de l'opération suivante une seule fois.

L'inconvénient de cette approche est que la requête n'est désormais pas entièrement optimisée, carfold()n'est pas optimisé. L'opération d'insertion qui suitfold()ne seront désormais pas optimisés.

Si vous devez utiliserfold()pour réduire le nombre de traverseurs pour les étapes suivantes, essayez d'organiser vos opérations de manière à ce que les moins coûteuses occupent la partie non optimisée de la requête.

Inversions qui modifient les sommets et les arêtes existants

Parfois, vous souhaitez créer un sommet ou une arête s'il n'existe pas, puis y ajouter ou mettre à jour une propriété, qu'il s'agisse d'un sommet ou d'une arête nouveau ou existant.

Pour ajouter ou modifier une propriété, utilisezproperty()Étape. Utilisez cette étape en dehors ducoalesce()Étape. Si vous essayez de modifier la propriété d'un sommet ou d'une arête existant à l'intérieur ducoalesce()étape, la requête peut ne pas être optimisée par le moteur de requêtes Neptune.

La requête suivante ajoute ou met à jour une propriété de compteur sur chaque sommet renversé. Chacunproperty()Une étape possède une cardinalité unique pour garantir que les nouvelles valeurs remplacent toutes les valeurs existantes, plutôt que d'être ajoutées à un ensemble de valeurs existantes.

g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org')) .property(single, 'counter', 1) .V('v-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-2') .property('email', 'person-2@example.org')) .property(single, 'counter', 2) .V('v-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-3') .property('email', 'person-3@example.org')) .property(single, 'counter', 3) .id()

Si vous avez une valeur de propriété, telle qu'unelastUpdatedvaleur d'horodatage, qui s'applique à tous les éléments bouleversés, vous pouvez l'ajouter ou la mettre à jour à la fin de la requête :

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')) .V('v-1', 'v-2', 'v-3') .property(single, 'lastUpdated', datetime('2020-02-08')) .id()

S'il existe des conditions supplémentaires qui déterminent si un sommet ou une arête doit être modifié davantage, vous pouvez utiliser unhas()une étape pour filtrer les éléments auxquels une modification sera appliquée. L'exemple suivant utilisehas()étape pour filtrer les sommets renversés en fonction de la valeur de leurversionpropriété. La requête est ensuite mise à jour à 3versionde tout sommet dontversionest inférieur à 3 :

g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org') .property('version', 3)) .V('v-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-2') .property('email', 'person-2@example.org') .property('version', 3)) .V('v-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-3') .property('email', 'person-3@example.org') .property('version', 3)) .V('v-1', 'v-2', 'v-3') .has('version', lt(3)) .property(single, 'version', 3) .id()

A l'aide desideEffect()dans un bouleversement

Si vous souhaitez supprimer des propriétés dans le cadre de l'upsert, vous devez utilisersideEffect()étape, qui n'est cependant pas actuellement optimisée par le moteur de requêtes Neptune. Cela signifie quesideEffect()étape et toutes les parties suivantes de la requête seront effectuées en utilisant TinkerPop plutôt que Neptune Steps.

La requête suivante bouleverse 3 sommets, supprimant lescorepropriété pour chaque sommet à l'aide d'unesideEffect()qui suit directement chaque sommet en amont :

// Optimizes only the first upsert g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1'). .property('email', 'person-1@example.org')) .sideEffect(properties('score').drop()) .V('v-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-2') .property('email', 'person-2@example.org')) .sideEffect(properties('score').drop()) .V('v-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-3') .property('email', 'person-3@example.org')) .sideEffect(properties('score').drop()) .id()

Parce quesideEffect()n'est pas optimisé, tout ce qui suit le premiersideEffect()utilisera des éléments non optimisés TinkerPop Étapes. En d'autres termes, seul le premier sommet en amont sera optimisé. Pour améliorer les performances de la requête, vous pouvez déplacer tous lessideEffect()comportement jusqu'au bout :

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')) .sideEffect(V('v-1', 'v-2', 'v-3').properties('score').drop()) .id()

Désormais, toutes les étapes précédentes seront optimisées. Dans lasideEffect()Cette étape n'est toujours pas optimisée, mais son impact sur les performances de la requête a été minimisé.