Réaliser des upserts efficaces avec GarmlinmergeV()etmergeE()pas - 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.

Réaliser des upserts efficaces avec GarmlinmergeV()etmergeE()pas

Un upsert (ou insertion conditionnelle) réutilise un sommet ou une arête s'il existe déjà, ou le crée s'il n'existe pas. Des upserts efficaces peuvent faire une différence significative dans les performances des requêtes Gremlin.

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 très simultanés où les modifications simultanées apportées à la même partie du graphique peuvent obliger une ou plusieurs transactions à revenir en arrière avec unConcurrentModificationException, ce qui nécessite de nouvelles tentatives.

Par exemple, la requête suivante insère un sommet à l'aide desMappour essayer d'abord de trouver un sommet avec unT.idde"v-1". Si ce sommet est trouvé, il est renvoyé. S'il n'est pas trouvé, alors un sommet avec çaidet les propriétés sont créées par le biais duonCreateclause.

g.mergeV([(id):'v-1'). option(onCreate, [(label): 'PERSON', 'email': 'person-1@example.org'])

Mise en lots des insertions pour améliorer le débit

Pour les scénarios d'écriture à haut débit, vous pouvez enchaînermergeV()etmergeE()s'agrège pour placer les sommets et les arêtes par lots. Le traitement par lots réduit la charge transactionnelle liée à l'insertion d'un grand nombre de sommets et d'arêtes. Vous pouvez ensuite améliorer encore le débit en augmentant les requêtes par lots en parallèle à l'aide de plusieurs clients.

En règle générale, nous recommandons d'insérer environ 200 enregistrements par demande de lot. Un enregistrement est une étiquette ou une propriété d'activité. Un sommet avec une seule étiquette et 4 propriétés, par exemple, crée 5 enregistrements. Une arête dotée d'une étiquette et d'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 des lots. 200 enregistrements par lot constituent 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 requête. 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 des demandes par lots volumineux comportant plus de 1 500 étapes.

Pour augmenter le débit, vous pouvez insérer des lots en parallèle en utilisant plusieurs clients (voirCréation d'écritures Gremlin multithreads efficaces). Le nombre de clients doit être identique au nombre de threads de travail sur votre instance Neptune Writer, qui est généralement 2 fois le nombre de vCPU sur le serveur. Par exemple, unr5.8xlargel'instance possède 32 vCPU et 64 threads de travail. Pour les scénarios d'écriture à haut débit utilisant unr5.8xlarge, vous utiliseriez 64 clients écrivant des upserts par lots sur Neptune en parallèle.

Chaque client doit soumettre une demande groupée et attendre que la demande soit terminée avant de soumettre une autre demande. Bien que les multiples clients fonctionnent en parallèle, 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 encombrer la file d'attente des demandes côté serveur (voirDimensionnement des instances de base de données dans un cluster de base de données Neptune).

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

Lorsqu'une étape Gremlin s'exécute, elle prend un traverseur entrant et émet un ou plusieurs traverseurs de sortie. Le nombre de traversers é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 supérieur A, soit exécutée une fois, de sorte que la séquence des opérations ressemble à ceci : sommet A, sommet B, sommet supérieur C, etc. Tant qu'une étape crée ou modifie un seul élément, elle n'émet qu'un seul traverseur, et les étapes représentant l'opération suivante ne sont exécutées qu'une seule fois. Si, en revanche, une opération crée ou modifie plusieurs éléments, elle émet plusieurs traversants, ce qui entraîne l'exécution des étapes suivantes plusieurs fois, une fois par traverseur émis. Cela peut obliger 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és supplémentaires indésirables.

Un exemple de la façon dont les choses peuvent mal tourner est une requête telle queg.V().addV(). Cette requête simple ajoute un sommet pour chaque sommet du graphe, carV()émet un traverseur pour chaque sommet du graphe et chacun de ces traverseurs déclenche un appel àaddV().

VoirMélanger des inserts et des insertspour savoir comment gérer les opérations qui peuvent émettre plusieurs traversers.

Insertion de sommets

LemergeV()L'étape est spécialement conçue pour la remontée de sommets. Il prend comme argument unMapqui représente les éléments à faire correspondre aux sommets existants dans le graphe, et si aucun élément n'est trouvé, l'utiliseMappour créer un nouveau sommet Cette étape vous permet également de modifier le comportement en cas de création ou de correspondance, lorsqueoption()le modulateur peut être appliqué avecMerge.onCreateetMerge.onMatchdes jetons pour contrôler ces comportements respectifs. Consultez le TinkerPop Documentation de référencepour obtenir de plus amples informations sur l'utilisation de cette étape.

Vous pouvez utiliser un identifiant de sommet pour déterminer si un sommet spécifique existe. Il s'agit de l'approche préférée, car Neptune optimise les upserts pour les cas d'utilisation hautement 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 s'il existe :

g.mergeV([(T.id): 'v-1']). option(onCreate, [(T.label): 'PERSON', email: 'person-1@example.org', age: 21]). option(onMatch, [age: 22]). id()

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

Vous pouvez également utiliser une propriété de vertex pour identifier un sommet :

g.mergeV([email: 'person-1@example.org']). option(onCreate, [(T.label): 'PERSON', age: 21]). option(onMatch, [age: 22]). id()

Si possible, utilisez vos propres identifiants fournis par l'utilisateur pour créer des sommets, et utilisez-les pour déterminer si un sommet existe lors d'une opération de remontée. Cela permet à Neptune d'optimiser les upserts. Une modification basée sur un identifiant peut être nettement plus efficace qu'une modification basée sur des propriétés lorsque des modifications simultanées sont courantes.

Enchaînement de sommets

Vous pouvez enchaîner les 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()

Alternativement, vous pouvez également utiliser cecimergeV()syntaxe :

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'])

Toutefois, étant donné que cette forme de requête inclut des éléments dans les critères de recherche qui sont superflus à la recherche de base parid, elle n'est pas aussi efficace que la requête précédente.

Bords ascendants

LemergeE()le marchepied est spécialement conçu pour les arêtes ascendantes. Il fautMapen tant qu'argument qui représente les éléments qui correspondent aux arêtes existantes du graphe et si aucun élément n'est trouvé, l'utiliseMappour créer une nouvelle arête. Cette étape vous permet également de modifier le comportement en cas de création ou de correspondance, lorsqueoption()le modulateur peut être appliqué avecMerge.onCreateetMerge.onMatchdes jetons pour contrôler ces comportements respectifs. Consultez le TinkerPop Documentation de référencepour obtenir de plus amples informations sur l'utilisation de cette étape.

Vous pouvez utiliser des identifiants d'arêtes pour surdimensionner des arêtes de la même manière que vous insérez des sommets à l'aide d'identifiants de sommet 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 une arête en fonction de son identifiant d'arête si elle n'existe pas déjà, ou la réutilise si c'est le cas. La requête utilise également les identifiants duDirection.frometDirection.tosommets s'il doit créer une nouvelle arête :

g.mergeE([(T.id): 'e-1']). option(onCreate, [(from): 'v-1', (to): 'v-2', weight: 1.0]). option(onMatch, [weight: 0.5]). id()

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

De nombreuses applications utilisent des identifiants de sommet personnalisés, mais laissent à Neptune le soin de générer des identifiants de bord. Si vous ne connaissez pas l'identifiant d'une arête, mais que vous connaissezfromettoID de sommet, vous pouvez utiliser ce type de requête pour modifier une arête :

g.mergeE([(from): 'v-1', (to): 'v-2', (T.label): 'KNOWS']). id()

Tous les sommets référencés parmergeE()doit exister pour que l'étape crée l'arête.

Supports à arêtes en chaîne

Comme pour les vertex upserts, il est simple de les enchaînermergeE()étapes à suivre pour les demandes par lots :

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

Combinaison de sommets et d'arêtes

Parfois, vous souhaiterez peut-être insérer à 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 insère 3 sommets et 2 arêtes :

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

Mélanger des inserts et des inserts

Parfois, vous souhaiterez peut-être insérer à 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 insère 3 sommets et 2 arêtes :

Les upserts procèdent généralement d'un élément à la fois. Si vous vous en tenez aux modèles de remontée présentés ici, chaque opération de remontée émet un seul traverseur, ce qui fait que l'opération suivante n'est exécutée qu'une seule fois.

Cependant, vous voudrez peut-être parfois mélanger des inserts 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 inserts 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 traversers émis par chaque opération.

Prenons l'exemple suivant, qui mélange des insertions et des insertions pour ajouter des arêtes représentant des événements dans le graphique :

// 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 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 FOLLOWED et 6 VISITED. Cela est dû au fait que l'opération qui insère les 2 arêtes suivies émet 2 traverseurs, ce qui entraîne l'exécution de deux fois de l'opération d'insertion suivante, qui insère 3 arêtes.

La solution consiste à ajouter unfold()étape après chaque opération susceptible d'émettre plusieurs traverseurs :

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

Ici, nous avons inséré unfold()étape après l'opération d'insertion des arêtes SUIVIES. Il en résulte un seul traverseur, ce qui fait que l'opération suivante n'est exécutée qu'une seule fois.

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

Si vous devez utiliserfold()pour réduire le nombre de traverseurs lors des é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.