

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# Praktik Terbaik Neptunus Menggunakan OpenCypher dan Bolt
<a name="best-practices-opencypher"></a>

Ikuti praktik terbaik ini saat menggunakan bahasa kueri OpenCypher dan protokol Bolt dengan Neptunus. Untuk informasi tentang menggunakan OpenCypher di Neptunus, lihat. [Mengakses Grafik Neptunus dengan OpenCypher](access-graph-opencypher.md)

**Topics**
+ [Buat koneksi baru setelah failover](#best-practices-opencypher-renew-connection)
+ [Penanganan koneksi untuk aplikasi berumur panjang](#best-practices-opencypher-long-connections)
+ [Penanganan koneksi untuk AWS Lambda](#best-practices-opencypher-lambda-connections)
+ [Lebih suka diarahkan ke tepi dua arah dalam kueri](best-practices-opencypher-directed-edges.md)
+ [Neptunus tidak mendukung beberapa kueri bersamaan dalam suatu transaksi](best-practices-opencypher-multiple-queries.md)
+ [Tutup objek pengemudi saat Anda selesai](best-practices-opencypher-close-driver.md)
+ [Gunakan mode transaksi eksplisit untuk membaca dan menulis](best-practices-opencypher-use-explicit-txs.md)
+ [Coba lagi logika untuk pengecualian](best-practices-opencypher-retry-logic.md)
+ [Mengatur beberapa properti sekaligus menggunakan satu klausa SET](best-practices-content-0.md)
+ [Gunakan kueri berparameter](best-practices-content-2.md)
+ [Gunakan peta yang diratakan alih-alih peta bersarang di klausa UNWIND](best-practices-content-3.md)
+ [Tempatkan node yang lebih ketat di sisi kiri dalam ekspresi Variable-Length Path (VLP)](best-practices-content-4.md)
+ [Hindari pemeriksaan label node yang berlebihan dengan menggunakan nama hubungan granular](best-practices-content-5.md)
+ [Tentukan label tepi jika memungkinkan](best-practices-content-6.md)
+ [Hindari menggunakan klausa WITH jika memungkinkan](best-practices-content-7.md)
+ [Tempatkan filter restriktif sedini mungkin dalam kueri](best-practices-content-8.md)
+ [Periksa secara eksplisit apakah properti ada](best-practices-content-9.md)
+ [Jangan gunakan jalur bernama (kecuali jika diperlukan)](best-practices-content-10.md)
+ [Hindari KUMPULKAN (DISTINCT ())](best-practices-content-11.md)
+ [Lebih suka fungsi properti daripada pencarian properti individu saat mengambil semua nilai properti](best-practices-content-12.md)
+ [Lakukan perhitungan statis di luar kueri](best-practices-content-13.md)
+ [Masukan batch menggunakan UNWIND alih-alih pernyataan individual](best-practices-content-14.md)
+ [Lebih suka menggunakan kustom IDs untuk node/hubungan](best-practices-content-15.md)
+ [Hindari melakukan perhitungan \$1id dalam kueri](best-practices-content-16.md)
+ [Memperbarui/Menggabungkan beberapa node](best-practices-merge-multiple-nodes.md)

## Buat koneksi baru setelah failover
<a name="best-practices-opencypher-renew-connection"></a>

Dalam kasus failover, driver Bolt dapat terus terhubung ke instance penulis lama daripada yang aktif baru, karena nama DNS diselesaikan ke alamat IP tertentu.

Untuk mencegah hal ini, tutup lalu sambungkan kembali `Driver` objek setelah failover apa pun.

## Penanganan koneksi untuk aplikasi berumur panjang
<a name="best-practices-opencypher-long-connections"></a>

Saat membuat aplikasi yang berumur panjang, seperti yang berjalan di dalam container atau di instans Amazon EC2, buat instance `Driver` objek sekali dan kemudian gunakan kembali objek tersebut selama masa pakai aplikasi. Objek `Driver` aman untuk thread, dan overhead yang menginisialisasinya cukup besar.

## Penanganan koneksi untuk AWS Lambda
<a name="best-practices-opencypher-lambda-connections"></a>

Driver baut tidak disarankan untuk digunakan dalam AWS Lambda fungsi, karena overhead koneksi dan persyaratan manajemennya. Gunakan [titik akhir HTTPS](access-graph-opencypher-queries.md) sebagai gantinya.

# Lebih suka diarahkan ke tepi dua arah dalam kueri
<a name="best-practices-opencypher-directed-edges"></a>

Saat Neptunus melakukan optimasi kueri, tepi dua arah menyulitkan untuk membuat rencana kueri yang optimal. Rencana sub-optimal mengharuskan mesin melakukan pekerjaan yang tidak perlu dan menghasilkan kinerja yang lebih buruk.

Oleh karena itu, gunakan tepi yang diarahkan daripada yang dua arah bila memungkinkan. Misalnya, gunakan:

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

bukannya:

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

Sebagian besar model data sebenarnya tidak perlu melintasi tepi di kedua arah, sehingga kueri dapat mencapai peningkatan kinerja yang signifikan dengan beralih menggunakan tepi terarah.

Jika model data Anda memang memerlukan melintasi tepi dua arah, buat node pertama (sisi kiri) dalam `MATCH` pola node dengan penyaringan paling ketat.

Ambil contoh, “Temukan saya semua `routes` ke dan dari `ANC` bandara”. Tulis kueri ini untuk memulai di `ANC` bandara, seperti ini:

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

Mesin dapat melakukan jumlah minimal pekerjaan untuk memenuhi query, karena node yang paling terbatas ditempatkan sebagai node pertama (sisi kiri) dalam pola. Mesin kemudian dapat mengoptimalkan kueri.

Ini jauh lebih disukai daripada menyaring `ANC` bandara di akhir pola, seperti ini:

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

Ketika node yang paling terbatas tidak ditempatkan pertama kali dalam pola, mesin harus melakukan pekerjaan tambahan karena tidak dapat mengoptimalkan kueri dan harus melakukan pencarian tambahan untuk sampai pada hasil.

# Neptunus tidak mendukung beberapa kueri bersamaan dalam suatu transaksi
<a name="best-practices-opencypher-multiple-queries"></a>

Meskipun driver Bolt sendiri memungkinkan kueri bersamaan dalam suatu transaksi, Neptunus tidak mendukung beberapa kueri dalam transaksi yang berjalan secara bersamaan. Sebaliknya, Neptunus mengharuskan beberapa kueri dalam transaksi dijalankan secara berurutan, dan bahwa hasil dari setiap kueri sepenuhnya dikonsumsi sebelum kueri berikutnya dimulai.

Contoh di bawah ini menunjukkan cara menggunakan Bolt untuk menjalankan beberapa kueri secara berurutan dalam suatu transaksi, sehingga hasil masing-masing benar-benar dikonsumsi sebelum yang berikutnya dimulai:

```
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());
    }
  }
}
```

# Tutup objek pengemudi saat Anda selesai
<a name="best-practices-opencypher-close-driver"></a>

Pastikan untuk menutup klien ketika Anda selesai dengannya, sehingga koneksi Bolt ditutup oleh server dan semua sumber daya yang terkait dengan koneksi dilepaskan. Ini terjadi secara otomatis jika Anda menutup driver menggunakan`driver.close()`.

Jika driver tidak ditutup dengan benar, Neptunus menghentikan semua koneksi Bolt idle setelah 20 menit, atau setelah 10 hari jika Anda menggunakan otentikasi IAM.

Neptunus mendukung tidak lebih dari 1000 koneksi Bolt bersamaan. Jika Anda tidak secara eksplisit menutup koneksi ketika Anda selesai dengan mereka, dan jumlah koneksi langsung mencapai batas 1000, setiap upaya koneksi baru gagal.

# Gunakan mode transaksi eksplisit untuk membaca dan menulis
<a name="best-practices-opencypher-use-explicit-txs"></a>

Saat menggunakan transaksi dengan Neptunus dan driver Bolt, yang terbaik adalah secara eksplisit mengatur mode akses untuk transaksi baca dan tulis ke pengaturan yang tepat.

## Transaksi hanya-baca
<a name="best-practices-opencypher-read-txs"></a>

Untuk transaksi hanya-baca, jika Anda tidak meneruskan konfigurasi mode akses yang sesuai saat membangun sesi, tingkat isolasi default digunakan, yaitu isolasi kueri mutasi. Akibatnya, penting bagi transaksi read-only untuk mengatur mode akses secara eksplisit. `read`

**Contoh transaksi baca komit otomatis:**

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

**Baca contoh transaksi:**

```
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)
    }
  }
);
```

Dalam kedua kasus, [`SNAPSHOT`isolasi](transactions-isolation-levels.md) dicapai dengan menggunakan semantik transaksi [read-only Neptunus](transactions-neptune.md#transactions-neptune-read-only).

Karena replika baca hanya menerima kueri hanya-baca, kueri apa pun yang dikirimkan ke replika baca berjalan di bawah semantik isolasi. `SNAPSHOT`

Tidak ada pembacaan kotor atau pembacaan yang tidak dapat diulang untuk transaksi hanya-baca.

## Transaksi mutasi
<a name="best-practices-opencypher-mutation-txs"></a>

Untuk kueri mutasi, ada tiga mekanisme berbeda untuk membuat transaksi tulis, yang masing-masing diilustrasikan di bawah ini:

**Contoh transaksi tulis implisit:**

```
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)
    }
  }
);
```

**Contoh transaksi tulis komit otomatis:**

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

**Contoh transaksi tulis eksplisit:**

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

**Tingkat isolasi untuk transaksi tulis**
+ Pembacaan yang dibuat sebagai bagian dari kueri mutasi dijalankan di bawah isolasi `READ COMMITTED` transaksi.
+ Tidak ada bacaan kotor untuk bacaan yang dibuat sebagai bagian dari kueri mutasi.
+ Catatan dan rentang catatan dikunci saat membaca dalam kueri mutasi.
+ Ketika rentang indeks telah dibaca oleh transaksi mutasi, ada jaminan kuat bahwa rentang ini tidak akan dimodifikasi oleh transaksi bersamaan sampai akhir pembacaan.

Kueri mutasi tidak aman untuk utas.

Untuk konflik, lihat[Resolusi Konflik Menggunakan Lock-Wait Timeout](transactions-neptune.md#transactions-neptune-conflicts).

Kueri mutasi tidak dicoba ulang secara otomatis jika terjadi kegagalan.

# Coba lagi logika untuk pengecualian
<a name="best-practices-opencypher-retry-logic"></a>

Untuk semua pengecualian yang memungkinkan percobaan ulang, umumnya yang terbaik adalah menggunakan [strategi backoff dan coba lagi eksponensial yang menyediakan waktu tunggu yang semakin lama di antara percobaan ulang](https://docs.aws.amazon.com/general/latest/gr/api-retries.html) sehingga dapat menangani masalah sementara seperti kesalahan dengan lebih baik. `ConcurrentModificationException` Berikut ini menunjukkan contoh backoff eksponensial dan pola coba lagi:

```
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);
  }
}
```

# Mengatur beberapa properti sekaligus menggunakan satu klausa SET
<a name="best-practices-content-0"></a>

 Alih-alih menggunakan beberapa klausa SET untuk menyetel properti individual, gunakan peta untuk menyetel beberapa properti untuk entitas sekaligus. 

 Anda dapat menggunakan: 

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

 Alih-alih: 

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

 Klausa SET menerima salah satu properti atau peta. Jika memperbarui beberapa properti pada satu entitas, menggunakan klausa SET tunggal dengan peta memungkinkan pembaruan dilakukan dalam satu operasi alih-alih beberapa operasi, yang dapat dijalankan dengan lebih efisien. 

## Gunakan klausa SET untuk menghapus beberapa properti sekaligus
<a name="best-practices-content-1"></a>

 Saat menggunakan bahasa OpenCypher, REMOVE digunakan untuk menghapus properti dari entitas. Di Neptunus, setiap properti yang dihapus memerlukan operasi terpisah, menambahkan latensi kueri. Sebagai gantinya, Anda dapat menggunakan SET dengan peta untuk mengatur semua nilai properti`null`, yang di Neptunus setara dengan menghapus properti. Neptunus akan meningkatkan kinerja ketika beberapa properti pada satu entitas diperlukan untuk dihapus. 

Gunakan:

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

Alih-alih:

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

# Gunakan kueri berparameter
<a name="best-practices-content-2"></a>

 Disarankan untuk selalu menggunakan query parameterized saat query menggunakan OpenCypher. Mesin kueri dapat memanfaatkan kueri parameter berulang untuk fitur seperti cache paket kueri, di mana pemanggilan berulang dari struktur berparameter yang sama dengan parameter yang berbeda dapat memanfaatkan paket yang di-cache. Paket kueri yang dihasilkan untuk kueri berparameter di-cache dan digunakan kembali hanya ketika selesai dalam 100ms dan tipe parameternya adalah NUMBER, BOOLEAN atau STRING. 

Gunakan:

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

Dengan parameter:

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

Alih-alih:

```
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
```

# Gunakan peta yang diratakan alih-alih peta bersarang di klausa UNWIND
<a name="best-practices-content-3"></a>

 Struktur bersarang dalam dapat membatasi kemampuan mesin kueri untuk menghasilkan rencana kueri yang optimal. Untuk mengatasi sebagian masalah ini, pola yang ditentukan berikut akan membuat rencana optimal untuk skenario berikut: 
+  Skenario 1: BERSANTAILAH dengan daftar literal cypher, yang mencakup NUMBER, STRING dan BOOLEAN. 
+  Skenario 2: BERSANTAILAH dengan daftar peta yang diratakan, yang hanya mencakup literal cypher (NUMBER, STRING, BOOLEAN) sebagai nilai. 

 Saat menulis kueri yang berisi klausa UNWIND, gunakan rekomendasi di atas untuk meningkatkan kinerja. 

Contoh skenario 1:

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

Dengan parameter:

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

 Contoh untuk Skenario 2 adalah untuk menghasilkan daftar node untuk CREATE atau MERGE. Alih-alih mengeluarkan beberapa pernyataan, gunakan pola berikut untuk mendefinisikan properti sebagai sekumpulan peta yang diratakan: 

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

Dengan parameter:

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

Alih-alih objek simpul bersarang seperti:

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

Dengan parameter:

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

# Tempatkan node yang lebih ketat di sisi kiri dalam ekspresi Variable-Length Path (VLP)
<a name="best-practices-content-4"></a>

 Dalam kueri Variable-Length Path (VLP), mesin kueri mengoptimalkan evaluasi dengan memilih untuk memulai traversal di sisi kiri atau kanan ekspresi. Keputusan didasarkan pada kardinalitas pola di sisi kiri dan kanan. Kardinalitas adalah jumlah node yang cocok dengan pola yang ditentukan. 
+  Jika pola yang tepat memiliki kardinalitas satu, maka sisi kanan akan menjadi titik awal. 
+  Jika sisi kiri dan kanan memiliki kardinalitas satu, ekspansi diperiksa di kedua sisi dan dimulai di samping dengan ekspansi yang lebih kecil. Ekspansi adalah jumlah tepi keluar atau masuk untuk node di sebelah kiri dan node di sisi kanan ekspresi VLP. Bagian optimasi ini hanya digunakan jika hubungan VLP searah dan jenis hubungan disediakan. 
+  Jika tidak, sisi kiri akan menjadi titik awal. 

 Untuk rantai ekspresi VLP, optimasi ini hanya dapat diterapkan pada ekspresi pertama. Yang lain VLPs dievaluasi dimulai dengan sisi kiri. Sebagai contoh, biarkan kardinalitas (a), (b) menjadi satu, dan kardinalitas (c) lebih besar dari satu. 
+  `(a)-[*1..]->(c)`: Evaluasi dimulai dengan (a). 
+  `(c)-[*1..]->(a)`: Evaluasi dimulai dengan (a). 
+  `(a)-[*1..]-(c)`: Evaluasi dimulai dengan (a). 
+  `(c)-[*1..]-(a)`: Evaluasi dimulai dengan (a). 

 Sekarang biarkan tepi masuk (a) menjadi dua, dan tepi keluar (a) menjadi tiga, tepi masuk (b) menjadi empat, dan tepi keluar (b) menjadi lima. 
+  `(a)-[*1..]->(b)`: Evaluasi dimulai dengan (a) karena tepi keluar (a) kurang dari tepi masuk (b). 
+  `(a)<-[*1..]-(b)`: Evaluasi dimulai dengan (a) karena tepi masuk (a) kurang dari tepi keluar (b). 

 Sebagai aturan umum, tempatkan pola yang lebih ketat di sisi kiri ekspresi VLP. 

# Hindari pemeriksaan label node yang berlebihan dengan menggunakan nama hubungan granular
<a name="best-practices-content-5"></a>

 Saat mengoptimalkan kinerja, menggunakan label hubungan yang eksklusif untuk pola simpul memungkinkan penghapusan pemfilteran label pada node. Pertimbangkan model grafik di mana hubungan hanya `likes` digunakan untuk mendefinisikan hubungan antara dua `person` node. Kita bisa menulis query berikut untuk menemukan pola ini: 

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

 Pemeriksaan `person` label pada n dan m berlebihan, karena kami mendefinisikan hubungan hanya muncul ketika keduanya bertipe. `person` Untuk mengoptimalkan kinerja, kita dapat menulis kueri sebagai berikut: 

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

 Pola ini juga dapat diterapkan ketika properti eksklusif untuk label node tunggal. Asumsikan bahwa hanya `person` node yang memiliki properti`email`, oleh karena itu memverifikasi kecocokan `person` label node berlebihan. Menulis kueri ini sebagai: 

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

 Kurang efisien daripada menulis kueri ini sebagai: 

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

 Anda hanya harus mengadopsi pola ini ketika kinerja penting dan Anda memiliki pemeriksaan dalam proses pemodelan Anda untuk memastikan label tepi ini tidak digunakan kembali untuk pola yang melibatkan label simpul lainnya. Jika Anda kemudian memperkenalkan `email` properti pada label node lain seperti`company`, maka hasilnya akan berbeda antara dua versi kueri ini. 

# Tentukan label tepi jika memungkinkan
<a name="best-practices-content-6"></a>

 Disarankan untuk memberikan label tepi jika memungkinkan saat menentukan tepi dalam suatu pola. Pertimbangkan contoh pertanyaan berikut, yang digunakan untuk menghubungkan semua orang yang tinggal di kota dengan semua orang yang mengunjungi kota itu. 

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

 Jika model grafik Anda menautkan orang ke node selain hanya kota yang menggunakan beberapa label tepi, dengan tidak menentukan label akhir, Neptunus perlu mengevaluasi jalur tambahan yang nantinya akan dibuang. Dalam kueri di atas, karena label tepi tidak diberikan, mesin melakukan lebih banyak pekerjaan terlebih dahulu dan kemudian menyaring nilai untuk mendapatkan hasil yang benar. Versi yang lebih baik dari kueri di atas mungkin: 

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

 Ini tidak hanya membantu dalam evaluasi, tetapi memungkinkan perencana kueri untuk membuat rencana yang lebih baik. Anda bahkan dapat menggabungkan praktik terbaik ini dengan pemeriksaan label node redundan untuk menghapus pemeriksaan label kota dan menulis kueri sebagai: 

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

# Hindari menggunakan klausa WITH jika memungkinkan
<a name="best-practices-content-7"></a>

 Klausa WITH di OpenCypher bertindak sebagai batas di mana segala sesuatu sebelum dijalankan, dan kemudian nilai yang dihasilkan diteruskan ke bagian yang tersisa dari kueri. Klausa WITH diperlukan ketika Anda memerlukan agregasi sementara atau ingin membatasi jumlah hasil, tetapi selain itu Anda harus mencoba menghindari penggunaan klausa WITH. Panduan umum adalah menghapus klausa WITH sederhana ini (tanpa agregasi, urutan berdasarkan atau batas) untuk memungkinkan perencana kueri bekerja pada seluruh kueri untuk membuat rencana optimal secara global. Sebagai contoh, asumsikan Anda menulis kueri untuk mengembalikan semua orang yang tinggal di`India`: 

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

 Pada versi di atas, klausa WITH membatasi penempatan pola `(city)-[:part_of]->(country {name: 'India'})` (yang lebih membatasi) sebelumnya. `(person)-[:lives_in]->(city)` Ini membuat rencana menjadi kurang optimal. Pengoptimalan pada kueri ini adalah menghapus klausa WITH dan membiarkan perencana menghitung paket terbaik. 

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

# Tempatkan filter restriktif sedini mungkin dalam kueri
<a name="best-practices-content-8"></a>

 Dalam semua skenario, penempatan awal filter dalam kueri membantu mengurangi solusi perantara yang harus dipertimbangkan oleh rencana kueri. Ini berarti lebih sedikit memori dan lebih sedikit sumber daya komputasi yang diperlukan untuk mengeksekusi kueri. 

 Contoh berikut membantu Anda memahami dampak ini. Misalkan Anda menulis kueri untuk mengembalikan semua orang yang tinggal di`India`. Salah satu versi kueri dapat berupa: 

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

 Versi kueri di atas bukanlah cara paling optimal untuk mencapai kasus penggunaan ini. Filter `country.name = 'India'` muncul kemudian dalam pola kueri. Ini pertama-tama akan mengumpulkan semua orang dan di mana mereka tinggal, dan mengelompokkan mereka berdasarkan negara, kemudian menyaring hanya untuk grup untuk`country.name = India`. Cara optimal untuk menanyakan hanya orang yang tinggal di `India` dan kemudian melakukan agregasi pengumpulan. 

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

 Aturan umum adalah menempatkan filter sesegera mungkin setelah variabel diperkenalkan. 

# Periksa secara eksplisit apakah properti ada
<a name="best-practices-content-9"></a>

 Berdasarkan semantik OpenCypher, ketika properti diakses itu setara dengan gabungan opsional dan harus mempertahankan semua baris bahkan jika properti tidak ada. Jika Anda tahu berdasarkan skema grafik Anda bahwa properti tertentu akan selalu ada untuk entitas itu, secara eksplisit memeriksa properti itu untuk keberadaan memungkinkan mesin kueri untuk membuat rencana optimal dan meningkatkan kinerja. 

 Pertimbangkan model grafik di mana node tipe `person` selalu memiliki properti`name`. Alih-alih melakukan ini: 

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

 Verifikasi keberadaan properti secara eksplisit dalam kueri dengan pemeriksaan IS NOT NULL: 

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

# Jangan gunakan jalur bernama (kecuali jika diperlukan)
<a name="best-practices-content-10"></a>

 Jalur bernama dalam kueri selalu dikenakan biaya tambahan, yang dapat menambahkan penalti dalam hal latensi dan penggunaan memori yang lebih tinggi. Pertimbangkan kueri berikut: 

```
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
```

 Dalam query di atas, dengan asumsi kita hanya ingin mengetahui properti node, penggunaan path “p” tidak perlu. Dengan menentukan jalur bernama sebagai variabel, operasi agregasi menggunakan DISTINCT akan menjadi mahal baik dari segi waktu maupun penggunaan memori. Versi kueri di atas yang lebih dioptimalkan dapat berupa: 

```
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
```

# Hindari KUMPULKAN (DISTINCT ())
<a name="best-practices-content-11"></a>

**catatan**  
Pada versi mesin [1.4.7.0](engine-releases-1.4.7.0.md), penulisan ulang yang disarankan ini tidak lagi diperlukan.

 COLLECT (DISTINCT ()) digunakan setiap kali daftar akan dibentuk berisi nilai-nilai yang berbeda. COLLECT adalah fungsi agregasi, dan pengelompokan dilakukan berdasarkan kunci tambahan yang diproyeksikan dalam pernyataan yang sama. Ketika berbeda digunakan, input dibagi menjadi beberapa potongan di mana setiap potongan menunjukkan satu kelompok untuk reduksi. Kinerja akan terpengaruh karena jumlah kelompok meningkat. Di Neptunus, jauh lebih efisien untuk melakukan DISTINCT sebelum collecting/forming benar-benar daftar. Hal ini memungkinkan pengelompokan dilakukan langsung pada kunci pengelompokan untuk seluruh potongan. 

 Pertimbangkan kueri berikut: 

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

 Cara yang lebih optimal untuk menulis kueri ini adalah: 

```
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
```

# Lebih suka fungsi properti daripada pencarian properti individu saat mengambil semua nilai properti
<a name="best-practices-content-12"></a>

 `properties()`Fungsi ini digunakan untuk mengembalikan peta yang berisi semua properti untuk entitas, dan jauh lebih efisien daripada mengembalikan properti secara individual. 

 Dengan asumsi `Person` node Anda berisi 5 properti,`firstName`,`lastName`,`age`,`dept`, dan`company`, kueri berikut akan lebih disukai: 

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

 Daripada menggunakan: 

```
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
```

# Lakukan perhitungan statis di luar kueri
<a name="best-practices-content-13"></a>

 Disarankan untuk menyelesaikan perhitungan statis ( mathematical/string operasi sederhana) di sisi klien. Pertimbangkan contoh ini di mana Anda ingin menemukan semua orang satu tahun lebih tua atau kurang dari penulis: 

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

 Di sini, `$age` disuntikkan ke dalam query melalui parameter, dan kemudian ditambahkan ke nilai tetap. Nilai ini kemudian dibandingkan dengan`p.age`. Sebaliknya, pendekatan yang lebih baik adalah melakukan penambahan di sisi klien dan meneruskan nilai yang dihitung sebagai parameter \$1ageplusone. Ini membantu mesin kueri untuk membuat rencana yang dioptimalkan, dan menghindari perhitungan statis untuk setiap baris yang masuk. Mengikuti pedoman ini, versi kueri yang lebih efisien adalah: 

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

# Masukan batch menggunakan UNWIND alih-alih pernyataan individual
<a name="best-practices-content-14"></a>

 Setiap kali kueri yang sama perlu dijalankan untuk input yang berbeda, alih-alih mengeksekusi satu kueri per input, akan jauh lebih berkinerja untuk menjalankan kueri untuk sekumpulan input. 

 Jika Anda ingin menggabungkan pada satu set node, salah satu opsi adalah menjalankan kueri gabungan per input: 

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

 Dengan parameter: 

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

 Query di atas perlu dieksekusi untuk setiap masukan. Meskipun pendekatan ini berhasil, mungkin memerlukan banyak kueri untuk dieksekusi untuk satu set input yang besar. Dalam skenario ini, batching dapat membantu mengurangi jumlah kueri yang dieksekusi di server, serta meningkatkan keseluruhan throughput. 

 Gunakan pola berikut: 

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

 Dengan parameter: 

```
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'}...]}
```

 Eksperimen dengan ukuran batch yang berbeda disarankan untuk menentukan apa yang terbaik untuk beban kerja Anda. 

# Lebih suka menggunakan kustom IDs untuk node/hubungan
<a name="best-practices-content-15"></a>

 Neptunus memungkinkan pengguna untuk secara eksplisit menetapkan pada node IDs dan hubungan. ID harus unik secara global dalam kumpulan data dan deterministik agar berguna. ID deterministik dapat digunakan sebagai pencarian atau mekanisme penyaringan seperti properti; Namun, menggunakan ID jauh lebih dioptimalkan dari perspektif eksekusi kueri daripada menggunakan properti. Ada beberapa manfaat menggunakan kustom IDs - 
+  Properti dapat menjadi nol untuk entitas yang ada, tetapi ID harus ada. Ini memungkinkan mesin kueri untuk menggunakan gabungan yang dioptimalkan selama eksekusi. 
+  Ketika kueri mutasi bersamaan dijalankan, kemungkinan [pengecualian modifikasi bersamaan](https://docs.aws.amazon.com//neptune/latest/userguide/transactions-exceptions.html) (CMEs) berkurang secara signifikan ketika digunakan untuk mengakses node karena lebih sedikit kunci IDs yang diambil IDs daripada properti karena keunikannya yang diberlakukan. 
+  Menggunakan IDs menghindari kemungkinan membuat data duplikat karena Neptunus memberlakukan keunikan pada, tidak seperti properti. IDs 

 Contoh query berikut menggunakan ID kustom: 

**catatan**  
 Properti `~id` ini digunakan untuk menentukan ID, sedangkan hanya `id` disimpan sebagai properti lainnya. 

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

 Tanpa menggunakan ID khusus: 

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

 Jika menggunakan mekanisme yang terakhir, tidak ada penegakan keunikan dan Anda nantinya dapat menjalankan kueri: 

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

 Ini menciptakan node kedua dengan `id=1` bernama`john`. Dalam skenario ini, Anda sekarang akan memiliki dua node dengan`id=1`, masing-masing memiliki nama yang berbeda - (alice dan john). 

# Hindari melakukan perhitungan \$1id dalam kueri
<a name="best-practices-content-16"></a>

 Saat menggunakan kustom IDs dalam kueri, selalu lakukan perhitungan statis di luar kueri dan berikan nilai ini dalam parameter. Ketika nilai statis disediakan, mesin lebih mampu mengoptimalkan pencarian dan menghindari pemindaian dan pemfilteran nilai-nilai ini. 

 Jika Anda ingin membuat tepi antara node yang ada di database, salah satu opsi dapat berupa: 

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

 Dengan parameter: 

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

 Dalam kueri di atas, `id` bagian sedang dihitung dalam kueri. Karena perhitungannya dinamis, mesin tidak dapat secara statis inline id dan akhirnya memindai semua node bagian. Mesin kemudian melakukan post-filtering untuk node yang diperlukan. Ini bisa mahal jika ada banyak node bagian dalam database. 

 Cara yang lebih baik untuk mencapai ini adalah `Sec-` dengan menambahkan id yang diteruskan ke database: 

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

 Dengan parameter: 

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

# Memperbarui/Menggabungkan beberapa node
<a name="best-practices-merge-multiple-nodes"></a>

 Saat menjalankan `MERGE` atau `CREATE` kueri pada beberapa node, disarankan untuk menggunakan kombinasi dengan klausa tunggal versus menggunakan MERGE/CREATE MERGE/CREATE klausa untuk setiap node. `UNWIND` Kueri yang menggunakan satu klausa untuk satu node menyebabkan rencana eksekusi yang tidak efisien karena setiap baris memerlukan pengoptimalan. Ini mengarah ke sebagian besar waktu eksekusi kueri yang dihabiskan dalam pemrosesan statis alih-alih pembaruan yang sebenarnya. 

 Satu Klausul per node tidak optimal karena tidak menskalakan dengan meningkatnya jumlah node: 

```
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'}
```

 Menggunakan `UNWIND` dalam hubungannya dengan satu MERGE/CREATE klausa memungkinkan untuk perilaku yang sama tetapi rencana eksekusi yang lebih optimal. Dengan mengingat hal ini, kueri yang diubah akan terlihat seperti: 

```
## 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')
```