

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 最佳實務：發揮 Neptune 的最大功用
<a name="best-practices"></a>

以下是使用 Amazon Neptune 的一些一般建議。使用這裡的參考資訊，快速找到使用 Amazon Neptune 並將效能發揮到最高的建議。

**Contents**
+ [Amazon Neptune 基本操作準則](best-practices-general-basic.md)
  + [Amazon Neptune 安全最佳實務](best-practices-general-security.md)
  + [避免叢集中的不同執行個體類別](best-practices-general-basic.md#best-practices-loader-heterogeneous-instances)
  + [避免在大量載入期間重複重新啟動](best-practices-general-basic.md#best-practices-loader-repeated-restarts)
  + [如果您有大量述詞，請啟用 OSGP 索引](best-practices-general-basic.md#best-practices-general-predicates)
  + [盡可能避免長時間執行的交易](best-practices-general-basic.md#best-practices-general-long-running-transactions)
  + [使用 Neptune 指標的最佳實務](best-practices-general-metrics.md)
  + [調校 Neptune 查詢的最佳實務](best-practices-general-basic.md#best-practices-general-tuning)
  + [在各個僅供讀取複本之間平衡負載](best-practices-general-basic.md#best-practices-general-loadbalance)
  + [使用臨時的較大執行個體，載入速度更快](best-practices-general-basic.md#best-practices-loader-tempinstance)
  + [容錯移轉至僅供讀取複本來調整您的寫入器執行個體大小](best-practices-general-basic.md#best-practices-resize-instance)
  + [在資料預先擷取任務中斷錯誤之後重試上傳](best-practices-general-basic.md#load-api-reference-status-interrupted)
+ [使用 Gremlin 搭配 Neptune 的一般最佳實務](best-practices-gremlin.md)
  + [Neptune Serverless 的活動訊號組態](best-practices-gremlin-heartbeat-serverless.md)
  + [建造 upsert 查詢以利用 DFE 引擎](best-practices-gremlin.md#best-practices-gremlin-upserts)
  + [在您將部署 Girmlin 程式碼的內容中測試該程式碼](best-practices-gremlin-console-glv-differences.md)
  + [建立有效率的多執行緒 Gremlin 寫入](best-practices-gremlin-multithreaded-writes.md)
  + [利用建立時間屬性清除記錄](best-practices-gremlin-prune.md)
  + [對 Groovy 時間資料使用 `datetime( )` 方法](best-practices-gremlin-datetime.md)
  + [在 GLV 時間資料使用原生日期和時間](best-practices-gremlin-datetime-glv.md)
+ [使用 Gremlin Java 用戶端搭配 Neptune 的最佳實務](best-practices-gremlin-java-client.md)
  + [跨多個執行緒重複使用用戶端物件](best-practices-gremlin-java-reuse.md)
  + [為讀取和寫入端點建立個別的 Gremlin Java 用戶端物件](best-practices-gremlin-java-separate.md)
  + [將多個僅供讀取複本端點新增至 Gremlin Java 連線集區](best-practices-gremlin-java-multiple.md)
  + [關閉用戶端以避免連線限制](best-practices-gremlin-java-close-connections.md)
  + [在容錯移轉之後建立新連線](best-practices-gremlin-java-new-connection.md)
  + [將 `maxInProcessPerConnection` 和 `maxSimultaneousUsagePerConnection` 設定為相同值。](best-practices-gremlin-java-maxes.md)
  + [將查詢以位元碼形式而非字串形式傳送至伺服器](best-practices-gremlin-java-bytecode.md)
  + [一律完全耗用由查詢傳回的 ResultSet 或反覆運算器](best-practices-gremlin-java-resultset.md)
  + [在批次中大量新增頂點和邊緣](best-practices-gremlin-java-batch-add.md)
  + [在 Java 虛擬機器中停用 DNS 快取](best-practices-gremlin-java-disable-dns-caching.md)
  + [或者，也可以在每個查詢層級設定逾時](best-practices-gremlin-java-per-query-timeout.md)
  + [`java.util.concurrent.TimeoutException` 疑難排解](best-practices-gremlin-java-exceptions-TimeoutException.md)
+ [使用 openCypher 和 Bolt 的 Neptune 最佳實務](best-practices-opencypher.md)
  + [在容錯移轉之後建立新連線](best-practices-opencypher.md#best-practices-opencypher-renew-connection)
  + [適用於長期命應用的連線處理](best-practices-opencypher.md#best-practices-opencypher-long-connections)
  + [的連線處理 AWS Lambda](best-practices-opencypher.md#best-practices-opencypher-lambda-connections)
  + [偏好查詢中的定向至雙向邊緣](best-practices-opencypher-directed-edges.md)
  + [Neptune 不支援交易中的多個並行查詢](best-practices-opencypher-multiple-queries.md)
  + [完成後關閉驅動程式物件](best-practices-opencypher-close-driver.md)
  + [使用明確的交易模式進行讀取和寫入](best-practices-opencypher-use-explicit-txs.md)
    + [唯讀交易](best-practices-opencypher-use-explicit-txs.md#best-practices-opencypher-read-txs)
    + [變動交易](best-practices-opencypher-use-explicit-txs.md#best-practices-opencypher-mutation-txs)
  + [例外狀況的重試邏輯](best-practices-opencypher-retry-logic.md)
  + [使用單一 SET 子句一次設定多個屬性](best-practices-content-0.md)
    + [使用 SET 子句一次移除多個屬性](best-practices-content-0.md#best-practices-content-1)
  + [使用參數化查詢](best-practices-content-2.md)
  + [在 UNWIND 子句中使用平面貼圖而非巢狀貼圖](best-practices-content-3.md)
  + [在可變長度路徑 (VLP) 表達式中，將更嚴格的節點放在左側](best-practices-content-4.md)
  + [使用精細的關係名稱避免備援節點標籤檢查](best-practices-content-5.md)
  + [盡可能指定邊緣標籤](best-practices-content-6.md)
  + [盡可能避免使用 WITH 子句](best-practices-content-7.md)
  + [儘早在查詢中放置限制性篩選條件](best-practices-content-8.md)
  + [明確檢查屬性是否存在](best-practices-content-9.md)
  + [請勿使用具名路徑 （除非必要）](best-practices-content-10.md)
  + [避免 COLLECT(DISTINCT())](best-practices-content-11.md)
  + [擷取所有屬性值時，偏好個別屬性查詢的屬性函數](best-practices-content-12.md)
  + [在查詢之外執行靜態運算](best-practices-content-13.md)
  + [使用 UNWIND 而非個別陳述式的批次輸入](best-practices-content-14.md)
  + [偏好使用節點/關係IDs](best-practices-content-15.md)
  + [避免在查詢中進行 \$1id 運算](best-practices-content-16.md)
  + [更新/合併多個節點](best-practices-merge-multiple-nodes.md)
+ [使用 SPARQL 的 Neptune 最佳實務](best-practices-sparql.md)
  + [預設查詢所有具名圖形](best-practices-sparql-query.md)
  + [指定要載入的具名圖表](best-practices-sparql-graph.md)
  + [在您的查詢中選擇 FILTER、FILTER...IN 和 VALUES](best-practices-sparql-batch.md)

# Amazon Neptune 基本操作準則
<a name="best-practices-general-basic"></a>

下列是使用 Neptune 時，您應該遵循的基本操作準則。
+ 了解 Neptune 資料庫執行個體，以便您可以基於效能和使用案例需求適當調地整其大小。請參閱 [Amazon Neptune 資料庫叢集和執行個體](feature-overview-db-clusters.md)。
+ 監控您的 CPU 和記憶體用量。這可協助您了解何時遷移到有更大 CPU 或記憶體容量的資料庫執行個體類別，以達到您需要的查詢效能。您可以設定 Amazon CloudWatch 在使用模式變更或接近部署容量時通知您。這樣做有助於維持系統效能和可用性。如需詳細資訊，請參閱[監控執行個體](feature-overview-db-clusters.md#feature-overview-monitoring-instances)和[監控 Neptune](monitoring.md)。

  因為 Neptune 擁有自己的記憶體管理員，因此在 CPU 用量相當高時，記憶體用量仍相對較低的情況非常正常。在執行查詢時發生記憶體不足異常，便是您需要增加可釋放記憶體的最佳指示。
+ 啟用自動備份並設定備份時段，在方便的時間進行備份。
+ 測試資料庫執行個體的容錯移轉，以了解您的使用案例執行此程序需時多長。它也有助於確保可存取您資料庫執行個體的應用程式，可以在容錯移轉後，自動連線到新的資料庫執行個體。
+ 如果可能，請在相同區域和 VPC 中執行用戶端與 Neptune 叢集，因為使用 VPC 對等互連進行跨區域連線可能會導致查詢回應時間的延遲。對於個位數毫秒的查詢回應，必須將用戶端和 Neptune 叢集保留在相同區域和 VPC 中。
+ 當您建立僅供讀取複本執行個體時，它應該至少與主要寫入器執行個體一樣大。這有助於在檢查時保持複寫延遲，並避免複本重新啟動。請參閱 [避免叢集中的不同執行個體類別](#best-practices-loader-heterogeneous-instances)。
+ 在升級到新的主要引擎版本之前，請務必在其上測試您的應用程式，然後再升級。您可以透過複製資料庫叢集來執行此動作，以便複製叢集可執行新的引擎版本，然後在該複製上測試您的應用程式。
+ 為了便於容錯移轉，所有執行個體最好都應該是相同的大小。

**Topics**
+ [Amazon Neptune 安全最佳實務](best-practices-general-security.md)
+ [避免叢集中的不同執行個體類別](#best-practices-loader-heterogeneous-instances)
+ [避免在大量載入期間重複重新啟動](#best-practices-loader-repeated-restarts)
+ [如果您有大量述詞，請啟用 OSGP 索引](#best-practices-general-predicates)
+ [盡可能避免長時間執行的交易](#best-practices-general-long-running-transactions)
+ [使用 Neptune 指標的最佳實務](best-practices-general-metrics.md)
+ [調校 Neptune 查詢的最佳實務](#best-practices-general-tuning)
+ [在各個僅供讀取複本之間平衡負載](#best-practices-general-loadbalance)
+ [使用臨時的較大執行個體，載入速度更快](#best-practices-loader-tempinstance)
+ [容錯移轉至僅供讀取複本來調整您的寫入器執行個體大小](#best-practices-resize-instance)
+ [在資料預先擷取任務中斷錯誤之後重試上傳](#load-api-reference-status-interrupted)

# Amazon Neptune 安全最佳實務
<a name="best-practices-general-security"></a>

使用 AWS Identity and Access Management (IAM) 帳戶來控制對 Neptune API 動作的存取。控制建立、修改或刪除 Neptune 資源的動作 (例如資料庫執行個體、安全群組、選項群組或參數群組)，以及執行常見管理動作的動作 (例如備份和還原資料庫執行個體)。
+ 盡可能使用臨時憑證而不是永久憑證。
+ 將個別的 IAM 帳戶指派給管理 Amazon Relational Database Service (Amazon RDS) 資源的每個人員。切勿使用 AWS 帳戶根使用者來管理 Neptune 資源。為每個人 (包含您) 建立 IAM 使用者。
+ 授予每個使用者執行其職責所需的最低許可集。
+ 使用 IAM 群組來有效管理多個使用者的許可。
+ 定期輪替您的 IAM 登入資料。

如需使用 IAM 存取 Neptune 資源的詳細資訊，請參閱 [保護您的 Amazon Neptune 資料庫](security.md)。如需有關使用 IAM 的一般資訊，請參閱《IAM 使用者指南》**中的 [AWS Identity and Access Management](https://docs.aws.amazon.com/IAM/latest/UserGuide/Welcome.html) 和 [IAM 最佳實務](https://docs.aws.amazon.com/IAM/latest/UserGuide/IAMBestPractices.html)。

## 避免叢集中的不同執行個體類別
<a name="best-practices-loader-heterogeneous-instances"></a>

當您的資料庫叢集包含不同類別的執行個體時，可能會在一段時間後發生問題。最常見的問題是，由於複寫延遲，小型讀取器執行個體可能會進入重複重新啟動的週期。如果讀取器節點具有的資料庫執行個體類別組態比寫入器資料庫執行個體的組態還弱，則變更量可能太大，以致讀取器無法跟上。

**重要**  
若要避免複寫延遲所導致的重複重新啟動，請設定資料庫叢集，讓所有執行個體都具有相同的執行個體類別 (大小)。

您可以使用 Amazon CloudWatch 中的 `ClusterReplicaLag` 指標，查看資料庫叢集中寫入器執行個體 (主要執行個體) 與讀取器之間的延遲。`VolumeWriteIOPs` 指標也可讓您偵測叢集中可能會造成複寫延遲的大量寫入活動。

## 避免在大量載入期間重複重新啟動
<a name="best-practices-loader-repeated-restarts"></a>

如果您由於大量載入期間的複寫延遲而遭遇重複僅供讀取複本重新啟動的週期，則您的複本可能無法跟上資料庫叢集中的寫入器。

將讀取器擴展為大於寫入器，或在大量載入期間暫時將其移除，然後在完成後重新建立這些讀取器。

## 如果您有大量述詞，請啟用 OSGP 索引
<a name="best-practices-general-predicates"></a>

如果您的資料模型包含大量不同的述詞 (在大多數情況下，超過一千個)，您可能會遭遇效能降低，而操作成本提高。

若是這種情況，您可以啟用 [OSGP 索引](feature-overview-storage-indexing.md#feature-overview-storage-indexing-osgp)來改善效能。請參閱 [OSGP 索引](features-lab-mode.md#features-lab-mode-features-osgp-index)。

## 盡可能避免長時間執行的交易
<a name="best-practices-general-long-running-transactions"></a>

長時間執行的交易 (唯讀或讀寫) 可能會造成下列種類的非預期問題：

在讀取器執行個體或具有並行寫入的寫入器執行個體上，長時間執行的交易可能會導致不同版本的資料大量累積。這可能會對讀取查詢引入更高的延遲，從而篩選掉其大部分的結果。

在某些情況下，數小時的累積版本可能會導致新的寫入遭到限流。

如果執行個體重新啟動，具有許多寫入的長時間執行的讀寫交易也可能會造成問題。如果執行個體從維護事件或當機重新啟動，則所有未遞交的寫入都會加以復原。這類復原操作通常會在背景執行，而且不會封鎖執行個體回復，但是任何與要回復之操作衝突的新寫入都會失敗。

例如，如果在上次執行時連線中斷之後重試相同的查詢，則在重新啟動執行個體時可能會失敗。

復原操作所需的時間與涉及的變更大小成正比。

# 使用 Neptune 指標的最佳實務
<a name="best-practices-general-metrics"></a>

若要識別由於資源不足和其他常見瓶頸造成的效能問題，您可以監控 Neptune 資料庫叢集的可用指標。

定期監控效能指標以蒐集各種時間範圍的平均值、最大值和最小值。效能降級時您便可以得知。使用此資料，您可以為特定指標閾值設定 Amazon CloudWatch 警示，讓您可以在到達閾值時收到提醒。

設定新的資料庫叢集並讓它以一般工作負載執行時，嘗試擷取不同間隔數量 (例如，一小時、24 小時、一週、兩週) 所有效能指標的平均值、最大值和最小值。這可讓您了解正常運作情況為何。這有助於比較尖峰與離峰時段的操作。您接著可以使用此資訊來識別效能下降到標準層級之下的時機，並設定警示來應對。

請參閱 [使用 Amazon CloudWatch 監控 Neptune](cloudwatch.md)，以取得如何檢視 Neptune 指標的相關資訊。

以下是您開始的最重要指標：
+ **BufferCacheHitRatio** - 由緩衝區快取提供服務的請求百分比。快取遺漏會將顯著延遲新增至查詢執行。如果快取命中率低於 99.9%，而延遲對應用程式造成問題，請考慮升級執行個體類型，以在記憶體中快取更多資料。
+ **CPU 使用率** - 已使用的電腦處理容量百分比。取決於您的查詢效能目標，較高的 CPU 使用量值有可能是適當的。
+ **可釋放的記憶體** – 資料庫執行個體可用的 RAM (以 MB 為單位)。Neptune 有自己的記憶體管理員，因此這個指標可能低於您預期的。指出您應考慮將執行個體類別升級至擁有較多 RAM 類別的一項良好指標，就是若查詢時常拋出記憶體不足的異常時。

**Monitoring (監控)** 標籤指標的 CPU 及記憶體指標在 75% 會以紅線標記。如果執行個體記憶體的使用量經常超過該線，請檢查您的工作負載，並考慮將您的執行個體升級以改善查詢效能。

## 調校 Neptune 查詢的最佳實務
<a name="best-practices-general-tuning"></a>

 其中一種改善 Neptune 效能的最佳方法，便是調校您最常使用及最資源密集的查詢，降低執行它們的成本。

如需如何調校 Gremlin 查詢的資訊，請參閱 [Gremlin 查詢提示](gremlin-query-hints.md) 和 [調校 Gremlin 查詢](gremlin-traversal-tuning.md)。如需如何調校 SPARQL 查詢的資訊，請參閱 [SPARQL 查詢提示](sparql-query-hints.md)。

## 在各個僅供讀取複本之間平衡負載
<a name="best-practices-general-loadbalance"></a>

讀取器端點循環配置資源路由的運作方式是改變 DNS 項目指向的主機。用戶端必須建立新連線並解析 DNS 記錄，以取得與僅供讀取新複本的連線，因為 WebSocket 連線通常會長時間保持活動狀態。

若要針對連續請求取得不同的僅供讀取複本，請確保用戶端在每次連線時都能解析 DNS 項目。這可能需要關閉連線並重新連接到讀取者端點。

您也可以明確連接到執行個體端點，在各個僅供讀取複本之間對請求進行負載平衡。

## 使用臨時的較大執行個體，載入速度更快
<a name="best-practices-loader-tempinstance"></a>

使用較大的執行個體可提高您的載入效能。如果您不是使用大型的執行個體類型，但您想要更高的載入速度，可以使用較大的執行個體來進行載入，然後將其刪除。

**注意**  
下列程序是用於新的叢集。如果您有現有的叢集，您可以新增較大的執行個體，然後將其提升至主要資料庫執行個體。

**使用較大的執行個體大小將資料載入**

1.  建立具有單一 `r5.12xlarge` 執行個體的叢集。這個執行個體是主要資料庫執行個體。

1. 建立大小 (`r5.12xlarge`) 相同的一個或多個僅供讀取複本。

   您可以建立更小的僅供讀取複本，但如果其大小不足以跟上主執行個體的寫入，則可能必須經常重新啟動。產生的停機時間會大幅降低效能。

1. 在大量載入器命令中，包括 `“parallelism” : “OVERSUBSCRIBE”` 以告訴 Neptune 使用所有可用的 CPU 資源進行載入 (請參閱 [Neptune 載入器請求參數](load-api-reference-load.md#load-api-reference-load-parameters))。然後，載入操作將以 I/O 允許的一樣快速度進行，這通常需要 60-70％ 的 CPU 資源。

1. 使用 Neptune 載入器載入資料。載入工作會在主要資料庫執行個體上執行。

1. 在完成資料載入之後，請務必將叢集中的所有執行個體縮減至相同的執行個體類型，以避免額外的費用和重複的重新啟動問題 (請參閱 [避免不同的執行個體大小](#best-practices-loader-heterogeneous-instances))。

## 容錯移轉至僅供讀取複本來調整您的寫入器執行個體大小
<a name="best-practices-resize-instance"></a>

調整資料庫叢集中執行個體大小 (包括寫入器執行個體) 的最佳方法，就是建立或修改僅供讀取複本執行個體，以便其具有您所需的大小，然後刻意容錯移轉至該僅供讀取複本。您的應用程式看到的停機時間只是變更寫入器 IP 地址所需的時間，應該是 3 到 5 秒左右。

您用來刻意將目前寫入器執行個體容錯移轉至僅供讀取複本執行個體的 Neptune 管理 API 為 [FailoverDBCluster](api-clusters.md#FailoverDBCluster)。如果您是使用 Gremlin Java 用戶端，則可能需要在容錯移轉之後建立新的用戶端物件，以挑選新的 IP 地址，如[這裡](best-practices-gremlin-java-new-connection.md)所述。

確定將所有執行個體變更為相同的大小，讓您避免重複重新啟動的週期，如下所述。

## 在資料預先擷取任務中斷錯誤之後重試上傳
<a name="load-api-reference-status-interrupted"></a>

當您使用大量載入器將資料載入 Neptune 時，結果有時會出現 `LOAD_FAILED` 狀態，而回應中會報告 `PARSING_ERROR` 和 `Data prefetch task interrupted` 訊息以要求詳細資訊，如下所示：

```
"errorLogs" : [
  {
    "errorCode" : "PARSING_ERROR",
    "errorMessage" : "Data prefetch task interrupted: Data prefetch task for 11467 failed",
    "fileName" : "s3://amzn-s3-demo-bucket/some-source-file",
    "recordNum" : 0
  }
]
```

當此錯誤發生時，只要重試一次大量上傳請求。

當出現通常原因不在於您的請求或資料的暫時中斷時，就會出現這個錯誤，而且通常再次執行大量上傳請求就能加以解決。

如果您使用的是預設設定，即 `"mode":"AUTO"` 和 `"failOnError":"TRUE"`，這時載入器會略過其已成功載入的檔案，並恢復其在中斷發生時尚未載入的檔案載入。

# 使用 Gremlin 搭配 Neptune 的一般最佳實務
<a name="best-practices-gremlin"></a>

使用 Gremlin 圖形周遊語言搭配 Neptune 時，請遵循這些建議。如需使用 Gremlin 搭配 Neptune 的詳細資訊，請參閱 [使用 Gremlin 存取 Neptune 圖形](access-graph-gremlin.md)。

**重要**  
已在 TinkerPop 3.4.11 版中進行變更，這可提高查詢處理方式的正確性，但目前有時可能會嚴重影響查詢效能。  
例如，此類查詢的執行速度可能會明顯變慢：  

```
g.V().hasLabel('airport').
  order().
    by(out().count(),desc).
  limit(10).
  out()
```
由於 TinkerPop 3.4.11 變更，限制步驟之後的頂點現在會以非最佳方式擷取。若要避免這種情況，您可以在 `order().by()` 之後的任何點新增 barrier() 步驟來修改查詢。例如：  

```
g.V().hasLabel('airport').
  order().
    by(out().count(),desc).
  limit(10).
  barrier().
  out()
```
TinkerPop 3.4.11 已在 [Neptune 引擎 1.0.5.0 版](engine-releases-1.0.5.0.md)中啟用。

**Topics**
+ [Neptune Serverless 的活動訊號組態](best-practices-gremlin-heartbeat-serverless.md)
+ [建造 upsert 查詢以利用 DFE 引擎](#best-practices-gremlin-upserts)
+ [在您將部署 Girmlin 程式碼的內容中測試該程式碼](best-practices-gremlin-console-glv-differences.md)
+ [建立有效率的多執行緒 Gremlin 寫入](best-practices-gremlin-multithreaded-writes.md)
+ [利用建立時間屬性清除記錄](best-practices-gremlin-prune.md)
+ [對 Groovy 時間資料使用 `datetime( )` 方法](best-practices-gremlin-datetime.md)
+ [在 GLV 時間資料使用原生日期和時間](best-practices-gremlin-datetime-glv.md)

# Neptune Serverless 的活動訊號組態
<a name="best-practices-gremlin-heartbeat-serverless"></a>

搭配 Neptune Serverless 使用 Gremlin WebSocket 用戶端時，您需要適當設定用戶端的 ping 間隔，以在擴展事件期間維持穩定的連線。Gremlin 用戶端使用 WebSocket 連線，並傳送定期 ping 以確認連線作用中。用戶端預期伺服器會在 ping 間隔時間範圍內回應。如果伺服器未回應，用戶端會自動關閉連線。

對於 Neptune **佈建的**執行個體，我們建議將 ping 間隔設定為 **5 秒**。對於 Neptune **Serverless 叢集**，我們建議將 ping 間隔設定為至少 **20 秒**，以適應擴展操作期間的潛在延遲。此參數控制用戶端在寫入伺服器之間等待多久，再傳送 ping 以確認連線仍然作用中。

此參數的組態會根據用戶端實作而有所不同：

**Java 用戶端組態**

針對 Java TinkerPop Gremlin 用戶端，設定 `keepAliveInterval` 參數：

```
Cluster.Builder builder = Cluster.build()
    .addContactPoint(endpoint)
    .keepAliveInterval(20000); // Configure ping interval in milliseconds
```

如需 Java 驅動程式組態的詳細資訊，請參閱 [Java TinkerPop 文件](https://tinkerpop.apache.org/docs/current/reference/#gremlin-java-configuration)。

**Go 用戶端組態**

針對 Gremlin Go 用戶端，設定 `KeepAliveInterval` 參數：

```
rc, err := driver.NewDriverRemoteConnection(endpoint,
    func(settings *driver.DriverRemoteConnectionSettings) {
        settings.TraversalSource = "g"
        settings.AuthInfo = auth
        settings.KeepAliveInterval = 20 * time.Second // Configure ping interval
        ...
    })
```

如需 Go 驅動程式組態的詳細資訊，請參閱 [Go TinkerPop 文件](https://tinkerpop.apache.org/docs/current/reference/#gremlin-go-configuration)。

**JavaScript/Node.js 用戶端組態**

針對 JavaScript/Node.js Gremlin 用戶端，設定 `pingInterval` 參數：

```
const gremlin = require('gremlin');
const DriverRemoteConnection = gremlin.driver.DriverRemoteConnection;

const connection = new DriverRemoteConnection(endpoint, {
    traversalSource: 'g',
    pingInterval: 20000  // Configure ping interval in milliseconds
});
```

如需 JavaScript 驅動程式組態的詳細資訊，請參閱 [JavaScript TinkerPop 文件](https://tinkerpop.apache.org/docs/current/reference/#gremlin-javascript-configuration)。

**Python 用戶端組態**

對於 Python Gremlin 用戶端，ping 間隔通常在傳輸層進行管理。如需組態選項，請參閱特定的傳輸實作文件：

```
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection

g = traversal().with_remote(
    DriverRemoteConnection('wss://your-neptune-endpoint:your-neptune-port/gremlin','g',
        transport_factory=lambda: AiohttpTransport(read_timeout=60,
                                                    write_timeout=20,
                                                    heartbeat=20, // Configure heartbeat
                                                    call_from_event_loop=True,
                                                    max_content_length=100*1024*1024,
                                                    ssl_options=ssl.create_default_context(Purpose.CLIENT_AUTH))))
```

如需 Python 驅動程式組態的詳細資訊，請參閱 [Python TinkerPop 文件](https://tinkerpop.apache.org/docs/current/reference/#gremlin-python-configuration)。

此組態可確保用戶端在 Neptune Serverless 擴展事件期間維持連線穩定性，防止不必要的連線關閉並改善應用程式可靠性。

## 建造 upsert 查詢以利用 DFE 引擎
<a name="best-practices-gremlin-upserts"></a>

[使用 Gremlin `mergeV()` 和 `mergeE()` 步驟進行有效的 upsert](gremlin-efficient-upserts.md) 說明如何建造 upsert 查詢，以盡可能有效地使用 DFE 引擎。

# 在您將部署 Girmlin 程式碼的內容中測試該程式碼
<a name="best-practices-gremlin-console-glv-differences"></a>

在 Gemlin 中，有多種方式可讓用戶端將查詢提交至伺服器：使用 WebSocket 或位元碼 GLV，或透過使用字串型指令碼的 Gemlin 控制台。

務必認識到 Gemlin 查詢執行可能會有所不同，取決於您提交查詢的方式。如果在位元碼模式下提交，則傳回空結果的查詢可能會被視為成功，但如果在指令碼模式下提交，則可能會被視為失敗。例如，如果您在指令碼模式查詢中包含 `next()`，則 `next()` 會傳送至伺服器，但是用戶端通常會使用位元碼來處理 `next()` 本身。在第一種情況下，如果找不到任何結果，查詢就會失敗，但在第二種情況下，無論結果集是否為空，查詢都會成功。

如果您在某個內容中開發並測試您的程式碼 (例如，通常以文字形式提交查詢的 Gemlin 控制台)，但是接著在不同的內容中部署程式碼 (例如，透過使用位元碼的 Java 驅動程式)，則您可能會遇到下列問題：程式碼在生產環境中的行為與在開發環境中的行為不同。

**重要**  
務必在將要部署 Gremlin 程式碼的 GLV 內容中測試該程式碼，以避免發生非預期的結果。

# 建立有效率的多執行緒 Gremlin 寫入
<a name="best-practices-gremlin-multithreaded-writes"></a>

使用 Gremlin 以多執行緒的方式將資料載入 Neptune，有幾項準則。

如有可能，請提供每個執行緒一組頂點或邊緣，以插入或修改不衝突的項目。例如，執行緒 1 的地址 ID 範圍為 1–50,000，執行緒 2 地址 ID 範圍為 50,001–100,000，以此類推。這可減少出現 `ConcurrentModificationException` 的機率。為安全起見，所有寫入請圍以 `try/catch` 區塊。若任何一個項目失敗，您便可以在短暫的延遲後重試它們。

以介於 50 和 100 (頂點或邊緣) 間的批次大小批次寫入，通常效果很好。若您要為每個頂點新增許多屬性，接近 50 的數字可能比接近 100 的數字更好。這很值得進行一些實驗。因此針對批次寫入，您可以使用如下的內容：

```
g.addV(‘test’).property(id,’1’).as(‘a’).
  addV(‘test’).property(id,’2’).
  addE(‘friend’).to(‘a’).
```

接著會在每一個批次操作中重複這段過程。

使用批次的效率，遠比在每一次 Gremlin 與伺服器的來回行程中新增一個頂點或邊緣高。

如果您使用的是 Gremlin 語言變體 (GLV) 用戶端，您可以先建立一個周遊，再以程式設計方式建立一個批次。然後，為它新增內容，最後再對它反覆運算，例如：

```
  t.addV(‘test’).property(id,’1’).as(‘a’)
  t.addV(‘test’).property(id,’2’)
  t.addE(‘friend’).to(‘a’)
  t.iterate()
```

如果可能，最好使用 Gremlin 語言變體用戶端。但是，您可以串連字串來建立批次，用將查詢提交為文字字串的用戶端執行類似的作業。

若您使用其中一個 Gremlin 用戶端程式庫，而非基本的 HTTP 來進行查詢，則執行緒應該全部都會共享相同的用戶端、叢集或連線集區。您可能需要調校設定，來盡可能取得最佳的輸送量，像是 Gremlin 用戶端所使用的連線集區大小及工作者執行緒數量等設定。

# 利用建立時間屬性清除記錄
<a name="best-practices-gremlin-prune"></a>

您可以把建立時間儲存為頂點上的屬性，然後定期丟棄它們，藉此清除過時記錄。

如果需要讓資料存放一段特定時間，然後從圖形中移除 (頂點存留時間)，您可以在建立頂點時儲存時間戳記屬性。然後，您可以定期對所有在特定時間前建立的頂點發出 `drop()` 查詢，例如：

```
g.V().has(“timestamp”, lt(datetime('2018-10-11')))
```

# 對 Groovy 時間資料使用 `datetime( )` 方法
<a name="best-practices-gremlin-datetime"></a>

Neptune 會提供 `datetime` 方法，為 Gremlin **Groovy** 變體中傳送的查詢指定日期和時間。這包括 Gremlin 主控台、使用 HTTP REST API 的文字字串，以及任何其他使用 Groovy 的序列化。

**重要**  
這「只」**適用於以「文字字串」**傳送 Gremlin 查詢的方法。如果使用的是 Gremlin 語言變體，您必須使用該語言的原生日期類別與函數。如需詳細資訊，請參閱下一節「[在 GLV 時間資料使用原生日期和時間](best-practices-gremlin-datetime-glv.md)」。  
從 TinkerPop `3.5.2` (在 [Neptune 引擎 1.1.1.0 版](engine-releases-1.1.1.0.md) 中引入) 開始，`datetime` 是 TinkerPop 不可或缺的一部分。

您可以使用 `datetime` 方法來存放和比較日期：

```
g.V('3').property('date',datetime('2001-02-08'))
```

```
g.V().has('date',gt(datetime('2000-01-01')))
```

# 在 GLV 時間資料使用原生日期和時間
<a name="best-practices-gremlin-datetime-glv"></a>

如果您是使用 Gremlin 語言變體 (GLV)，必須使用該程式語言為 Gremlin 時間資料提供的原生日期和時間類別與函數。

官方 TinkerPop 程式庫都是 Gremlin 語言變體程式庫。
+  [Go](https://tinkerpop.apache.org/docs/current/reference/#gremlin-go) 
+  [Java](https://tinkerpop.apache.org/docs/current/reference/#gremlin-java) 
+  [Javascript](https://tinkerpop.apache.org/docs/current/reference/#gremlin-javascript) 
+  [.NET](https://tinkerpop.apache.org/docs/current/reference/#gremlin-dotnet) 
+  [Python](https://tinkerpop.apache.org/docs/current/reference/#gremlin-python) 

**重要**  
 此頁面僅適用於 Gremlin 語言變體 (GLV) 程式庫。如果您使用將 Gremlin 查詢作為文字字串傳送的方法，則必須使用 Gremlin 的日期時間 () 函數。這包括 Gremlin 主控台、使用 HTTP REST API 的文字字串，或透過驅動程式直接提交 Gremlin 字串。



**Go**  
 以下是 Go 中的部分範例，該範例會為 ID 為 '3' 的頂點建立名為 'date' 的單一屬性。它將值設定為使用 Go time.Now() 函數產生的日期。

```
import ( "time" )

g.V('3').property('date', time.Now()).next();
```

如需使用 Go 連線至 Neptune 的完整範例，請參閱[使用 Go 用戶端連線至 Neptune 資料庫執行個體](https://docs.aws.amazon.com//neptune/latest/userguide/access-graph-gremlin-go.html)。

**Java**  
以下是 Java 範例的一部分，它會為頂點建立一個名為 '`date`' 且 ID 為 '`3`' 的屬性。它將值設為使用 Java `Date()` 建構函數產生的日期。

```
import java.util.date

g.V('3').property('date', new Date()).next();
```

如需使用 Java 連線至 Neptune 的完整範例，請參閱 [使用 Java 連線至 Neptune 資料庫執行個體](access-graph-gremlin-java.md)。

**Node.js (JavaScript)**  
以下是 JavaScript 範例的一部分，它會為頂點建立一個名為 '`date`' 且 ID 為 '`3`' 的屬性。它將值設為使用 Node.js `Date()` 建構函數產生的日期。

```
g.V('3').property('date', new Date()).next()
```

如需使用 Node.js 連線至 Neptune 的完整範例，請參閱 [使用 Node.js 連線至 Neptune 資料庫執行個體](access-graph-gremlin-node-js.md)。

**.NET (C\$1)**  
以下是 C\$1 範例的一部分，它會為頂點建立一個名為 '`date`' 且 ID 為 '`3`' 的屬性。它將值設為使用 .NET `DateTime.UtcNow` 屬性產生的日期。

```
Using System;

g.V('3').property('date', DateTime.UtcNow).next()
```

如需使用 C\$1 連線至 Neptune 的完整範例，請參閱 [使用 .NET 連線至 Neptune 資料庫執行個體](access-graph-gremlin-dotnet.md)。

**Python**  
以下是 Python 範例的一部分，它會為頂點建立一個名為 '`date`' 且 ID 為 '`3`' 的屬性。它將值設為使用 Python `datetime.now()` 方法產生的日期。

```
import datetime

g.V('3').property('date',datetime.datetime.now()).next()
```

如需使用 Python 連線至 Neptune 的完整範例，請參閱 [使用 Python 連線至 Neptune 資料庫執行個體](access-graph-gremlin-python.md)。

# 使用 Gremlin Java 用戶端搭配 Neptune 的最佳實務
<a name="best-practices-gremlin-java-client"></a>

搭配 Neptune 使用 Gremlin Java 用戶端時，請遵循這些建議。這些最佳實務可協助您最佳化效能、有效管理連線，並在使用 Java 驅動程式時避免常見的陷阱。

如需設定 Neptune Serverless 活動訊號間隔的詳細資訊，請參閱 [Neptune Serverless 的活動訊號組態](best-practices-gremlin-heartbeat-serverless.md)。

**Topics**
+ [跨多個執行緒重複使用用戶端物件](best-practices-gremlin-java-reuse.md)
+ [為讀取和寫入端點建立個別的 Gremlin Java 用戶端物件](best-practices-gremlin-java-separate.md)
+ [將多個僅供讀取複本端點新增至 Gremlin Java 連線集區](best-practices-gremlin-java-multiple.md)
+ [關閉用戶端以避免連線限制](best-practices-gremlin-java-close-connections.md)
+ [在容錯移轉之後建立新連線](best-practices-gremlin-java-new-connection.md)
+ [將 `maxInProcessPerConnection` 和 `maxSimultaneousUsagePerConnection` 設定為相同值。](best-practices-gremlin-java-maxes.md)
+ [將查詢以位元碼形式而非字串形式傳送至伺服器](best-practices-gremlin-java-bytecode.md)
+ [一律完全耗用由查詢傳回的 ResultSet 或反覆運算器](best-practices-gremlin-java-resultset.md)
+ [在批次中大量新增頂點和邊緣](best-practices-gremlin-java-batch-add.md)
+ [在 Java 虛擬機器中停用 DNS 快取](best-practices-gremlin-java-disable-dns-caching.md)
+ [或者，也可以在每個查詢層級設定逾時](best-practices-gremlin-java-per-query-timeout.md)
+ [`java.util.concurrent.TimeoutException` 疑難排解](best-practices-gremlin-java-exceptions-TimeoutException.md)

# 跨多個執行緒重複使用用戶端物件
<a name="best-practices-gremlin-java-reuse"></a>

跨多個執行緒重複使用相同的用戶端 (或 `GraphTraversalSource`) 物件。也就是說，在您的應用程式中建立 `org.apache.tinkerpop.gremlin.driver.Client` 類別的共用執行個體，而不是在每個執行緒中建立。`Client` 物件是安全執行緒，初始化執行緒的額外負荷很大。

這也適用於 `GraphTraversalSource`，這會在內部建立 `Client` 物件。例如，以下程式碼會執行個體化新的 `Client` 物件：

```
import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal; 

  /////

GraphTraversalSource traversal = traversal()
                                   .withRemote(DriverRemoteConnection.using(cluster));
```

# 為讀取和寫入端點建立個別的 Gremlin Java 用戶端物件
<a name="best-practices-gremlin-java-separate"></a>

只在寫入器端點執行寫入，以及只從一個或多個唯讀端點讀取，可以提高效能。

```
Client readerClient = Cluster.build("https://reader-endpoint")
          ...
          .connect()

Client writerClient = Cluster.build("https://writer-endpoint")
          ...
          .connect()
```

# 將多個僅供讀取複本端點新增至 Gremlin Java 連線集區
<a name="best-practices-gremlin-java-multiple"></a>

建立 Gremlin Java `Cluster` 物件時，可以使用 `.addContactPoint()` 方法將多個僅供讀取複本執行個體新增到連線集區的接觸點。

```
Cluster.Builder readerBuilder = Cluster.build()
          .port(8182)
          .minConnectionPoolSize(…)
          .maxConnectionPoolSize(…)
          ………
          .addContactPoint("reader-endpoint-1")
          .addContactPoint("reader-endpoint-2")
```

# 關閉用戶端以避免連線限制
<a name="best-practices-gremlin-java-close-connections"></a>

完成後請務必關閉用戶端，以確保伺服器關閉 WebSocket 連線，以及釋放與連線關聯的所有資源。如果您使用 `Cluster.close( )` 關閉叢集，此程序會自動發生，因為會隨後在內部呼叫 `client.close( )`。

如果未正確關閉用戶端，Neptune 會在 20 到 25 分鐘之後終止所有閒置的 WebSocket 連線。不過，如果您在完成 WebSocket 連線時沒有明確地關閉 WebSocket 連線，並且即時連線數達到 [WebSocket 並行連線限制](limits.md#limits-websockets)，則會拒絕其他連線，並出現 HTTP `429` 錯誤碼。此時，您必須重新啟動 Neptune 執行個體才能關閉連線。

呼叫 `cluster.close()` 的建議不適用於 Java AWS Lambda 函數。如需詳細資訊，請參閱 [在 AWS Lambda 函數中管理 Gremlin WebSocket 連線](lambda-functions-websocket-connections.md)。

# 在容錯移轉之後建立新連線
<a name="best-practices-gremlin-java-new-connection"></a>

如果發生容錯移轉，因為叢集 DNS 名稱已解析為 IP 地址，所以 Gremlin 驅動程式可能會繼續連線到舊的寫入器。如果發生這種情況，您可以在容錯移轉後建立新的 `Client` 物件。

# 將 `maxInProcessPerConnection` 和 `maxSimultaneousUsagePerConnection` 設定為相同值。
<a name="best-practices-gremlin-java-maxes"></a>

`maxSimultaneousUsagePerConnection` 和 `maxInProcessPerConnection` 參數都與您可以在單一 WebSocket 連線上提交的同步查詢數目上限相關。在內部，這些參數是相互關聯的，修改一個參數而不修改另一個，可能導致用戶端在嘗試從用戶端連線集區擷取連線時收到逾時。

我們建議保持預設的最小處理中和同時使用量值，並將 `maxInProcessPerConnection` 和 `maxSimultaneousUsagePerConnection` 設定為相同的值。

設定這些參數的值是查詢複雜性和資料模型的函數。在傳回許多資料的查詢使用案例中，每個查詢需要更多連線頻寬，因此，參數應該具有更低的值，而 `maxConnectionPoolSize` 應該具有更高的值。

相反地，如果查詢傳回較少的資料量，則 `maxInProcessPerConnection` 和 `maxSimultaneousUsagePerConnection` 應設定為大於 `maxConnectionPoolSize` 的值。

# 將查詢以位元碼形式而非字串形式傳送至伺服器
<a name="best-practices-gremlin-java-bytecode"></a>

在以下情況提交查詢時，使用位元碼來取代字串具有優勢：
+ **早期擷取無效的查詢語法：**使用位元碼變體可讓您在編譯階段偵測無效的查詢語法。如果您使用字串變體，直到查詢送至伺服器之前都無法發現無效的語法，此時會傳回錯誤。
+ **避免字串效能負面影響：**無論使用 WebSocket 或 HTTP，任何字串查詢提交都會產生分離頂點，這表示 Vertex 物件包含 ID、標籤以及與 Vertex 關聯的所有屬性 (請參閱[元素屬性](http://tinkerpop.apache.org/docs/current/reference/#_properties_of_elements))。

  在不需要屬性的情況下，這可能會導致伺服器上不必要的運算。例如，如果客戶想要使用查詢 `g.V("hakuna#1")` 取得 ID 為「hakuna\$11」的頂點。如果查詢以字串提交傳送出去，伺服器需花時間擷取 ID、標籤以及與此頂點關聯的所有屬性。如果查詢以位元碼提交傳送出去，伺服器只需花費擷取頂點 ID 和標籤的時間。

換言之，與其提交這種查詢：

```
  final Cluster cluster = Cluster.build("localhost")
                                 .port(8182)
                                 .maxInProcessPerConnection(32)
                                 .maxSimultaneousUsagePerConnection(32)
                                 .serializer(Serializers.GRAPHBINARY_V1D0)
                                 .create();

  try {
      final Client client = cluster.connect();
      List<Result> results = client.submit("g.V().has('name','pumba').out('friendOf').id()").all().get();
      System.out.println(verticesWithNamePumba);
  } finally {
      cluster.close();
  }
```

不如使用位元碼提交查詢，如下所示：

```
  final Cluster cluster = Cluster.build("localhost")
                                 .port(8182)
                                 .maxInProcessPerConnection(32)
                                 .maxSimultaneousUsagePerConnection(32)
                                 .serializer(Serializers.GRAPHBINARY_V1D0)
                                 .create();

  try {
      final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
      List<Object> verticesWithNamePumba = g.V().has("name", "pumba").out("friendOf").id().toList();
      System.out.println(verticesWithNamePumba);
  } finally {
      cluster.close();
  }
```

# 一律完全耗用由查詢傳回的 ResultSet 或反覆運算器
<a name="best-practices-gremlin-java-resultset"></a>

用戶端物件應一律完全耗用 `ResultSet` (若是字串提交) 或 `GraphTraversal` 傳回的反覆運算器。如果查詢結果未完全耗用，伺服器保留它們，直到用戶端完成耗用它們為止。

如果您的應用程式只需要一部分結果，您可以使用 `limit(X)` 步驟加上您的查詢，來限制伺服器產生的結果數量。

# 在批次中大量新增頂點和邊緣
<a name="best-practices-gremlin-java-batch-add"></a>

每個對 Neptune 資料庫的查詢都在單一交易的範圍中執行，除非您使用工作階段。這表示如果您需要使用 gremlin 查詢插入許多資料，使用 50-100 的批次大小來同時批次處理，可透過降低建立的負載交易量來改善效能。

舉例來說，新增 5 個頂點到資料庫會如下所示：

```
// Create a GraphTraversalSource for the remote connection
final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
// Add 5 vertices in a single query
g.addV("Person").property(T.id, "P1")
 .addV("Person").property(T.id, "P2")
 .addV("Person").property(T.id, "P3")
 .addV("Person").property(T.id, "P4")
 .addV("Person").property(T.id, "P5").iterate();
```

同樣地，您可以使用 批次新增邊緣`addE`。使用 `V()`參考現有頂點做為每個邊緣的來源和目標：

```
// Add edges in a single batched query
g.V("P1").addE("knows").to(V("P2"))
 .V("P2").addE("knows").to(V("P3"))
 .V("P3").addE("knows").to(V("P4"))
 .V("P4").addE("knows").to(V("P5")).iterate();
```

您也可以在單一批次中結合頂點和邊緣建立。使用 `as()`標記新建立的頂點，以便在相同周遊中新增邊緣時參考它們：

```
// Add vertices and edges together in a single query
g.addV("Person").property(T.id, "P1").as("p1")
 .addV("Person").property(T.id, "P2").as("p2")
 .addV("Person").property(T.id, "P3").as("p3")
 .addE("knows").from("p1").to("p2")
 .addE("knows").from("p2").to("p3").iterate();
```

# 在 Java 虛擬機器中停用 DNS 快取
<a name="best-practices-gremlin-java-disable-dns-caching"></a>

在您想要跨多個僅供讀取複本載入平衡請求的環境中，您需要停用 Java 虛擬機器 (JVM) 中的 DNS 快取，並在建立[叢集物件](https://tinkerpop.apache.org/javadocs/current/core/org/apache/tinkerpop/gremlin/driver/Cluster.html)時提供 Neptune 的讀取器端點。停用 JVM DNS 快取可確保每次新連線都會再次解析 DNS，如此一來，就會將請求分配到所有僅供讀取複本。您可以在應用程式的初始化程式碼中使用下列一行執行此動作：

```
java.security.Security.setProperty("networkaddress.cache.ttl", "0");
```

不過，GitHub 上的 [Amazon Gremlin Java 用戶端程式碼](https://github.com/awslabs/amazon-neptune-tools/tree/master/neptune-gremlin-client)提供了更完整和強大的負載平衡解決方案。Amazon Java Gemlin 用戶端知道您的叢集拓撲，並將連線和請求公平分配到 Neptune 叢集中的一組執行個體。如需使用該用戶端的 Java Lambda 函數範例，請參閱[此部落格文章](https://aws.amazon.com/blogs/database/load-balance-graph-queries-using-the-amazon-neptune-gremlin-client/)。

# 或者，也可以在每個查詢層級設定逾時
<a name="best-practices-gremlin-java-per-query-timeout"></a>

Neptune 可讓您使用參數群組選項 `neptune_query_timeout` 來設定查詢的逾時 (請參閱 [Parameters](parameters.md))。您也可以使用如下所示的程式碼覆寫全域逾時：

```
  final Cluster cluster = Cluster.build("localhost")
                                 .port(8182)
                                 .maxInProcessPerConnection(32)
                                 .maxSimultaneousUsagePerConnection(32)
                                 .serializer(Serializers.GRAPHBINARY_V1D0)
                                 .create();

  try {
      final GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
      List<Object> verticesWithNamePumba = g.with(ARGS_EVAL_TIMEOUT, 500L).V().has("name", "pumba").out("friendOf").id().toList();
      System.out.println(verticesWithNamePumba);
  } finally {
      cluster.close();
  }
```

或者，對於字串查詢提交，程式碼將如下所示：

```
  RequestOptions options = RequestOptions.build().timeout(500).create();
  List<Result> result = client.submit("g.V()", options).all().get();
```

**注意**  
如果您將查詢逾時值設得太高，特別是在無伺服器執行個體上，可能會產生非預期的成本。若沒有合理的逾時設定，您的查詢執行時間可能會比預期的長得多，進而產生您從未預期的成本。這在無伺服器執行個體上尤是如此，因為該執行個體在執行查詢時可能會縱向擴展為大型且昂貴的執行個體類型。  
您可以使用符合您預期之執行階段的查詢逾時值，避免此類非預期的費用，而且只會導致異常的長時間執行逾時。  
 從 Neptune 引擎 1.3.2.0 版開始，Neptune 支援新的 neptune\$1lab\$1mode 參數做為 `StrictTimeoutValidation`。當此參數的值為 時`Enabled`，指定為請求選項或查詢提示的每個查詢逾時值不能超過參數群組中全域設定的值。在這種情況下，Neptune 會擲回 `InvalidParameterException`。  
 當值為 時，可在 '/status' 端點的回應中確認此設定`Disabled`。在引擎版本 中`1.3.2.0`，此參數的預設值為 `Disabled`。從引擎版本 開始`1.4.0.0`， 參數`StrictTimeoutValidation``Enabled`預設為 。  
 如需設定多個逾時設定時如何決定逾時優先順序的詳細資訊，請參閱 [neptune\$1query\$1timeout](parameters.md#parameters-db-cluster-parameters-neptune_query_timeout) 參數文件。

# `java.util.concurrent.TimeoutException` 疑難排解
<a name="best-practices-gremlin-java-exceptions-TimeoutException"></a>

等待其中一個 WebSocket 連線中的插槽變成可用時，若 Gremlin 請求在客戶端本身逾時，則 Gemlin Java 用戶端會擲回 `java.util.concurrent.TimeoutException`。此逾時持續時間是由 `maxWaitForConnection` 用戶端可設定參數控制。

**注意**  
因為在用戶端逾時的請求永遠都不會傳送至伺服器，所以它們不會反映在伺服器中擷取的任何指標中，例如 `GremlinRequestsPerSec`。

這種逾時通常是由下列兩種方式之一引起：
+ **伺服器實際上已達到容量上限。**若是這種情況，則伺服器上的佇列填滿，您可以監控 [MainRequestQueuePendingRequests](cw-metrics.md#cw-metrics-available) CloudWatch 指標來偵測此佇列。伺服器可以處理的平行查詢數目取決於其執行個體大小。

  如果 `MainRequestQueuePendingRequests` 指標未在伺服器上顯示待定請求的累積，則伺服器可以處理更多請求，而且逾時是由用戶端限流造成的。
+ **請求的用戶端限流。**通常可以透過變更用戶端組態設定來修正此問題。

  用戶端可以傳送的平行請求數目上限，可以大致估計如下：

  ```
  maxParallelQueries = maxConnectionPoolSize * Max( maxSimultaneousUsagePerConnection, maxInProcessPerConnection )
  ```

  傳送至用戶端的數目若超過 `maxParallelQueries`，會導致 `java.util.concurrent.TimeoutException` 例外狀況。您通常可採取幾種方式來解決此問題：
  + *增加連線逾時持續時間。*如果延遲對您的應用程式而言並不重要，請增加用戶端的 `maxWaitForConnection` 設定。然後，用戶端會等待更長的時間才逾時，因而增加延遲。
  + *增加每個連線的請求數上限。*這允許使用相同的 WebSocket 連線傳送更多的請求。透過增加用戶端的 `maxSimultaneousUsagePerConnection` 和 `maxInProcessPerConnection` 設定來執行此動作。這些設定通常應該具有相同的值。
  + *增加連線集區中的連線數目。*透過增加用戶端的 `maxConnectionPoolSize` 設定來執行此動作。成本是增加資源耗用量，因為每個連線都會使用記憶體和作業系統檔案描述項，並且在初始化期間需要 SSL 和 WebSocket 交握。

# 使用 openCypher 和 Bolt 的 Neptune 最佳實務
<a name="best-practices-opencypher"></a>

搭配 Neptune 使用 openCypher 查詢語言和 Bolt 通訊協定時，請遵循這些最佳實務。如需在 Neptune 使用 openCypher 的相關資訊，請參閱 [使用 openCypher 存取 Neptune 圖形](access-graph-opencypher.md)。

**Topics**
+ [在容錯移轉之後建立新連線](#best-practices-opencypher-renew-connection)
+ [適用於長期命應用的連線處理](#best-practices-opencypher-long-connections)
+ [的連線處理 AWS Lambda](#best-practices-opencypher-lambda-connections)
+ [偏好查詢中的定向至雙向邊緣](best-practices-opencypher-directed-edges.md)
+ [Neptune 不支援交易中的多個並行查詢](best-practices-opencypher-multiple-queries.md)
+ [完成後關閉驅動程式物件](best-practices-opencypher-close-driver.md)
+ [使用明確的交易模式進行讀取和寫入](best-practices-opencypher-use-explicit-txs.md)
+ [例外狀況的重試邏輯](best-practices-opencypher-retry-logic.md)
+ [使用單一 SET 子句一次設定多個屬性](best-practices-content-0.md)
+ [使用參數化查詢](best-practices-content-2.md)
+ [在 UNWIND 子句中使用平面貼圖而非巢狀貼圖](best-practices-content-3.md)
+ [在可變長度路徑 (VLP) 表達式中，將更嚴格的節點放在左側](best-practices-content-4.md)
+ [使用精細的關係名稱避免備援節點標籤檢查](best-practices-content-5.md)
+ [盡可能指定邊緣標籤](best-practices-content-6.md)
+ [盡可能避免使用 WITH 子句](best-practices-content-7.md)
+ [儘早在查詢中放置限制性篩選條件](best-practices-content-8.md)
+ [明確檢查屬性是否存在](best-practices-content-9.md)
+ [請勿使用具名路徑 （除非必要）](best-practices-content-10.md)
+ [避免 COLLECT(DISTINCT())](best-practices-content-11.md)
+ [擷取所有屬性值時，偏好個別屬性查詢的屬性函數](best-practices-content-12.md)
+ [在查詢之外執行靜態運算](best-practices-content-13.md)
+ [使用 UNWIND 而非個別陳述式的批次輸入](best-practices-content-14.md)
+ [偏好使用節點/關係IDs](best-practices-content-15.md)
+ [避免在查詢中進行 \$1id 運算](best-practices-content-16.md)
+ [更新/合併多個節點](best-practices-merge-multiple-nodes.md)

## 在容錯移轉之後建立新連線
<a name="best-practices-opencypher-renew-connection"></a>

若發生容錯移轉，Bolt 驅動程式可以繼續連線至舊的寫入器執行個體，而不是新的作用中執行個體，因為 DNS 名稱已解析為特定 IP 地址。

若要避免這種情況發生，請在任何容錯移轉之後關閉 `Driver` 物件，然後重新連線該物件。

## 適用於長期命應用的連線處理
<a name="best-practices-opencypher-long-connections"></a>

建置長期應用程式 (例如在容器內或在 Amazon EC2 執行個體上執行的應用程式) 時，請將 `Driver` 物件執行個體化一次，然後在應用程式的生命週期內重複使用該物件。`Driver` 物件是安全執行緒，初始化執行緒的額外負荷很大。

## 的連線處理 AWS Lambda
<a name="best-practices-opencypher-lambda-connections"></a>

由於 Bolt 驅動程式的連線額外負荷和管理需求，因此不建議在 AWS Lambda 函數內使用。請改用 [HTTPS 端點](access-graph-opencypher-queries.md)。

# 偏好查詢中的定向至雙向邊緣
<a name="best-practices-opencypher-directed-edges"></a>

Neptune 執行查詢最佳化時，雙向邊緣會使建立最佳化查詢計劃變得困難。次佳計劃要求引擎執行不必要的工作並導致更差的效能。

因此，盡可能使用定向邊緣而不是雙向邊緣。例如，使用：

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

而不是：

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

大多數資料模型實際上不需要周遊兩個方向的邊緣，因此查詢可以透過切換到使用定向邊緣來達到大幅的效能改進。

如果您的資料模型確實需要周遊雙向邊緣，則使 `MATCH` 模式中的第一個節點 (左側) 成為篩選最嚴格的節點。

舉個例子，「為我找到所有往返 `ANC` 機場的 `routes`」。編寫此查詢，從 `ANC` 機場開始，如下所示：

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

引擎可以執行最少的工作量來滿足查詢，因為受到最多限制的節點會放置在模式中作為第一個節點 (左側)。然後，引擎可以最佳化查詢。

這比在模式結束時篩選 `ANC` 機場更好，如下所示：

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

當受到最多限制的節點未放在模式中的第一位時，引擎必須執行額外的工作，因為它無法最佳化查詢，而且必須執行其他查詢才能得出結果。

# Neptune 不支援交易中的多個並行查詢
<a name="best-practices-opencypher-multiple-queries"></a>

雖然 Bolt 驅動程式本身允許交易中的並行查詢，但 Neptune 不支援交易中的多個查詢同時執行。相反，Neptune 會要求交易中的多個查詢循序執行，並且在啟動下一個查詢之前，完全耗用每個查詢的結果。

以下範例說明如何使用 Bolt 在交易中循序執行多個查詢，以便在下一個查詢開始之前完全耗用每個查詢的結果：

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

# 完成後關閉驅動程式物件
<a name="best-practices-opencypher-close-driver"></a>

完成後請務必關閉用戶端，以便伺服器關閉 Bolt 連線，以及釋放與連線相關聯的所有資源。如果您使用 `driver.close()` 關閉驅動程式，則此情況會自動發生。

如果驅動程式未正確關閉，Neptune 會在 20 分鐘後終止所有閒置的 Bolt 連線，或者如果您使用 IAM 驗證，則會在 10 天後終止。

Neptune 支援不超過 1000 個並行 Bolt 連線。如果您在完成後未明確關閉連線，且即時連線數目達到 1000 限制，則任何新的連線嘗試都會失敗。

# 使用明確的交易模式進行讀取和寫入
<a name="best-practices-opencypher-use-explicit-txs"></a>

使用交易搭配 Neptune 和 Bolt 驅動程式時，最好將讀取和寫入交易的存取模式明確設定為正確的設定。

## 唯讀交易
<a name="best-practices-opencypher-read-txs"></a>

對於唯讀交易，如果您在建置工作階段時未傳入適當的存取模式組態，則會使用預設隔離層級，也就是變動查詢隔離。因此，對於唯讀交易來說，將存取模式明確設定為 `read` 很重要。

**自動遞交讀取交易範例：**

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

**讀取交易範例：**

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

在這兩種情況下，[`SNAPSHOT` 隔離](transactions-isolation-levels.md)都是使用 [Neptune 唯讀交易語義](transactions-neptune.md#transactions-neptune-read-only)來達成的。

因為僅供讀取複本只接受唯讀查詢，所以提交至僅供讀取複本的任何查詢都會在 `SNAPSHOT` 隔離語義下執行。

唯讀交易沒有已變更讀取或不可重複讀取。

## 變動交易
<a name="best-practices-opencypher-mutation-txs"></a>

對於變動查詢，有三種不同的機制來建立寫入交易，每個方法描述如下：

**隱含寫入交易範例：**

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

**自動遞交寫入交易範例：**

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

**明確寫入交易範例：**

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

**寫入交易的隔離層級**
+ 作為變動查詢一部分進行的讀取會在 `READ COMMITTED` 交易隔離下執行。
+ 作為變動查詢一部分進行的讀取沒有任何已讀取讀取。
+ 在變動查詢中讀取時，會鎖定記錄和記錄範圍。
+ 當變動交易讀取某個索引範圍時，強力保證在讀取結束之前，任何並行交易都不會修改此範圍。

變動查詢不是執行緒安全的。

如需衝突，請參閱 [使用鎖定等待逾時的衝突解決機制](transactions-neptune.md#transactions-neptune-conflicts)。

變動查詢不會在失敗的情況下自動重試。

# 例外狀況的重試邏輯
<a name="best-practices-opencypher-retry-logic"></a>

對於允許重試的所有例外狀況，通常最好使用[指數退避和重試策略](https://docs.aws.amazon.com/general/latest/gr/api-retries.html)，該策略會在重試之間提供逐漸加長的等待時間，以便更好地處理暫時性問題，例如 `ConcurrentModificationException` 錯誤。下面顯示指數退避和重試模式的範例：

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

# 使用單一 SET 子句一次設定多個屬性
<a name="best-practices-content-0"></a>

 使用映射一次為實體設定多個屬性，而不是使用多個 SET 子句來設定個別屬性。

 您可以使用：

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

 而不是：

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

 SET 子句接受單一屬性或映射。如果更新單一實體上的多個屬性，使用具有映射的單一 SET 子句允許在單一操作中執行更新，而不是以更有效率的方式執行多個操作。

## 使用 SET 子句一次移除多個屬性
<a name="best-practices-content-1"></a>

 使用 openCypher 語言時，REMOVE 用於從實體移除屬性。在 Neptune 中，每個要移除的屬性都需要個別的操作，以新增查詢延遲。您可以改為使用 SET 搭配映射，將所有屬性值設定為 `null`，其在 Neptune 中等同於移除屬性。需要移除單一實體上的多個屬性時，Neptune 將提高效能。

使用：

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

而不是：

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

# 使用參數化查詢
<a name="best-practices-content-2"></a>

 建議在使用 openCypher 查詢時一律使用參數化查詢。查詢引擎可以針對查詢計畫快取等功能利用重複參數化查詢，其中使用不同參數重複調用相同的參數化結構可以利用快取計畫。只有在參數化查詢在 100 毫秒內完成且參數類型為 NUMBER、BOOLEAN 或 STRING 時，才會快取和重複使用針對參數化查詢產生的查詢計劃。

使用：

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

使用參數：

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

而不是：

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

# 在 UNWIND 子句中使用平面貼圖而非巢狀貼圖
<a name="best-practices-content-3"></a>

 深度巢狀結構可以限制查詢引擎產生最佳查詢計畫的能力。為了部分緩解此問題，下列定義的模式會針對下列案例建立最佳計劃：
+  案例 1：UNWIND 與 cypher 常值清單，其中包括 NUMBER、STRING 和 BOOLEAN。
+  案例 2：UNWIND 搭配平面貼圖清單，其中僅包含做為值的字元常值 (NUMBER、STRING、BOOLEAN)。

 撰寫包含 UNWIND 子句的查詢時，請使用上述建議來改善效能。

案例 1 範例：

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

使用參數：

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

 案例 2 的範例是為 CREATE 或 MERGE 產生節點清單。與其發出多個陳述式，請使用下列模式將屬性定義為一組平面貼圖：

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

使用參數：

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

而不是巢狀節點物件，例如：

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

使用參數：

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

# 在可變長度路徑 (VLP) 表達式中，將更嚴格的節點放在左側
<a name="best-practices-content-4"></a>

 在可變長度路徑 (VLP) 查詢中，查詢引擎會透過選擇在表達式的左側或右側啟動周遊來最佳化評估。決策是根據左側和右側模式的基數。基數是符合指定模式的節點數量。
+  如果右側模式的基數為 1，則右側將是起點。
+  如果左側和右側的基數為 1，則會檢查兩側的擴展，並以較小的擴展從側面開始。擴展是 VLP 表達式左側節點和右側節點的傳出或傳入邊緣數量。只有在 VLP 關係為單向且提供關係類型時，才會使用最佳化的此部分。
+  否則，左側將是起點。

 對於 VLP 表達式鏈，此最佳化只能套用至第一個表達式。從左側開始評估其他 VLPs。例如，讓 (a)、(b) 的基數為 1，而 (c) 的基數大於 1。
+  `(a)-[*1..]->(c)`：評估開頭為 (a)。
+  `(c)-[*1..]->(a)`：評估開頭為 (a)。
+  `(a)-[*1..]-(c)`：評估開頭為 (a)。
+  `(c)-[*1..]-(a)`：評估開頭為 (a)。

 現在讓 (a) 的傳入邊緣為兩個，(a) 的傳出邊緣為三個，(b) 的傳入邊緣為四個，(b) 的傳出邊緣為五個。
+  `(a)-[*1..]->(b)`：評估以 (a) 開頭，因為 (a) 的傳出邊緣小於 (b) 的傳入邊緣。
+  `(a)<-[*1..]-(b)`：評估以 (a) 開頭，因為 (a) 的傳入邊緣小於 (b) 的傳出邊緣。

 一般而言，請將更嚴格的模式放在 VLP 表達式的左側。

# 使用精細的關係名稱避免備援節點標籤檢查
<a name="best-practices-content-5"></a>

 最佳化效能時，使用節點模式獨有的關係標籤可移除節點上的標籤篩選。考慮圖形模型，其中關係`likes`僅用於定義兩個`person`節點之間的關係。我們可以撰寫下列查詢來尋找此模式：

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

 n 和 m 上的`person`標籤檢查是多餘的，因為我們定義了與 的關係只會在兩者都屬於類型 時出現`person`。若要最佳化效能，我們可以撰寫查詢，如下所示：

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

 當屬性專屬於單一節點標籤時，此模式也可以套用。假設只有`person`節點具有 屬性 `email`，因此驗證節點標籤相符項目`person`是多餘的。將此查詢寫入為：

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

 效率低於將此查詢寫入為：

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

 只有在效能很重要，而且您已在建模程序中進行檢查，以確保這些邊緣標籤不會重複使用於涉及其他節點標籤的模式時，才應該採用此模式。如果您稍後在其他節點標籤上引入 `email` 屬性，例如 `company`，則這兩個版本的查詢結果會有所不同。

# 盡可能指定邊緣標籤
<a name="best-practices-content-6"></a>

 在模式中指定邊緣時，建議盡可能提供邊緣標籤。請考慮下列範例查詢，此查詢用於連結住在城市的所有人員與造訪該城市的所有人員。

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

 如果您的圖形模型使用多個邊緣標籤將人員連結至城市以外的節點，則透過不指定最終標籤，Neptune 將需要評估稍後將捨棄的其他路徑。在上述查詢中，由於未提供邊緣標籤，因此引擎會先執行更多工作，然後篩選掉值以取得正確的結果。上述查詢的較好版本可能是：

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

 這不僅有助於評估，還可以讓查詢規劃器建立更好的計劃。您甚至可以結合此最佳實務與備援節點標籤檢查，以移除城市標籤檢查，並將查詢寫入為：

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

# 盡可能避免使用 WITH 子句
<a name="best-practices-content-7"></a>

 openCypher 中的 WITH 子句充當執行所有項目前的邊界，然後將產生的值傳遞至查詢的剩餘部分。當您需要臨時彙總或想要限制結果數量時，除了應該避免使用 WITH 子句之外，還需要 WITH 子句。一般指引是移除這些簡單的 WITH 子句 （不彙總、排序或限制），讓查詢規劃器能夠處理整個查詢，以建立全域最佳計劃。例如，假設您撰寫查詢來傳回所有住在 的人`India`：

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

 在上述版本中， WITH 子句會限制在 之前放置模式 `(city)-[:part_of]->(country {name: 'India'})`（較嚴格）`(person)-[:lives_in]->(city)`。這會使計劃次佳。此查詢的最佳化是移除 WITH 子句，並讓規劃器運算最佳計劃。

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

# 儘早在查詢中放置限制性篩選條件
<a name="best-practices-content-8"></a>

 在所有案例中，在查詢中提早放置篩選條件有助於減少查詢計劃必須考慮的中繼解決方案。這表示執行查詢所需的記憶體和運算資源較少。

 下列範例可協助您了解這些影響。假設您撰寫查詢來傳回所有住在 的人員`India`。查詢的一個版本可以是：

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

 上述版本的查詢不是實現此使用案例的最佳方式。篩選條件稍後`country.name = 'India'`會出現在查詢模式中。它會先收集所有人員及其居住地，並依國家/地區分組，然後僅篩選 的 群組`country.name = India`。僅查詢居住者`India`，然後執行收集彙總的最佳方式。

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

 一般規則是在引入變數後盡快放置篩選條件。

# 明確檢查屬性是否存在
<a name="best-practices-content-9"></a>

 根據 openCypher 語意，當存取屬性時，它等同於選用聯結，即使屬性不存在，也必須保留所有資料列。如果您根據圖形結構描述知道該實體一律存在特定屬性，請明確檢查該屬性是否存在可讓查詢引擎建立最佳計劃並改善效能。

 請考慮圖形模型，其中 類型的節點`person`一律具有屬性 `name`。而不是這樣做：

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

 使用 IS NOT NULL 檢查明確驗證查詢中存在的屬性：

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

# 請勿使用具名路徑 （除非必要）
<a name="best-practices-content-10"></a>

 查詢中的具名路徑一律需支付額外費用，這可能會增加延遲和記憶體用量方面的懲罰。請考處下列查詢：

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

 在上述查詢中，假設我們只想知道節點的屬性，則不需要使用路徑「p」。透過將具名路徑指定為變數，使用 DISTINCT 的彙總操作在時間和記憶體用量方面都會變得昂貴。上述查詢的更最佳化版本可能是：

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

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

**注意**  
從引擎 [ 1.4.7.0](engine-releases-1.4.7.0.md) 版開始，不再需要此建議的重寫。

 每當要形成包含不同值的清單時，都會使用 COLLECT(DISTINCT())。COLLECT 是一種彙總函數，分組是根據在相同陳述式中投影的其他金鑰來完成。使用差異時，輸入會分割成多個區塊，其中每個區塊代表一個群組進行減少。隨著群組數量的增加，效能將會受到影響。在 Neptune 中，在實際收集/形成清單之前執行 DISTINCT 會更有效率。這允許對整個區塊的分組索引鍵直接進行分組。

 請考處下列查詢：

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

 撰寫此查詢的另一種最佳方式是：

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

# 擷取所有屬性值時，偏好個別屬性查詢的屬性函數
<a name="best-practices-content-12"></a>

 `properties()` 函數用於傳回包含實體所有屬性的映射，而且比個別傳回屬性更有效率。

 假設您的`Person`節點包含 5 個屬性：`firstName`、`dept`、、 `lastName` `age`和 `company`，則最好使用下列查詢：

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

 而不是使用：

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

# 在查詢之外執行靜態運算
<a name="best-practices-content-13"></a>

 建議在用戶端解析靜態運算 （簡單的數學/字串操作）。請考慮此範例，其中您想要尋找比作者長一年或更短的所有人：

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

 在這裡， 會透過參數`$age`注入查詢，然後新增至固定值。然後，將此值與 進行比較`p.age`。反之，更好的方法是在用戶端進行新增，並將計算值作為參數 \$1ageplusone 傳遞。這有助於查詢引擎建立最佳化計劃，並避免每個傳入資料列的靜態運算。遵循這些準則，更有效率的查詢會是：

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

# 使用 UNWIND 而非個別陳述式的批次輸入
<a name="best-practices-content-14"></a>

 每當需要對不同的輸入執行相同的查詢時，而不是對每個輸入執行一個查詢，對一批輸入執行查詢會更高效能。

 如果您想要在一組節點上合併，其中一個選項是執行每個輸入的合併查詢：

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

 使用參數：

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

 每個輸入都需要執行上述查詢。雖然此方法有效，但可能需要對大量輸入執行許多查詢。在此案例中，批次處理可能有助於減少在伺服器上執行的查詢數量，並改善整體輸送量。

 使用下列模式：

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

 使用參數：

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

 建議使用不同的批次大小進行實驗，以判斷哪些項目最適合您的工作負載。

# 偏好使用節點/關係IDs
<a name="best-practices-content-15"></a>

 Neptune 允許使用者在節點和關係上明確指派 IDs。ID 在資料集中必須是全域唯一的，並且確定性才有用。決定性 ID 可以用作查詢或篩選機制，就像屬性一樣；但是，使用 ID 從查詢執行角度比使用屬性更最佳化。使用自訂 IDs 有幾個好處 - 
+  現有實體的屬性可以是 null，但 ID 必須存在。這可讓查詢引擎在執行期間使用最佳化聯結。
+  執行並行變動查詢時，當 IDs用於存取節點時，並[行修改例外](https://docs.aws.amazon.com//neptune/latest/userguide/transactions-exceptions.html)狀況 (CMEs) 的機率會大幅降低，因為 IDs 的鎖定數量會比屬性來得少，因為其強制執行的唯一性。
+  使用 IDs 可避免建立重複資料的機會，因為 Neptune 會在 IDs上強制執行唯一性，與屬性不同。

 下列查詢範例使用自訂 ID：

**注意**  
 屬性`~id`用於指定 ID，而 `id`僅儲存為任何其他屬性。

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

 不使用自訂 ID：

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

 如果使用後者機制，則沒有唯一性強制執行，您可以稍後執行查詢：

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

 這會建立`id=1`名為 的第二個節點`john`。在此案例中，您現在將有兩個具有 的節點`id=1`，每個節點都有不同的名稱 - (alice 和 john)。

# 避免在查詢中進行 \$1id 運算
<a name="best-practices-content-16"></a>

 在查詢中使用自訂 IDs 時，請一律在查詢之外執行靜態運算，並在參數中提供這些值。提供靜態值時，引擎更能夠最佳化查詢，並避免掃描和篩選這些值。

 如果您想要在資料庫中現有的節點之間建立邊緣，其中一個選項可能是：

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

 使用參數：

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

 在上述查詢中，正在查詢中計算 區段`id`的 。由於運算是動態的，因此引擎無法靜態內嵌 ID，最終會掃描所有區段節點。然後，引擎會針對所需的節點執行後篩選。如果資料庫中有許多區段節點，這可能會很昂貴。

 達成此目的更好的方法是在要傳遞至資料庫的 ID 前面`Sec-`加上 ：

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

 使用參數：

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

# 更新/合併多個節點
<a name="best-practices-merge-multiple-nodes"></a>

 在多個節點上執行 `MERGE`或 `CREATE`查詢時，建議將 與單一 MERGE/CREATE 子句`UNWIND`搭配使用，而不是對每個節點使用 MERGE/CREATE 子句。由於每行需要最佳化，針對一個節點使用一個子句的查詢會導致執行計畫效率低下。這會導致查詢的大部分執行時間花費在靜態處理，而不是實際更新。

 每個節點有一個子句不是最佳的，因為它不會隨著節點數量的增加而擴展：

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

 `UNWIND` 搭配一個 MERGE/CREATE 子句使用 可允許相同行為，但執行計畫更理想。考慮到這一點，變更後的查詢會如下所示：

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

# 使用 SPARQL 的 Neptune 最佳實務
<a name="best-practices-sparql"></a>

搭配 Neptune 使用 SPARQL 查詢語言時，請遵循這些最佳實務。如需在 Neptune 中使用 SPARQL 的相關資訊，請參閱 [使用 SPARQL 存取 Neptune 圖形](access-graph-sparql.md)。

**Topics**
+ [預設查詢所有具名圖形](best-practices-sparql-query.md)
+ [指定要載入的具名圖表](best-practices-sparql-graph.md)
+ [在您的查詢中選擇 FILTER、FILTER...IN 和 VALUES](best-practices-sparql-batch.md)

# 預設查詢所有具名圖形
<a name="best-practices-sparql-query"></a>

Amazon Neptune 會將每個三元組與一個具名圖形建立關聯。預設圖形是定義成所有具名圖形的聯集。

若您提交 SPARQL 查詢而未透過 `GRAPH` 關鍵字或 `FROM NAMED` 之類的建構式明確指定圖形，Neptune 一律會考慮資料庫執行個體中的所有三元組。例如，以下查詢將從 Neptune SPARQL 端點傳回所有三元組：

```
SELECT * WHERE { ?s ?p ?o }
```

顯現於多個圖形中的三元組將僅傳回一次。

如需預設圖形規格的相關資訊，請參閱 SPARQL 1.1 查詢語言規格的 [RDF 資料集](https://www.w3.org/TR/sparql11-query/#rdfDataset)一節。

# 指定要載入的具名圖表
<a name="best-practices-sparql-graph"></a>

Amazon Neptune 會將每個三元組與一個具名圖形建立關聯。如果您在載入、插入或更新三元組時未指定具名圖形，Neptune 會使用由 URI 定義的備用具名圖形 (`http://aws.amazon.com/neptune/vocab/v01/DefaultNamedGraph`)。

如果您是使用 Neptune 大量載入器，則可以使用 `parserConfiguration: namedGraphUri` 參數，指定要用於所有三元組 (或第四位置空白的四元組) 的具名圖形。如需 Neptune 載入器 `Load` 命令語法的相關資訊，請參閱 [Neptune 載入器命令](load-api-reference-load.md)。

# 在您的查詢中選擇 FILTER、FILTER...IN 和 VALUES
<a name="best-practices-sparql-batch"></a>

有三種基本方式可在 SPARQL 查詢中注入值：`FILTER`、`FILTER...IN` 和 `VALUES`。

例如，假設您想要在單次查詢中，查詢多名人員的好友。使用 `FILTER`，您可以建構如下查詢：

```
  PREFIX ex: <https://www.example.com/>
  PREFIX foaf : <http://xmlns.com/foaf/0.1/>

  SELECT ?s ?o
  WHERE {?s foaf:knows ?o. FILTER (?s = ex:person1 || ?s = ex:person2)}
```

這會傳回將 `?s` 繫結至 `ex:person1` 或 `ex:person2`，且外寄節點標示為 `foaf:knows` 之圖形中的所有三元組。

您也可以使用傳回同等結果的 `FILTER...IN` 建立查詢：

```
  PREFIX ex: <https://www.example.com/>
  PREFIX foaf : <http://xmlns.com/foaf/0.1/>

  SELECT ?s ?o
  WHERE {?s foaf:knows ?o. FILTER (?s IN (ex:person1, ex:person2))}
```

您也可以使用在這種情況下也會傳回同等結果的 `VALUES` 建立查詢：

```
  PREFIX ex: <https://www.example.com/>
  PREFIX foaf : <http://xmlns.com/foaf/0.1/>

  SELECT ?s ?o
  WHERE {?s foaf:knows ?o. VALUES ?s {ex:person1 ex:person2}}
```

雖然在許多情況下，這些查詢在語意上是相等的，但在某些情況下，兩個 `FILTER` 變體和 `VALUES` 變體不同：
+ 第一種情況是當您注入重複值時，例如注入相同的人員兩次。在這種情況下，`VALUES` 查詢會在結果中包含重複項目。您可以將 `DISTINCT` 新增至 `SELECT` 子句，明確消除這類重複。但有時候，您可能真的需要在查詢結果中出現重複項目，以注入多餘值。

  不過，當相同的值出現多次時，`FILTER` 和 `FILTER...IN` 版本只擷取一次值。
+ 第二種情況與 `VALUES` 一律執行完全符合，而 `FILTER` 可能會套用類型提升，並在某些情況下執行模糊映射有關。

  例如，當您在值子句中包含 `"2.0"^^xsd:float` 等常值時，`VALUES` 查詢會尋找與此常值完全符合的項目，包括常值的值和資料類型。

  相反地，`FILTER` 則會產生與這些數值常值模糊符合的項目。符合項目可能包括值相同、但數值資料類型不同的常值，例如 `xsd:double`。
**注意**  
列舉字串常值或 URI 時，`FILTER` 和 `VALUES` 的行為間無差異。

`FILTER` 和 `VALUES` 之間的差異會影響最佳化和產生的查詢評估策略。除非您的使用案例需要模糊相符，否則建議您使用 `VALUES`，因為它會避免查看有關類型轉換的特殊案例。因此，`VALUES` 通常會產生更有效率的查詢，執行速度更快但價格低廉。