

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.

# Bonnes pratiques Neptune avec openCypher et Bolt
<a name="best-practices-opencypher"></a>

Suivez ces bonnes pratiques lorsque vous utilisez le langage de requête openCypher et le protocole Bolt avec Neptune. Pour plus d'informations sur l'utilisation d'openCypher dans Neptune, consultez [Accès au graphe Neptune avec openCypher](access-graph-opencypher.md).

**Topics**
+ [

## Création d'une connexion après un basculement
](#best-practices-opencypher-renew-connection)
+ [

## Gestion des connexions pour les applications de longue durée
](#best-practices-opencypher-long-connections)
+ [

## Gestion des connexions pour AWS Lambda
](#best-practices-opencypher-lambda-connections)
+ [

# Préférer les arêtes dirigées aux arêtes bidirectionnelles dans les requêtes
](best-practices-opencypher-directed-edges.md)
+ [

# Neptune ne prend pas en charge plusieurs requêtes simultanées dans une transaction
](best-practices-opencypher-multiple-queries.md)
+ [

# Fermer les objets Driver lorsque vous avez terminé
](best-practices-opencypher-close-driver.md)
+ [

# Utilisation des modes de transaction explicites pour la lecture et l'écriture
](best-practices-opencypher-use-explicit-txs.md)
+ [

# Logique des nouvelles tentatives pour les exceptions
](best-practices-opencypher-retry-logic.md)
+ [

# Définissez plusieurs propriétés à la fois à l'aide d'une seule clause SET
](best-practices-content-0.md)
+ [

# Utilisation des requêtes paramétrées
](best-practices-content-2.md)
+ [

# Utilisez des cartes aplaties au lieu de cartes imbriquées dans la clause UNWIND
](best-practices-content-3.md)
+ [

# Placez des nœuds plus restrictifs sur le côté gauche dans les expressions VLP (Variable-Length Path)
](best-practices-content-4.md)
+ [

# Évitez les vérifications redondantes des étiquettes des nœuds en utilisant des noms de relations granulaires
](best-practices-content-5.md)
+ [

# Spécifiez les étiquettes de bord dans la mesure du possible
](best-practices-content-6.md)
+ [

# Évitez d'utiliser la clause WITH dans la mesure du possible
](best-practices-content-7.md)
+ [

# Placez les filtres restrictifs le plus tôt possible dans la requête
](best-practices-content-8.md)
+ [

# Vérifiez explicitement si les propriétés existent
](best-practices-content-9.md)
+ [

# N'utilisez pas de chemin nommé (sauf si cela est obligatoire)
](best-practices-content-10.md)
+ [

# Évitez COLLECT (DISTINCT ())
](best-practices-content-11.md)
+ [

# Préférez la fonction de propriétés à la recherche de propriétés individuelle lors de la récupération de toutes les valeurs de propriété
](best-practices-content-12.md)
+ [

# Effectuer des calculs statiques en dehors de la requête
](best-practices-content-13.md)
+ [

# Entrées par lots utilisant UNWIND au lieu d'instructions individuelles
](best-practices-content-14.md)
+ [

# Préférez utiliser la personnalisation IDs pour le nœud ou la relation
](best-practices-content-15.md)
+ [

# Évitez de faire des calculs \$1id dans la requête
](best-practices-content-16.md)
+ [

# Mise à jour/fusion de plusieurs nœuds
](best-practices-merge-multiple-nodes.md)

## Création d'une connexion après un basculement
<a name="best-practices-opencypher-renew-connection"></a>

En cas de basculement, le pilote Bolt peut continuer à se connecter à l'ancienne instance d'enregistreur plutôt qu'à la nouvelle instance active, car le nom DNS a été résolu en une adresse IP spécifique.

Pour éviter cela, fermez puis reconnectez l'objet `Driver` après un basculement.

## Gestion des connexions pour les applications de longue durée
<a name="best-practices-opencypher-long-connections"></a>

Lorsque vous créez des applications à longue durée de vie, telles que celles qui s'exécutent dans des conteneurs ou sur des instances Amazon EC2, instanciez un objet `Driver` une fois, puis réutilisez-le pendant toute la durée de vie de l'application. L'objet `Driver` est thread-safe, et la surcharge entraînée par l'initialisation est considérable.

## Gestion des connexions pour AWS Lambda
<a name="best-practices-opencypher-lambda-connections"></a>

Les pilotes Bolt ne sont pas recommandés pour une utilisation dans AWS Lambda les fonctions, en raison de leur surcharge de connexion et des exigences de gestion. Utilisez plutôt le [point de terminaison HTTPS](access-graph-opencypher-queries.md).

# Préférer les arêtes dirigées aux arêtes bidirectionnelles dans les requêtes
<a name="best-practices-opencypher-directed-edges"></a>

Lorsque Neptune optimise les requêtes, les arêtes bidirectionnelles compliquent la création de plans de requêtes optimaux. Les plans sous-optimaux obligent le moteur à effectuer des tâches superflues, ce qui entraîne une baisse des performances.

Par conséquent, utilisez des arêtes dirigées plutôt que des arêtes bidirectionnelles dans la mesure du possible. Par exemple, utilisez :

```
MATCH p=(:airport {code: 'ANC'})-[:route]->(d) RETURN p)
```

au lieu de :

```
MATCH p=(:airport {code: 'ANC'})-[:route]-(d) RETURN p)
```

La plupart des modèles de données n'ont pas besoin de traverser les arêtes dans les deux sens. Les requêtes peuvent donc améliorer considérablement les performances avec l'utilisation d'arêtes dirigées.

Si le modèle de données nécessite de traverser des arêtes bidirectionnelles, faites du premier nœud (côté gauche) le nœud avec le filtrage le plus restrictif dans le modèle `MATCH`.

Prenons un exemple recherchant tous les itinéraires (`routes`) à destination et en provenance de l'aéroport `ANC`. Écrivez cette requête pour commencer à l'aéroport `ANC`, comme suit :

```
MATCH p=(src:airport {code: 'ANC'})-[:route]-(d) RETURN p
```

Le moteur peut ainsi effectuer un minimum d'efforts pour satisfaire la requête, car le nœud le plus restreint est placé en tant que premier nœud (côté gauche) dans le modèle. Puis, il peut optimiser la requête.

Cette approche est de loin préférable à un filtrage de l'aéroport `ANC` à la fin du modèle, comme ceci :

```
MATCH p=(d)-[:route]-(src:airport {code: 'ANC'}) RETURN p
```

Lorsque le nœud le plus restreint n'est pas placé en premier dans le modèle, le moteur doit effectuer des efforts supplémentaires, car il ne peut pas optimiser la requête et doit réaliser des recherches supplémentaires pour obtenir les résultats.

# Neptune ne prend pas en charge plusieurs requêtes simultanées dans une transaction
<a name="best-practices-opencypher-multiple-queries"></a>

Bien que le pilote Bolt lui-même autorise les requêtes simultanées dans une transaction, Neptune ne prend pas en charge plusieurs requêtes dans le cadre d'une transaction exécutée simultanément. Neptune exige plutôt que plusieurs requêtes d'une transaction soient exécutées de manière séquentielle et que les résultats de chaque requête soient entièrement utilisés avant le lancement de la prochaine requête.

L'exemple ci-dessous montre comment utiliser Bolt pour exécuter plusieurs requêtes de manière séquentielle dans une transaction, de sorte que les résultats de chacune soient entièrement consommés avant le début de la suivante :

```
final String query = "MATCH (n) RETURN n";

try (Driver driver = getDriver(HOST_BOLT, getDefaultConfig())) {
  try (Session session = driver.session(readSessionConfig)) {
    try (Transaction trx = session.beginTransaction()) {
      final Result res_1 = trx.run(query);
      Assert.assertEquals(10000, res_1.list().size());
      final Result res_2 = trx.run(query);
      Assert.assertEquals(10000, res_2.list().size());
    }
  }
}
```

# Fermer les objets Driver lorsque vous avez terminé
<a name="best-practices-opencypher-close-driver"></a>

Il est important de fermer le client lorsque vous n'en avez plus besoin afin de garantir que les connexions Bolt sont fermées par le serveur et que toutes les ressources associées aux connexions sont libérées. Cela se produit automatiquement si vous fermez le pilote à l'aide de `driver.close()`.

Si le pilote n'est pas correctement fermé, Neptune met fin à toutes les connexions Bolt inactives au bout de 20 minutes, ou au bout de 10 jours si vous utilisez l'authentification IAM.

Neptune prend en charge jusqu'à 1 000 connexions Bolt simultanées. Si vous ne fermez pas explicitement les connexions lorsque vous n'en avez plus besoin, et que le nombre de connexions en direct atteint cette limite de 1 000, toute nouvelle tentative de connexion échouera.

# Utilisation des modes de transaction explicites pour la lecture et l'écriture
<a name="best-practices-opencypher-use-explicit-txs"></a>

Lorsque vous utilisez des transactions avec Neptune et le pilote Bolt, il est préférable de définir explicitement le mode d'accès des transactions en lecture et en écriture avec les paramètres appropriés.

## Transactions en lecture seule
<a name="best-practices-opencypher-read-txs"></a>

Pour les transactions en lecture seule, si vous ne transmettez pas la configuration de mode d'accès appropriée lors de la création de la session, le niveau d'isolement par défaut est utilisé, à savoir l'isolement des requêtes de mutation. Par conséquent, il est important de définir le mode d'accès `read` de manière explicite pour les transactions en lecture seule.

**Exemple de transaction de lecture avec validation automatique :**

```
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.READ)
  .build();
Session session = driver.session(sessionConfig);
try {
  (Add your application code here)
} catch (final Exception e) {
  throw e;
} finally {
  driver.close()
}
```

**Exemple de transaction de lecture :**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withDefaultAccessMode(AccessMode.READ)
  .build();
driver.session(sessionConfig).readTransaction(
  new TransactionWork<List<String>>() {
    @Override
    public List<String> execute(org.neo4j.driver.Transaction tx) {
      (Add your application code here)
    }
  }
);
```

Dans les deux cas, l'[isolement `SNAPSHOT`](transactions-isolation-levels.md) est réalisé à l'aide de la sémantique des [transactions en lecture seule de Neptune](transactions-neptune.md#transactions-neptune-read-only).

Comme les réplicas en lecture n'acceptent que les requêtes en lecture seule, toute requête soumise à un réplica en lecture s'exécute selon une sémantique d'isolement `SNAPSHOT`.

Il n'y a pas de lectures corrompues ni de lectures non reproductibles pour les transactions en lecture seule.

## Transactions de mutation
<a name="best-practices-opencypher-mutation-txs"></a>

Pour les requêtes de mutation, il existe trois mécanismes différents pour créer une transaction d'écriture, chacun étant illustré ci-dessous :

**Exemple de transaction d'écriture implicite :**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withDefaultAccessMode(AccessMode.WRITE)
  .build();
driver.session(sessionConfig).writeTransaction(
  new TransactionWork<List<String>>() {
    @Override
    public List<String> execute(org.neo4j.driver.Transaction tx) {
      (Add your application code here)
    }
  }
);
```

**Exemple de transaction d'écriture à validation automatique :**

```
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.Write)
  .build();
Session session = driver.session(sessionConfig);
try {
  (Add your application code here)
} catch (final Exception e) {
    throw e;
} finally {
    driver.close()
}
```

**Exemple de transaction d'écriture explicite :**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.WRITE)
  .build();
Transaction beginWriteTransaction = driver.session(sessionConfig).beginTransaction();
  (Add your application code here)
beginWriteTransaction.commit();
driver.close();
```

**Niveaux d'isolement pour les transactions d'écriture**
+ Les lectures effectuées dans le cadre de requêtes de mutation sont exécutées dans le cadre de l'isolement des transactions `READ COMMITTED`.
+ Il n'existe aucune lecture corrompue pour les lectures effectuées dans le cadre de requêtes de mutation.
+ Les enregistrements et les plages d'enregistrements sont verrouillés lors de la lecture d'une requête de mutation.
+ Lorsqu'une plage de l'index a été lue par une transaction de mutation, vous bénéficiez de la garantie que cette plage ne sera pas modifiée par des transactions simultanées jusqu'à la fin de la lecture.

Les requêtes de mutation ne sont pas thread-safe.

Pour les conflits, voir [Résolution des conflits à l'aide de délais d'attente de verrouillage](transactions-neptune.md#transactions-neptune-conflicts).

Les requêtes de mutation ne font pas automatiquement l'objet de nouvelles tentatives en cas d'échec.

# Logique des nouvelles tentatives pour les exceptions
<a name="best-practices-opencypher-retry-logic"></a>

Pour toutes les exceptions qui autorisent une nouvelle tentative, il est généralement préférable d'utiliser une [stratégie de backoff exponentiel et de nouvelle tentative](https://docs.aws.amazon.com/general/latest/gr/api-retries.html) qui allonge progressivement les temps d'attente entre les tentatives afin de mieux gérer les problèmes transitoires tels que les erreurs `ConcurrentModificationException`. Voici un exemple de modèle de backoff exponentiel et de nouvelle tentative :

```
public static void main() {
  try (Driver driver = getDriver(HOST_BOLT, getDefaultConfig())) {
    retriableOperation(driver, "CREATE (n {prop:'1'})")
        .withRetries(5)
        .withExponentialBackoff(true)
        .maxWaitTimeInMilliSec(500)
        .call();
  }
}

protected RetryableWrapper retriableOperation(final Driver driver, final String query){
  return new RetryableWrapper<Void>() {
    @Override
    public Void submit() {
      log.info("Performing graph Operation in a retry manner......");
      try (Session session = driver.session(writeSessionConfig)) {
        try (Transaction trx =  session.beginTransaction()) {
            trx.run(query).consume();
            trx.commit();
        }
      }
      return null;
    }

    @Override
    public boolean isRetryable(Exception e) {
      if (isCME(e)) {
        log.debug("Retrying on exception.... {}", e);
        return true;
      }
      return false;
    }

    private boolean isCME(Exception ex) {
      return ex.getMessage().contains("Operation failed due to conflicting concurrent operations");
    }
  };
}



/**
 * Wrapper which can retry on certain condition. Client can retry operation using this class.
 */
@Log4j2
@Getter
public abstract class RetryableWrapper<T> {

  private long retries = 5;
  private long maxWaitTimeInSec = 1;
  private boolean exponentialBackoff = true;

  /**
   * Override the method with custom implementation, which will be called in retryable block.
   */
  public abstract T submit() throws Exception;

  /**
   * Override with custom logic, on which exception to retry with.
   */
  public abstract boolean isRetryable(final Exception e);

  /**
   * Define the number of retries.
   *
   * @param retries -no of retries.
   */
  public RetryableWrapper<T> withRetries(final long retries) {
    this.retries = retries;
    return this;
  }

  /**
   * Max wait time before making the next call.
   *
   * @param time - max polling interval.
   */
  public RetryableWrapper<T> maxWaitTimeInMilliSec(final long time) {
    this.maxWaitTimeInSec = time;
    return this;
  }

  /**
   * ExponentialBackoff coefficient.
   */
  public RetryableWrapper<T> withExponentialBackoff(final boolean expo) {
    this.exponentialBackoff = expo;
    return this;
  }

  /**
   * Call client method which is wrapped in submit method.
   */
  public T call() throws Exception {
    int count = 0;
    Exception exceptionForMitigationPurpose = null;
    do {
      final long waitTime = exponentialBackoff ? Math.min(getWaitTimeExp(retries), maxWaitTimeInSec) : 0;
      try {
          return submit();
      } catch (Exception e) {
        exceptionForMitigationPurpose = e;
        if (isRetryable(e) && count < retries) {
          Thread.sleep(waitTime);
          log.debug("Retrying on exception attempt - {} on exception cause - {}", count, e.getMessage());
        } else if (!isRetryable(e)) {
          log.error(e.getMessage());
          throw new RuntimeException(e);
        }
      }
    } while (++count < retries);

    throw new IOException(String.format(
          "Retry was unsuccessful.... attempts %d. Hence throwing exception " + "back to the caller...", count),
          exceptionForMitigationPurpose);
  }

  /*
   * Returns the next wait interval, in milliseconds, using an exponential backoff
   * algorithm.
   */
  private long getWaitTimeExp(final long retryCount) {
    if (0 == retryCount) {
      return 0;
    }
    return ((long) Math.pow(2, retryCount) * 100L);
  }
}
```

# Définissez plusieurs propriétés à la fois à l'aide d'une seule clause SET
<a name="best-practices-content-0"></a>

 Au lieu d'utiliser plusieurs clauses SET pour définir des propriétés individuelles, utilisez une carte pour définir simultanément plusieurs propriétés pour une entité. 

 Vous pouvez utiliser : 

```
MATCH (n:SomeLabel {`~id`: 'id1'})
SET n += {property1 : 'value1',
property2 : 'value2',
property3 : 'value3'}
```

 Au lieu de : 

```
MATCH (n:SomeLabel {`~id`: 'id1'})
SET n.property1 = 'value1'
SET n.property2 = 'value2'
SET n.property3 = 'value3'
```

 La clause SET accepte soit une propriété unique, soit une carte. Si vous mettez à jour plusieurs propriétés sur une seule entité, l'utilisation d'une seule clause SET avec une carte permet d'effectuer les mises à jour en une seule opération au lieu de plusieurs opérations, qui peuvent être exécutées plus efficacement. 

## Utilisez la clause SET pour supprimer plusieurs propriétés à la fois
<a name="best-practices-content-1"></a>

 Lorsque vous utilisez le langage OpenCypher, REMOVE est utilisé pour supprimer les propriétés d'une entité. Dans Neptune, chaque propriété supprimée nécessite une opération distincte, ce qui ajoute de la latence aux requêtes. Vous pouvez plutôt utiliser SET avec une carte pour définir les valeurs de toutes les propriétés`null`, ce qui, dans Neptune, revient à supprimer des propriétés. Neptune bénéficiera de performances accrues lorsque plusieurs propriétés d'une même entité doivent être supprimées. 

Utilisez :

```
WITH {prop1: null, prop2: null, prop3: null} as propertiesToRemove 
MATCH (n) 
SET n += propertiesToRemove
```

Au lieu de :

```
MATCH (n) 
REMOVE n.prop1, n.prop2, n.prop3
```

# Utilisation des requêtes paramétrées
<a name="best-practices-content-2"></a>

 Il est recommandé de toujours utiliser des requêtes paramétrées lorsque vous utilisez OpenCypher. Le moteur de requêtes peut exploiter des requêtes paramétrées répétées pour des fonctionnalités telles que le cache de plans de requêtes, où l'invocation répétée de la même structure paramétrée avec des paramètres différents peut tirer parti des plans mis en cache. Le plan de requête généré pour les requêtes paramétrées est mis en cache et réutilisé uniquement lorsqu'il est terminé dans les 100 ms et que les types de paramètres sont NUMBER, BOOLEAN ou STRING. 

Utilisez :

```
MATCH (n:foo) WHERE id(n) = $id RETURN n
```

Avec paramètres :

```
parameters={"id": "first"}
parameters={"id": "second"}
parameters={"id": "third"}
```

Au lieu de :

```
MATCH (n:foo) WHERE id(n) = "first" RETURN n
MATCH (n:foo) WHERE id(n) = "second" RETURN n
MATCH (n:foo) WHERE id(n) = "third" RETURN n
```

# Utilisez des cartes aplaties au lieu de cartes imbriquées dans la clause UNWIND
<a name="best-practices-content-3"></a>

 Une structure profondément imbriquée peut limiter la capacité du moteur de requêtes à générer un plan de requête optimal. Pour pallier partiellement ce problème, les modèles définis ci-dessous créeront des plans optimaux pour les scénarios suivants : 
+  Scénario 1 : DÉTENDEZ-VOUS avec une liste de littéraux chiffrés, qui inclut NUMBER, STRING et BOOLEAN. 
+  Scénario 2 : DÉTENDEZ-VOUS avec une liste de cartes aplaties, qui inclut uniquement des littéraux chiffrés (NUMBER, STRING, BOOLEAN) comme valeurs. 

 Lorsque vous rédigez une requête contenant la clause UNWIND, suivez les recommandations ci-dessus pour améliorer les performances. 

Exemple de scénario 1 :

```
UNWIND $ids as x
MATCH(t:ticket {`~id`: x})
```

Avec paramètres :

```
parameters={
  "ids": [1, 2, 3]
}
```

 Un exemple pour le scénario 2 consiste à générer une liste de nœuds à CRÉER ou à FUSIONNER. Au lieu d'émettre plusieurs instructions, utilisez le modèle suivant pour définir les propriétés sous la forme d'un ensemble de cartes aplaties : 

```
UNWIND $props as p
CREATE(t:ticket {title: p.title, severity:p.severity})
```

Avec paramètres :

```
parameters={
  "props": [
    {"title": "food poisoning", "severity": "2"},
    {"title": "Simone is in office", "severity": "3"}
  ]
}
```

Au lieu d'objets de nœuds imbriqués tels que :

```
UNWIND $nodes as n
CREATE(t:ticket n.properties)
```

Avec paramètres :

```
parameters={
  "nodes": [
    {"id": "ticket1", "properties": {"title": "food poisoning", "severity": "2"}},
    {"id": "ticket2", "properties": {"title": "Simone is in office", "severity": "3"}}
  ]
}
```

# Placez des nœuds plus restrictifs sur le côté gauche dans les expressions VLP (Variable-Length Path)
<a name="best-practices-content-4"></a>

 Dans les requêtes VLP (Variable-Length Path), le moteur de requêtes optimise l'évaluation en choisissant de démarrer la traversée à gauche ou à droite de l'expression. La décision est basée sur la cardinalité des motifs à gauche et à droite. La cardinalité est le nombre de nœuds correspondant au modèle spécifié. 
+  Si le motif droit a une cardinalité de un, le côté droit sera le point de départ. 
+  Si le côté gauche et le côté droit ont une cardinalité égale à un, l'expansion est vérifiée des deux côtés et commence du côté dont l'expansion est la plus faible. L'extension est le nombre d'arêtes sortantes ou entrantes pour le nœud de gauche et le nœud de droite de l'expression VLP. Cette partie de l'optimisation n'est utilisée que si la relation VLP est unidirectionnelle et si le type de relation est fourni. 
+  Sinon, le côté gauche sera le point de départ. 

 Pour une chaîne d'expressions VLP, cette optimisation ne peut être appliquée qu'à la première expression. Les autres VLPs sont évalués en commençant par le côté gauche. Par exemple, supposons que la cardinalité de (a), (b) soit un et que la cardinalité de (c) soit supérieure à un. 
+  `(a)-[*1..]->(c)`: L'évaluation commence par (a). 
+  `(c)-[*1..]->(a)`: L'évaluation commence par (a). 
+  `(a)-[*1..]-(c)`: L'évaluation commence par (a). 
+  `(c)-[*1..]-(a)`: L'évaluation commence par (a). 

 Supposons maintenant que les arêtes entrantes de (a) soient deux, les arêtes sortantes de (a) trois, les arêtes entrantes de (b) quatre et les arêtes sortantes de (b) cinq. 
+  `(a)-[*1..]->(b)`: L'évaluation commence par (a) car les arêtes sortantes de (a) sont inférieures aux arêtes entrantes de (b). 
+  `(a)<-[*1..]-(b)`: L'évaluation commence par (a) car les arêtes entrantes de (a) sont inférieures aux arêtes sortantes de (b). 

 En règle générale, placez le modèle le plus restrictif sur le côté gauche d'une expression VLP. 

# Évitez les vérifications redondantes des étiquettes des nœuds en utilisant des noms de relations granulaires
<a name="best-practices-content-5"></a>

 Lors de l'optimisation des performances, l'utilisation d'étiquettes de relation exclusives aux modèles de nœuds permet de supprimer le filtrage des étiquettes sur les nœuds. Prenons l'exemple d'un modèle de graphe dans lequel la relation `likes` est uniquement utilisée pour définir une relation entre deux `person` nœuds. Nous pourrions écrire la requête suivante pour trouver ce modèle : 

```
MATCH (n:person)-[:likes]->(m:person)
RETURN n, m
```

 La vérification des `person` étiquettes sur n et m est redondante, car nous avons défini la relation de manière à ce qu'elle n'apparaisse que lorsque les deux sont du même type`person`. Pour optimiser les performances, nous pouvons écrire la requête comme suit : 

```
MATCH (n)-[:likes]->(m)
RETURN n, m
```

 Ce modèle peut également s'appliquer lorsque les propriétés sont exclusives à une étiquette de nœud unique. Supposons que seuls `person` les nœuds possèdent cette propriété`email`. Il est donc redondant de vérifier que l'étiquette du nœud `person` correspond. Écrire cette requête sous la forme : 

```
MATCH (n:person)
WHERE n.email = 'xxx@gmail.com'
RETURN n
```

 C'est moins efficace que d'écrire cette requête sous la forme : 

```
MATCH (n)
WHERE n.email = 'xxx@gmail.com'
RETURN n
```

 Vous ne devez adopter ce modèle que lorsque les performances sont importantes et que votre processus de modélisation est contrôlé pour vous assurer que ces étiquettes de bord ne sont pas réutilisées pour des modèles impliquant d'autres étiquettes de nœuds. Si vous introduisez ultérieurement une `email` propriété sur une autre étiquette de nœud`company`, par exemple, les résultats seront différents entre ces deux versions de la requête. 

# Spécifiez les étiquettes de bord dans la mesure du possible
<a name="best-practices-content-6"></a>

 Il est recommandé de fournir une étiquette de bord lorsque cela est possible lorsque vous spécifiez une arête dans un motif. Prenons l'exemple de requête suivant, qui est utilisé pour relier toutes les personnes vivant dans une ville à toutes les personnes qui ont visité cette ville. 

```
MATCH (person)-->(city {country: "US"})-->(anotherPerson)
RETURN person, anotherPerson
```

 Si votre modèle de graphe relie des personnes à des nœuds autres que des villes à l'aide de plusieurs étiquettes de bord, Neptune devra évaluer d'autres chemins qui seront ensuite supprimés en omettant de spécifier l'étiquette finale. Dans la requête ci-dessus, aucune étiquette de bord n'ayant été donnée, le moteur effectue d'abord un travail supplémentaire, puis filtre les valeurs pour obtenir le résultat correct. Une meilleure version de la requête ci-dessus pourrait être : 

```
MATCH (person)-[:livesIn]->(city {country: "US"})-[:visitedBy]->(anotherPerson)
RETURN person, anotherPerson
```

 Cela facilite non seulement l'évaluation, mais permet également au planificateur de requêtes de créer de meilleurs plans. Vous pouvez même associer cette bonne pratique à des vérifications redondantes de l'étiquette des nœuds pour supprimer la vérification de l'étiquette de la ville et écrire la requête sous la forme suivante : 

```
MATCH (person)-[:livesIn]->({country: "US"})-[:visitedBy]->(anotherPerson)
RETURN person, anotherPerson
```

# Évitez d'utiliser la clause WITH dans la mesure du possible
<a name="best-practices-content-7"></a>

 La clause WITH d'OpenCypher agit comme une limite où tout ce qui se trouve avant son exécution, puis les valeurs qui en résultent, sont transmis aux parties restantes de la requête. La clause WITH est nécessaire lorsque vous avez besoin d'une agrégation temporaire ou que vous souhaitez limiter le nombre de résultats, mais en dehors de cela, vous devez essayer d'éviter d'utiliser la clause WITH. Le conseil général est de supprimer ces simples clauses WITH (sans agrégation, ordre par ou limite) pour permettre au planificateur de requêtes de travailler sur l'ensemble de la requête afin de créer un plan global optimal. Par exemple, supposons que vous ayez écrit une requête pour renvoyer toutes les personnes vivant dans `India` : 

```
MATCH (person)-[:lives_in]->(city)
WITH person, city
MATCH (city)-[:part_of]->(country {name: 'India'})
RETURN collect(person) AS result
```

 Dans la version ci-dessus, la clause WITH restreint le placement du motif `(city)-[:part_of]->(country {name: 'India'})` (ce qui est plus restrictif) avant`(person)-[:lives_in]->(city)`. Cela rend le plan sous-optimal. Une optimisation de cette requête serait de supprimer la clause WITH et de laisser le planificateur calculer le meilleur plan. 

```
MATCH (person)-[:lives_in]->(city)
MATCH (city)-[:part_of]->(country {name: 'India'})
RETURN collect(person) AS result
```

# Placez les filtres restrictifs le plus tôt possible dans la requête
<a name="best-practices-content-8"></a>

 Dans tous les scénarios, le placement précoce de filtres dans la requête permet de réduire les solutions intermédiaires qu'un plan de requête doit prendre en compte. Cela signifie que moins de mémoire et de ressources de calcul sont nécessaires pour exécuter la requête. 

 L'exemple suivant vous aide à comprendre ces impacts. Supposons que vous écriviez une requête pour renvoyer toutes les personnes qui y vivent`India`. L'une des versions de la requête peut être : 

```
MATCH (n)-[:lives_in]->(city)-[:part_of]->(country)
WITH country, collect(n.firstName + " "  + n.lastName) AS result
WHERE country.name = 'India'
RETURN result
```

 La version ci-dessus de la requête n'est pas le moyen le plus optimal pour réaliser ce cas d'utilisation. Le filtre `country.name = 'India'` apparaît ultérieurement dans le modèle de requête. Il collectera d'abord toutes les personnes et leur lieu de résidence, puis les regroupera par pays, puis filtrera uniquement pour le groupe pour`country.name = India`. La méthode optimale pour interroger uniquement les personnes vivant sur place, `India` puis effectuer l'agrégation des collectes. 

```
MATCH (n)-[:lives_in]->(city)-[:part_of]->(country)
WHERE country.name = 'India'
RETURN collect(n.firstName + " "  + n.lastName) AS result
```

 Une règle générale consiste à placer un filtre dès que possible après l'introduction de la variable. 

# Vérifiez explicitement si les propriétés existent
<a name="best-practices-content-9"></a>

 Selon la sémantique d'OpenCypher, l'accès à une propriété équivaut à une jointure facultative et doit conserver toutes les lignes même si la propriété n'existe pas. Si vous savez, sur la base de votre schéma graphique, qu'une propriété particulière existera toujours pour cette entité, le fait de vérifier explicitement l'existence de cette propriété permet au moteur de requêtes de créer des plans optimaux et d'améliorer les performances. 

 Prenons l'exemple d'un modèle de graphe dans lequel les nœuds de type ont `person` toujours une propriété`name`. Au lieu de faire ceci : 

```
MATCH (n:person)
RETURN n.name
```

 Vérifiez explicitement l'existence de la propriété dans la requête avec une vérification IS NOT NULL : 

```
MATCH (n:person)
WHERE n.name IS NOT NULL
RETURN n.name
```

# N'utilisez pas de chemin nommé (sauf si cela est obligatoire)
<a name="best-practices-content-10"></a>

 Le chemin indiqué dans une requête entraîne toujours un coût supplémentaire, ce qui peut entraîner des pénalités en termes de latence et d'utilisation de la mémoire plus élevées. Considérons la requête suivante : 

```
MATCH p = (n)-[:commentedOn]->(m)
WITH p, m, n, n.score + m.score as total
WHERE total > 100 
MATCH (m)-[:commentedON]->(o)
WITH p, m, n, distinct(o) as o1
RETURN p, m.name, n.name, o1.name
```

 Dans la requête ci-dessus, en supposant que nous voulions uniquement connaître les propriétés des nœuds, l'utilisation du chemin « p » n'est pas nécessaire. En spécifiant le chemin nommé sous forme de variable, l'opération d'agrégation utilisant DISTINCT deviendra coûteuse à la fois en termes de temps et d'utilisation de la mémoire. Une version plus optimisée de la requête ci-dessus pourrait être : 

```
MATCH (n)-[:commentedOn]->(m)
WITH m, n, n.score + m.score as total
WHERE total > 100 
MATCH (m)-[:commentedON]->(o)
WITH m, n, distinct(o) as o1
RETURN m.name, n.name, o1.name
```

# Évitez COLLECT (DISTINCT ())
<a name="best-practices-content-11"></a>

**Note**  
À partir de la version [1.4.7.0](engine-releases-1.4.7.0.md) du moteur, cette réécriture recommandée n'est plus nécessaire.

 COLLECT (DISTINCT ()) est utilisé chaque fois qu'une liste contenant des valeurs distinctes doit être formée. COLLECT est une fonction d'agrégation, et le regroupement est effectué sur la base de clés supplémentaires projetées dans la même instruction. Lorsque distinct est utilisé, l'entrée est divisée en plusieurs segments, chaque segment désignant un groupe à réduire. Les performances seront affectées à mesure que le nombre de groupes augmentera. Dans Neptune, il est beaucoup plus efficace d'exécuter DISTINCT avant collecting/forming la liste. Cela permet d'effectuer le regroupement directement sur les touches de regroupement pour l'ensemble du morceau. 

 Considérons la requête suivante : 

```
MATCH (n:Person)-[:commented_on]->(p:Post)
WITH n, collect(distinct(p.post_id)) as post_list
RETURN n, post_list
```

 Une manière plus optimale d'écrire cette requête est la suivante : 

```
MATCH (n:Person)-[:commented_on]->(p:Post)
WITH DISTINCT n, p.post_id as postId
WITH n, collect(postId) as post_list
RETURN n, post_list
```

# Préférez la fonction de propriétés à la recherche de propriétés individuelle lors de la récupération de toutes les valeurs de propriété
<a name="best-practices-content-12"></a>

 La `properties()` fonction est utilisée pour renvoyer une carte contenant toutes les propriétés d'une entité, et elle est bien plus efficace que de renvoyer des propriétés individuellement. 

 En supposant que vos `Person` nœuds contiennent 5 propriétés `firstName` `lastName``age`,,`dept`,`company`, et que la requête suivante serait préférable : 

```
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN properties(n) as personDetails
```

 Plutôt que d'utiliser : 

```
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN n.firstName, n.lastName, n.age, n.dept, n.company
    
=== OR ===
    
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN {firstName: n.firstName, lastName: n.lastName, age: n.age, 
department: n.dept, company: n.company} as personDetails
```

# Effectuer des calculs statiques en dehors de la requête
<a name="best-practices-content-13"></a>

 Il est recommandé de résoudre les calculs statiques ( mathematical/string opérations simples) côté client. Prenons cet exemple où vous souhaitez rechercher toutes les personnes âgées d'un an ou moins que l'auteur : 

```
MATCH (m:Message)-[:HAS_CREATOR]->(p:person)
WHERE p.age <= ($age + 1)
RETURN m
```

 Ici, `$age` est injecté dans la requête via des paramètres, puis ajouté à une valeur fixe. Cette valeur est ensuite comparée à`p.age`. Au lieu de cela, une meilleure approche consisterait à effectuer l'ajout côté client et à transmettre la valeur calculée sous forme de paramètre \$1ageplusone. Cela permet au moteur de requêtes de créer des plans optimisés et d'éviter les calculs statiques pour chaque ligne entrante. En suivant ces directives, une version plus efficace de la requête serait : 

```
MATCH (m:Message)-[:HAS_CREATOR]->(p:person)
WHERE p.age <= $ageplusone
RETURN m
```

# Entrées par lots utilisant UNWIND au lieu d'instructions individuelles
<a name="best-practices-content-14"></a>

 Chaque fois que la même requête doit être exécutée pour différentes entrées, au lieu d'exécuter une requête par entrée, il serait beaucoup plus performant d'exécuter une requête pour un lot d'entrées. 

 Si vous souhaitez effectuer une fusion sur un ensemble de nœuds, l'une des options consiste à exécuter une requête de fusion par entrée : 

```
MERGE (n:Person {`~id`: $id})
SET n.name = $name, n.age = $age, n.employer = $employer
```

 Avec paramètres : 

```
params = {id: '1', name: 'john', age: 25, employer: 'Amazon'}
```

 La requête ci-dessus doit être exécutée pour chaque entrée. Bien que cette approche fonctionne, elle peut nécessiter l'exécution de nombreuses requêtes pour un grand nombre d'entrées. Dans ce scénario, le traitement par lots peut contribuer à réduire le nombre de requêtes exécutées sur le serveur et à améliorer le débit global. 

 Utilisez le modèle suivant : 

```
UNWIND $persons as person
MERGE (n:Person {`~id`: person.id})
SET n += person
```

 Avec paramètres : 

```
params = {persons: [{id: '1', name: 'john', age: 25, employer: 'Amazon'}, 
{id: '2', name: 'jack', age: 28, employer: 'Amazon'},
{id: '3', name: 'alice', age: 24, employer: 'Amazon'}...]}
```

 Il est recommandé d'expérimenter avec différentes tailles de lots afin de déterminer ce qui convient le mieux à votre charge de travail. 

# Préférez utiliser la personnalisation IDs pour le nœud ou la relation
<a name="best-practices-content-15"></a>

 Neptune permet aux utilisateurs d'attribuer des nœuds et des relations IDs de manière explicite. L'identifiant doit être globalement unique dans l'ensemble de données et déterministe pour être utile. Un identifiant déterministe peut être utilisé comme mécanisme de recherche ou de filtrage, tout comme les propriétés ; toutefois, l'utilisation d'un identifiant est beaucoup plus optimisée du point de vue de l'exécution des requêtes que celle des propriétés. L'utilisation de la personnalisation présente plusieurs avantages IDs  : 
+  Les propriétés peuvent être nulles pour une entité existante, mais l'ID doit exister. Cela permet au moteur de requête d'utiliser une jointure optimisée lors de l'exécution. 
+  Lorsque des requêtes de mutation simultanées sont exécutées, les risques d'[exceptions de modification simultanées](https://docs.aws.amazon.com//neptune/latest/userguide/transactions-exceptions.html) (CMEs) sont considérablement réduits lorsqu'elles IDs sont utilisées pour accéder à des nœuds, car le nombre de verrous requis est inférieur à IDs celui des propriétés en raison de leur caractère unique imposé. 
+  L'utilisation IDs permet d'éviter de créer des doublons de données, car Neptune impose l'unicité, contrairement aux IDs propriétés. 

 L'exemple de requête suivant utilise un identifiant personnalisé : 

**Note**  
 La propriété `~id` est utilisée pour spécifier l'ID, alors qu'elle `id` est simplement stockée comme n'importe quelle autre propriété. 

```
CREATE (n:Person {`~id`: '1', name: 'alice'})
```

 Sans utiliser d'identifiant personnalisé : 

```
CREATE (n:Person {id: '1', name: 'alice'})
```

 Si vous utilisez ce dernier mécanisme, il n'y a pas d'application de l'unicité et vous pouvez exécuter la requête ultérieurement : 

```
CREATE (n:Person {id: '1', name: 'john'})
```

 Cela crée un deuxième nœud `id=1` nommé`john`. Dans ce scénario, vous auriez maintenant deux nœuds portant `id=1` chacun un nom différent - (alice et john). 

# Évitez de faire des calculs \$1id dans la requête
<a name="best-practices-content-16"></a>

 Lorsque vous utilisez la personnalisation IDs dans les requêtes, effectuez toujours des calculs statiques en dehors des requêtes et fournissez ces valeurs dans les paramètres. Lorsque des valeurs statiques sont fournies, le moteur est mieux à même d'optimiser les recherches et d'éviter de scanner et de filtrer ces valeurs. 

 Si vous souhaitez créer des limites entre les nœuds existants dans la base de données, l'une des options suivantes peut être la suivante : 

```
UNWIND $sections as section
MATCH (s:Section {`~id`: 'Sec-' + section.id})
MERGE (s)-[:IS_PART_OF]->(g:Group {`~id`: 'g1'})
```

 Avec paramètres : 

```
parameters={sections: [{id: '1'}, {id: '2'}]}
```

 Dans la requête ci-dessus, `id` la section est calculée dans la requête. Comme le calcul est dynamique, le moteur ne peut pas insérer les identifiants de manière statique et finit par scanner tous les nœuds de section. Le moteur effectue ensuite un post-filtrage pour les nœuds requis. Cela peut être coûteux si la base de données contient de nombreux nœuds de section. 

 Une meilleure façon d'y parvenir est d'ajouter au `Sec-` préalable les identifiants transmis à la base de données : 

```
UNWIND $sections as section
MATCH (s:Section {`~id`: section.id})
MERGE (s)-[:IS_PART_OF]->(g:Group {`~id`: 'g1'})
```

 Avec paramètres : 

```
parameters={sections: [{id: 'Sec-1'}, {id: 'Sec-2'}]}
```

# Mise à jour/fusion de plusieurs nœuds
<a name="best-practices-merge-multiple-nodes"></a>

 Lorsque vous exécutez `MERGE` ou `CREATE` interrogez plusieurs nœuds, il est recommandé d'utiliser une `UNWIND` clause combinée avec une seule MERGE/CREATE clause plutôt qu'une MERGE/CREATE clause pour chaque nœud. Les requêtes qui utilisent une clause pour un nœud entraînent un plan d'exécution inefficace car chaque ligne doit être optimisée. Cela signifie que la majeure partie du temps d'exécution de la requête est consacrée au traitement statique plutôt qu'à la mise à jour proprement dite. 

 Une clause par nœud n'est pas optimale car elle ne s'adapte pas à l'augmentation du nombre de nœuds : 

```
MERGE (p1:Person {name: 'NameA'})
ON CREATE SET p1 += {prop1: 'prop1V1', prop2: 'prop2V1'}
MERGE (p2:Person {name: 'NameB'})
ON CREATE SET p2 += {prop1: 'prop1V2', prop2: 'prop2V2'}
MERGE (p3:Person {name: 'NameC'})
ON CREATE SET p3 += {prop1: 'prop1V3', prop2: 'prop1V3'}
```

 L'utilisation d'une clause `UNWIND` en conjonction avec une MERGE/CREATE clause permet d'obtenir le même comportement, mais un plan d'exécution plus optimal. Dans cet esprit, la requête modifiée ressemblerait à ce qui suit : 

```
## If not using custom id for nodes/relationship
UNWIND [{name: 'NameA', prop1: 'prop1V1', prop2: 'prop2V1'}, {name: 'NameB', prop1: 'prop1V2', prop2: 'prop2V2'}, {name: 'NameC', prop1: 'prop1V3', prop2: 'prop1V3'}] AS props
MERGE (p:Person {name: props.name})
ON CREATE SET p = props

## If using custom id for nodes/relationship
UNWIND [{`~id`: '1', 'name': 'NameA', 'prop1: 'prop1V1', prop2: 'prop2V1'}, {`~id`: '2', name: 'NameB', prop1: 'prop1V2', prop2: 'prop2V2'}, {`~id`: '3', name: 'NameC', prop1: 'prop1V3', prop2: 'prop1V3'}] AS props
MERGE (p:Person {`~id`: props.id})
ON CREATE SET p = removeKeyFromMap(props, '~id')
```