

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 모범 사례: 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)
+ [

# Neptune으로 Gremlin을 사용하기 위한 일반 모범 사례
](best-practices-gremlin.md)
  + [

# Neptune Serverless에 대한 하트비트 구성
](best-practices-gremlin-heartbeat-serverless.md)
  + [

## DFE 엔진을 활용하기 위한 구조 업서트 쿼리
](best-practices-gremlin.md#best-practices-gremlin-upserts)
  + [

# Gremlin 코드를 배포할 컨텍스트에서 테스트하세요.
](best-practices-gremlin-console-glv-differences.md)
  + [

# 효율적인 멀티스레드 Gremlin 쓰기 생성
](best-practices-gremlin-multithreaded-writes.md)
  + [

# 생성 시간 속성으로 레코드 정리
](best-practices-gremlin-prune.md)
  + [

# `datetime( )` Groovy 시간 데이터 메서드 사용
](best-practices-gremlin-datetime.md)
  + [

# GLV 시간 데이터에 기본 날짜 및 시간 사용
](best-practices-gremlin-datetime-glv.md)
+ [

# Neptune으로 Gremlin Java 클라이언트를 사용한 모범 사례
](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 또는 Iterator를 완전히 사용
](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)
  + [

# 노드/관계에 사용자 정의 ID 사용 선호
](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 DB 인스턴스를 이해해야 합니다. [Amazon Neptune DB 클러스터 및 인스턴스](feature-overview-db-clusters.md)을(를) 참조하세요.
+ CPU 및 메모리 사용을 모니터링합니다. 이를 통해 필요한 쿼리 성능을 얻기 위해 CPU 또는 메모리 용량이 더 큰 DB 인스턴스 클래스로 마이그레이션할 때를 알 수 있습니다. Amazon CloudWatch에서 사용 패턴이 변경되거나 사용자가 배포 용량에 도달했을 때 알림을 받도록 설정할 수 있으므로 시스템 성능 및 가용성을 유지하는 데 도움이 될 수 있습니다. 자세한 내용은 [인스턴스 모니터링](feature-overview-db-clusters.md#feature-overview-monitoring-instances) 및 [Neptune 모니터링](monitoring.md) 단원을 참조하십시오.

  Neptune에는 자체 메모리 관리자가 있기 때문에 CPU 사용량이 많을 때도 메모리 사용량은 비교적 적게 보이는 것이 정상입니다. 쿼리 실행 중 메모리 부족 예외가 발생하는 것은 사용 가능 메모리를 늘려야 한다는 가장 확실한 신호입니다.
+ 자동 백업을 활성화하고 백업 기간을 편리한 시간으로 설정하십시오.
+ DB 인스턴스의 장애 조치를 테스트하여 사용 사례 절차에 소요되는 시간을 파악하십시오. 이를 통해 DB 인스턴스에 액세스하는 애플리케이션이 장애 조치 후 자동으로 새 DB 인스턴스에 연결할 수 있도록 할 수 있습니다.
+ VPC 피어링과의 리전 간 연결로 인해 쿼리 응답 시간이 지연될 수 있으므로 가능하면 동일한 리전과 VPC에서 클라이언트와 Neptune 클러스터를 실행하십시오. 한 자릿수 밀리초 쿼리 응답의 경우 클라이언트와 Neptune 클러스터를 동일한 리전과 VPC에 유지해야 합니다.
+ 읽기 전용 복제본 인스턴스를 생성할 때 크기는 최소한 기본 라이터 인스턴스와 같아야 합니다. 그래야 복제 지연 여부를 확인하고 복제본이 다시 시작되는 것을 방지할 수 있습니다. [한 클러스터에서 상이한 인스턴스 클래스 방지](#best-practices-loader-heterogeneous-instances)을(를) 참조하세요.
+ 새 주요 엔진 버전으로 업그레이드하기 전에 해당 버전에서 애플리케이션을 테스트해야 합니다. 이를 위해서는 DB 클러스터를 복제하여 클론 클러스터가 새 엔진 버전을 실행하도록 한 다음, 클론에서 애플리케이션을 테스트하면 됩니다.
+ 장애 조치를 용이하게 하기 위해 모든 인스턴스의 크기가 같은 것이 좋습니다.

**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 작업에 대한 액세스를 제어합니다. DB 인스턴스, 보안 그룹, 옵션 그룹 또는 파라미터 그룹 같은 Neptune 리소스를 생성, 수정 또는 삭제하는 작업 및 DB 인스턴스 백업 및 복원 등의 일반적인 관리 작업을 수행하는 작업을 제어합니다.
+ 가능하면 영구 자격 증명 대신 임시 보안 인증 정보를 사용하세요.
+ Amazon Relational Database Service(RDS) 리소스를 관리하는 각 사용자에게 개별 IAM 계정을 할당합니다. 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>

DB 클러스터에 다른 클래스의 인스턴스가 있는 경우 시간이 지나면서 문제가 발생할 수 있습니다. 가장 일반적인 문제는 복제 지연으로 인해 소규모 리더 인스턴스가 반복적으로 재시작되는 주기가 발생할 수 있다는 것입니다. 라이터 DB 인스턴스보다 리더 노드의 DB 인스턴스 클래스 구성이 약한 경우 변경 볼륨이 너무 커서 리더가 따라잡을 수 없습니다.

**중요**  
복제 지연으로 인한 반복적인 재시작을 방지하려면 모든 인스턴스가 동일한 인스턴스 클래스(크기)를 갖도록 DB 클러스터를 구성하면 됩니다.

Amazon CloudWatch의 `ClusterReplicaLag` 지표를 사용하여 라이터 인스턴스(기본)와 DB 클러스터의 리더 간 지연을 확인할 수 있습니다. 또한 이 `VolumeWriteIOPs` 지표를 통해 클러스터에서 복제 지연을 야기할 수 있는 쓰기 작업의 폭증을 감지할 수 있습니다.

## 대량 로드하는 동안 반복적인 재시작 방지
<a name="best-practices-loader-repeated-restarts"></a>

대량 로드 중에 복제 지연으로 인해 읽기 전용 복제본이 반복적으로 재시작되면 복제본이 DB 클러스터의 라이터 속도를 따라가지 못할 수 있습니다.

리더를 라이터보다 크게 확장하거나 대량 로드 중에 일시적으로 제거한 다음, 완료 후 다시 생성하세요.

## 조건자 수가 많은 경우 OSGP 인덱스 활성화
<a name="best-practices-general-predicates"></a>

데이터 모델에 많은 수의 Distinct 조건자(대개의 경우에 1,000명 이상)가 포함된 경우 성능 저하 및 운영 비용 상승을 겪을 수 있습니다.

이 경우 [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 DB 클러스터에서 사용 가능한 지표를 모니터링할 수 있습니다.

다양한 기간 동안의 평균값, 최댓값, 최솟값에 대한 데이터를 모으려면 정기적으로 성능 지표를 모니터링합니다. 이렇게 하면 성능이 저하된 시점을 식별할 수 있습니다. 이 데이터를 사용하면 특정 지표 임계값에 도달했을 때 알림을 받을 수 있도록 해당 임계값에 대한 Amazon CloudWatch 경보를 설정할 수 있습니다.

새 DB 클러스터를 설정하고 일반적인 워크로드로 실행할 경우 다양한 간격(예: 1시간, 24시간, 1주, 2주)으로 모든 성능 지표의 평균값, 최댓값, 최솟값을 수집합니다. 이렇게 하면 무엇이 정상인지를 알 수 있습니다. 이렇게 하면 작업의 최고 피크와 최저 피크 시간을 비교할 수 있습니다. 그런 다음 이 정보를 사용하여 성능이 표준 수준 이하로 떨어진 때를 파악하고, 그에 따라 경보를 설정할 수 있습니다.

Neptune 지표를 보는 방법에 대한 자세한 내용은 [Amazon CloudWatch를 사용하여 Neptune 모니터링](cloudwatch.md)을 참조하세요.

가장 중요한 지표는 다음과 같습니다.
+ **BufferCacheHitRatio** - 버퍼 캐시에서 처리하는 요청 비율입니다. 캐시를 놓치면 쿼리 실행에 상당한 지연 시간이 추가됩니다. 캐시 적중률이 99.9% 미만이고 애플리케이션에 지연 시간이 문제가 되는 경우 메모리에 더 많은 데이터를 캐시하도록 인스턴스 유형을 업그레이드해 보세요.
+ **CPU 사용률** - 사용된 컴퓨터 처리 용량의 백분율입니다. 쿼리의 성능 목표에 따라 CPU 소비량 값이 높아도 괜찮을 수 있습니다.
+ **여유 메모리** – DB 인스턴스에서 사용 가능한 RAM을 메가바이트 단위로 나타냅니다. Neptune에는 자체 메모리 관리자가 있으므로, 이 지표는 예상보다 낮을 수 있습니다. 쿼리에서 종종 메모리 부족 예외가 발생하는 것은 인스턴스 클래스를 RAM 용량이 큰 클래스로 업그레이드할지 고민해 봐야 한다는 확실한 신호입니다.

**모니터링** 탭 지표의 빨간색 선은 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 항목이 가리키는 호스트를 변경하여 작동합니다. WebSocket 연결은 오랜 기간 동안 계속 유지되기 때문에 클라이언트는 새 연결을 생성하고 DNS 레코드를 해결하여 새 읽기 복제본에 연결해야 합니다.

연속적인 요청에 대해 서로 다른 읽기 복제본을 가져오려면 클라이언트가 연결할 때마다 DNS 항목을 해결해야 합니다. 이 경우 연결을 닫고 리더 엔드포인트에 다시 연결해야 할 수 있습니다.

인스턴스 엔드포인트에 명시적으로 연결하여 읽기 복제본 간에 요청을 로드 밸런싱할 수 있습니다.

## 더 큰 임시 인스턴스를 사용하여 더욱 빠르게 로딩
<a name="best-practices-loader-tempinstance"></a>

인스턴스 크기가 클수록 로드 성능이 향상됩니다. 대형 인스턴스 유형을 사용하지 않고 로드 속도가 증가하기를 바란다면 더 큰 인스턴스를 사용하여 로드한 후 삭제할 수 있습니다.

**참고**  
다음 절차는 새 클러스터에 대한 절차입니다. 기존 클러스터가 있다면 더 큰 새 인스턴스를 추가한 다음 기본 DB 인스턴스로 크기를 높일 수 있습니다.

**더 큰 인스턴스 크기를 사용하여 데이터를 로드하려면**

1.  단일 `r5.12xlarge` 인스턴스로 클러스터를 만듭니다. 이 인스턴스는 기본 DB 인스턴스입니다.

1. 같은 크기(`r5.12xlarge`)의 읽기 전용 복제본을 하나 이상 생성합니다.

   읽기 전용 복제본을 더 작은 크기로 만들 수 있지만, 기본 인스턴스의 쓰기를 처리할 수 있을 만큼 크기가 크지 않으면 자주 다시 시작해야 할 수 있습니다. 이로 인한 가동 중지 때문에 성능이 크게 저하됩니다.

1. 대량 로더 명령에 `“parallelism” : “OVERSUBSCRIBE”`를 포함하여 로딩에 사용 가능한 모든 CPU 리소스를 사용하도록 Neptune에 지시합니다([Neptune 로더 요청 파라미터](load-api-reference-load.md#load-api-reference-load-parameters) 참조). 그러면 로드 작업은 I/O가 허용하는 한 빠르게 진행되며, 이 경우 일반적으로 60\$170%의 CPU 리소스가 필요합니다.

1. Neptune 로더를 사용하여 데이터를 로드합니다. 로드 작업은 기본 DB 인스턴스에서 실행합니다.

1. 데이터 로딩을 완료한 후에는 클러스터의 모든 인스턴스를 동일한 인스턴스 유형으로 축소하여 추가 요금이 부과되거나 재시작 문제가 반복되지 않도록 해야 합니다([상이한 인스턴스 크기 방지](#best-practices-loader-heterogeneous-instances) 참조).

## 읽기 전용 복제본으로 장애 조치하여 라이터 인스턴스의 크기 조정
<a name="best-practices-resize-instance"></a>

라이터 인스턴스를 비롯한 DB 클러스터의 인스턴스 크기를 조정하는 가장 좋은 방법은 읽기 전용 복제본 인스턴스를 생성하거나 수정하여 원하는 크기로 만든 후 의도적으로 읽기 전용 복제본으로 장애 조치하는 것입니다. 애플리케이션에서 확인할 수 있는 가동 중지는 라이터의 IP 주소를 변경하는 데 필요한 시간일 뿐이며, 이는 약 3\$15초입니다.

현재 라이터 인스턴스를 의도적으로 읽기 전용 복제본 인스턴스로 장애 조치하는 데 사용하는 Neptune 관리 API는 [FailoverDBCluster](api-clusters.md#FailoverDBCluster)입니다. Gremlin Java 클라이언트를 사용하는 경우 [여기](best-practices-gremlin-java-new-connection.md)에 설명된 것처럼 장애 조치 후에 새 IP 주소를 가져오기 위해 새 클라이언트 객체를 생성해야 할 수 있습니다.

아래 설명과 같이 재시작 주기가 반복되지 않도록 모든 인스턴스를 같은 크기로 변경해야 합니다.

## 데이터 미리 가져오기 작업 중단 오류 후 업로드 다시 시도
<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"`를 사용하는 경우, 로더는 이미 로드한 파일을 건너뛰고 중단이 발생했을 때 아직 로드하지 않은 파일 로드를 다시 시작합니다.

# Neptune으로 Gremlin을 사용하기 위한 일반 모범 사례
<a name="best-practices-gremlin"></a>

Neptune과 함께 Gremlin 그래프 순회 언어를 사용할 때 다음 권장 사항을 따르세요. 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)
+ [

## DFE 엔진을 활용하기 위한 구조 업서트 쿼리
](#best-practices-gremlin-upserts)
+ [

# Gremlin 코드를 배포할 컨텍스트에서 테스트하세요.
](best-practices-gremlin-console-glv-differences.md)
+ [

# 효율적인 멀티스레드 Gremlin 쓰기 생성
](best-practices-gremlin-multithreaded-writes.md)
+ [

# 생성 시간 속성으로 레코드 정리
](best-practices-gremlin-prune.md)
+ [

# `datetime( )` Groovy 시간 데이터 메서드 사용
](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 조정 이벤트 중에 클라이언트가 연결 안정성을 유지하여 불필요한 연결 종료를 방지하고 애플리케이션 신뢰성을 개선할 수 있습니다.

## DFE 엔진을 활용하기 위한 구조 업서트 쿼리
<a name="best-practices-gremlin-upserts"></a>

[Gremlin `mergeV()` 및 `mergeE()` 단계를 사용하여 효율적인 업서트 생성](gremlin-efficient-upserts.md)에서는 DFE 엔진을 최대한 효과적으로 사용하도록 업서트 쿼리를 구성하는 방법을 설명합니다.

# Gremlin 코드를 배포할 컨텍스트에서 테스트하세요.
<a name="best-practices-gremlin-console-glv-differences"></a>

Gremlin에서는 클라이언트가 서버에 쿼리를 제출하는 여러 가지 방법이 있습니다. WebSocket 또는 Bytecode GLV를 사용하거나 Gremlin 콘솔을 통해 문자열 기반 스크립트를 사용하는 방법이 있습니다.

Gremlin 쿼리 실행은 쿼리를 제출하는 방법에 따라 달라질 수 있다는 점을 인지하는 것이 중요합니다. 빈 결과를 반환하는 쿼리를 바이트코드 모드로 제출하면 성공한 것으로 처리될 수 있지만, 스크립트 모드에서 제출하면 실패한 것으로 처리될 수 있습니다. 예를 들어 스크립트 모드 쿼리에 `next()`를 포함하면 `next()`가 서버로 전송되지만, 바이트코드를 사용하면 클라이언트가 보통 `next()`를 자체적으로 처리합니다. 첫 번째 경우에는 결과가 없으면 쿼리가 실패하지만, 두 번째 경우에는 결과 집합이 비어 있는지 여부에 관계없이 쿼리가 성공합니다.

한 컨텍스트(예: 일반적으로 쿼리를 텍스트 형식으로 제출하는 Gremlin 콘솔)에서 코드를 개발하고 테스트한 후 다른 컨텍스트(예: 바이트코드를 사용하는 Java 드라이버를 통해)에 코드를 배포하면 프로덕션 환경에서 코드가 개발 환경과 다르게 동작하는 문제가 발생할 수 있습니다.

**중요**  
Gremlin 코드를 배포할 GLV 컨텍스트에서 테스트하여 예상치 못한 결과를 방지하세요.

# 효율적인 멀티스레드 Gremlin 쓰기 생성
<a name="best-practices-gremlin-multithreaded-writes"></a>

Gremlin을 사용하여 데이터를 Neptune으로 멀티스레드 로드하는 작업에 대한 몇 가지 지침이 있습니다.

가능하면 각 스레드에 일련의 버텍스 또는 엣지를 지정하고 충돌하지 않는 것을 삽입하거나 수정하십시오. 예를 들어 스레드 1에 ID 범위 1\$150,000이 지정되고, 스레드 2에 ID 범위 50,001\$1100,000이 지정되는 방식입니다. 이렇게 하면 `ConcurrentModificationException` 발생 가능성이 낮아집니다. 더 확실하게 하려면 모든 쓰기 주위에 `try/catch` 블록을 배치합니다. 장애가 발생하는 경우 잠시 기다린 후 다시 시도할 수 있습니다.

50에서 100 사이(버텍스 또는 엣지)의 배치 크기로 쓰기를 배치로 묶는 것도 대개 효과가 좋습니다. 각 버텍스용으로 추가할 속성 수가 많은 경우 100보다 50에 가까운 숫자를 선택하는 것이 더 좋습니다. 몇 가지 실험을 해 볼 만합니다. 배치 쓰기의 경우, 다음과 같은 방법을 사용할 수 있습니다.

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

그런 다음 각 배치 작업에서 이를 반복합니다.

Gremlin 라운트 트립당 버텍스 또는 엣지를 하나씩 서버에 추가하는 것보다 배치를 사용하는 편이 훨씬 효율적입니다.

GLV(Gremlin Language Variant) 클라이언트를 사용하는 경우 먼저 순회를 생성하여 프로그래밍 방식으로 배치를 생성할 수 있습니다. 그런 다음 추가하고 마지막으로 반복합니다. 예를 들면 다음과 같습니다.

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

가능하면 GLV(Gremlin Language Variant) 클라이언트를 사용하는 것이 가장 좋습니다. 그러나 문자열을 연결하여 하나의 배치를 만들어 쿼리를 텍스트 문자열로 제출하는 클라이언트를 사용해도 유사한 작업을 수행할 수 있습니다.

기본 HTTP가 아닌 Gremlin 클라이언트 라이브러리 중 하나를 쿼리에 사용하는 경우 스레드가 동일한 클라이언트, 클러스터 또는 연결 풀을 모두 공유해야 합니다. 최상의 처리량을 얻으려면 Gremlin 클라이언트가 사용하는 작업자 스레드 수, 연결 풀의 크기와 같은 설정을 조정해야 할 수 있습니다.

# 생성 시간 속성으로 레코드 정리
<a name="best-practices-gremlin-prune"></a>

생성 시간을 버텍스에 대한 속성으로 저장하고 주기적으로 삭제하여 기한 경과 레코드를 정리할 수 있습니다.

특정 수명 동안 데이터를 저장한 다음 그래프에서 제거해야 하는 경우(버텍스 유효 시간) 버텍스 생성 시 타임스탬프 속성을 저장할 수 있습니다. 그러면 다음과 같이 특정 시간 전에 생성된 모든 버텍스 대해 `drop()` 쿼리를 주기적으로 발행할 수 있습니다.

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

# `datetime( )` Groovy 시간 데이터 메서드 사용
<a name="best-practices-gremlin-datetime"></a>

Neptune은 Gremlin **Groovy** 변형으로 전송되는 쿼리의 날짜 및 시간을 지정하는 `datetime` 메서드를 제공합니다. 여기에는 Gremlin 콘솔, HTTP REST API를 사용하는 텍스트 문자열, Groovy를 사용하는 기타 직렬화가 포함됩니다.

**중요**  
이는 Gremlin 쿼리를 *텍스트 문자열*로 보내는 메서드*에만* 적용됩니다. GLV(Gremlin Language Variant)를 사용하는 경우 이 언어의 기본 날짜 클래스 및 함수를 사용해야 합니다. 자세한 정보는 다음 섹션([GLV 시간 데이터에 기본 날짜 및 시간 사용](best-practices-gremlin-datetime-glv.md))을 참조하세요.  
TinkerPop `3.5.2`([Neptune 엔진 릴리스 1.1.1.0](engine-releases-1.1.1.0.md)에서 도입)을 시작으로 TinkerPop의 필수 요소는 `datetime`입니다.

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

GLV(Gremlin Language Variant)를 사용하는 경우 Gremlin 시간 데이터에 대해 프로그래밍 언어에서 제공하는 기본 날짜 및 시간 클래스와 함수를 사용해야 합니다.

공식 TinkerPop 라이브러리는 모두 Gremlin Language Variant 라이브러리입니다.
+  [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 Language Variant(GLV) 라이브러리에만 적용됩니다. Gremlin 쿼리를 텍스트 문자열로 전송하는 메서드를 사용하는 경우 Gremlin의 datetime() 함수를 사용해야 합니다. 여기에는 Gremlin 콘솔, HTTP REST API를 사용하거나 드라이버를 통해 Gremlin 문자열을 직접 제출하는 텍스트 문자열이 포함됩니다.



**Go**  
 다음은 ID가 '3'인 버텍스에 대해 'date'라는 단일 속성을 생성하는 Go의 일부 예제입니다. Go time.Now() 함수를 사용하여 생성된 날짜로 값을 설정합니다.

```
import ( "time" )

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

Go를 사용하여 Neptune에 연결하는 전체 예제는 [Go 클라이언트를 사용하여 Neptune DB 인스턴스에 연결](https://docs.aws.amazon.com//neptune/latest/userguide/access-graph-gremlin-go.html)을 참조하세요.

**Java**  
다음은 ID가 '`3`'인 버텍스에 대해 '`date`'라는 단일 속성을 생성하는 Java의 일부 예제입니다. 이는 값을 Java `Date()` 생성자를 사용하여 생성된 날짜로 설정합니다.

```
import java.util.date

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

Java를 사용하여 Neptune에 연결하는 전체 예제는 [Java 클라이언트를 사용하여 Neptune DB 인스턴스에 연결](access-graph-gremlin-java.md) 섹션을 참조하세요.

**Node.js(JavaScript)**  
다음은 ID가 '`3`'인 버텍스에 대해 '`date`'라는 단일 속성을 생성하는 JavaScript의 일부 예제입니다. 이는 값을 Node.js `Date()` 생성자를 사용하여 생성된 날짜로 설정합니다.

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

Node.js를 사용하여 Neptune에 연결하는 전체 예제는 [Node.js를 사용하여 Neptune DB 인스턴스에 연결](access-graph-gremlin-node-js.md) 섹션을 참조하세요.

**.NET(C\$1)**  
다음은 ID가 '`3`'인 버텍스에 대해 '`date`'라는 단일 속성을 생성하는 C\$1 예제의 일부입니다. 이는 값을 .NET `DateTime.UtcNow` 속성을 사용하여 생성된 날짜로 설정합니다.

```
Using System;

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

C\$1을 사용하여 Neptune에 연결하는 전체 예제는 [.NET을 사용하여 Neptune DB 인스턴스에 연결](access-graph-gremlin-dotnet.md) 섹션을 참조하세요.

**Python**  
다음은 ID가 ‘`3`’인 버텍스에 대해 ‘`date`’라는 단일 속성을 생성하는 Python의 일부 예제입니다. 이는 값을 Python `datetime.now()` 메서드를 사용하여 생성된 날짜로 설정합니다.

```
import datetime

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

Python을 사용하여 Neptune에 연결하는 전체 예제는 [Python을 사용하여 Neptune DB 인스턴스에 연결](access-graph-gremlin-python.md) 섹션을 참조하세요.

# Neptune으로 Gremlin Java 클라이언트를 사용한 모범 사례
<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 또는 Iterator를 완전히 사용
](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` 객체는 스레드 세이프이고 그 초기화의 오버헤드가 상당합니다.

이는 또한 내부적으로 `Client` 객체를 생성하는 `GraphTraversalSource`에도 적용됩니다. 예를 들어 다음 코드는 인스턴스화할 새로운 `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>

완료되면 서버가 WebSocker 연결을 닫고 연결과 관련된 모든 리소스가 해제되도록 클라이언트를 닫는 것이 중요합니다. 이는 `Cluster.close( )`를 사용하여 클러스터를 닫으면 자동으로 발생합니다. 이때 `client.close( )`가 내부적으로 호출되기 때문입니다.

클러스터를 적절히 닫지 않으면 Neptune이 20\$125분 후에 모든 유휴 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>

`maxInProcessPerConnection` 및 `maxSimultaneousUsagePerConnection` 파라미터가 모두 단일 WebSocket 연결에서 제출할 수 있는 동시 쿼리의 최대 수에 관련됩니다. 내부적으로 이러한 파라미터는 상호 관련되어 있으며 다른 것을 빼놓고 하나만을 수정할 경우 클라이언트가 클라이언트 연결 풀에서 연결을 가져오려고 시도하는 동안 제한 시간에 도달할 수 있습니다.

기본 최소값을 진행 중 및 동시 사용 값으로 유지하고 `maxInProcessPerConnection`와 `maxSimultaneousUsagePerConnection`를 동일한 값으로 설정하는 것이 좋습니다.

이러한 값을 설정할 값은 쿼리 복잡성 및 데이터 모델의 함수입니다. 쿼리에서 많은 데이터를 반환하는 사용 사례의 경우 쿼리당 더 넓은 연결 대역폭이 요구되므로 파라미터에 대해서는 낮은 값을, `maxConnectionPoolSize`에 대해서는 높은 값을 가져야 합니다.

이와 대조적으로, 쿼리에서 적은 양의 데이터를 반환하는 사례에서는 `maxInProcessPerConnection` 및 `maxSimultaneousUsagePerConnection`가 `maxConnectionPoolSize`보다 높은 값으로 설정되어야 합니다.

# 쿼리를 문자열이 아닌 바이트코드로 서버에 전송
<a name="best-practices-gremlin-java-bytecode"></a>

쿼리 제출 시 문자열이 아닌 바이트코드를 사용하면 다음과 같은 장점이 있습니다.
+ **잘못된 쿼리 구문의 조기 파악: **  바이트코드 변형을 사용하면 컴파일링 단계에서 잘못된 쿼리 구문을 감지할 수 있습니다. 문자열 기반 변형을 사용할 경우 쿼리가 서버에 제출되고 오류가 반환될 때까지는 잘못된 구문을 발견할 수 없습니다.
+ **문자열 기반 성능 패널티의 방지: **  문자열 기반의 모든 쿼리 제출은, WebSockets를 사용하든 HTTP를 사용하든, 버텍스의 분리로 이어집니다. 이는 버텍스 객체가 자격 증명, 레이블 및 버텍스와 연결된 모든 속성으로 구성됨을 의미합니다([요소의 속성](http://tinkerpop.apache.org/docs/current/reference/#_properties_of_elements) 단원 참조).

  이로 인해 속성이 필요하지 않은 경우 서버에서 불필요한 계산이 발생할 수 있습니다. 예를 들어 고객이 쿼리를 사용하여 자격 증명이 "hakuna\$11"인 버텍스를 가져오려 하는 경우 `g.V("hakuna#1")`. 쿼리가 문자열 기반 제출로 전송되면 서버에서 자격 증명, 레이블 및 이 버텍스에 대한 모든 속성을 검색하는 데 시간을 소모하게 됩니다. 쿼리가 바이트코드 제출로 전송되면 서버는 자격 증명과 버텍스의 레이블을 검색하는 데에만 시간을 소모합니다.

즉, 쿼리 제출이 아니라 다음과 같이 진행합니다.

```
  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 또는 Iterator를 완전히 사용
<a name="best-practices-gremlin-java-resultset"></a>

클라이언트 객체는 항상 `GraphTraversal`에서 반환한 `ResultSet`(문자열 기반 제출의 경우) 또는 Iterator를 완전히 사용해야 합니다. 쿼리 결과를 완전히 사용하지 않은 경우 그에 따른 영향으로 서버가 클라이언트에서 사용을 종료할 때까지 대기하게 됩니다.

애플리케이션이 부분적인 결과 집합만을 필요로 하는 경우 쿼리로 `limit(X)` 단계를 사용하여 서버에서 생성되는 결과의 수를 제한할 수 있습니다.

# 배치에서 버텍스 및 엣지 일괄 추가
<a name="best-practices-gremlin-java-batch-add"></a>

Neptune DB에 대한 모든 쿼리는 세션을 사용하지 않을 경우 단일 트랜잭션의 범위에서 실행됩니다. 즉, Gremlin 쿼리를 사용하여 많은 양의 데이터를 삽입해야 하는 경우 50\$1100의 배치 크기로 데이터를 배치화하면 해당 로드에 대해 생성된 트랜잭션의 수를 줄임으로써 성능을 향상할 수 있습니다.

그 예로써 데이터베이스에 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>

여러 읽기 전용 복제본에서 요청을 로드 밸런싱하려는 환경에서는 [클러스터 객체](https://tinkerpop.apache.org/javadocs/current/core/org/apache/tinkerpop/gremlin/driver/Cluster.html)를 생성하는 동안 Java 가상 머신(JVM)에서 DNS 캐싱을 비활성화하고 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 Gremlin 클라이언트는 클러스터 토폴로지를 인식하고 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.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>

Gremlin Java 클라이언트는 WebSocket 연결 중 하나에 있는 슬롯을 사용할 수 있을 때까지 기다리는 동안 클라이언트 자체에서 Gremlin 요청 제한 시간이 초과될 때 `java.util.concurrent.TimeoutException`을 발생시킵니다. 이 제한 시간은 `maxWaitForConnection` 클라이언트측 구성 가능한 파라미터에 의해 제어됩니다.

**참고**  
클라이언트에서 제한 시간이 초과된 요청은 서버로 전송되지 않으므로, 서버에서 캡처된 지표에는 반영되지 않습니다(예: `GremlinRequestsPerSec`).

이러한 종류의 제한 시간은 일반적으로 2가지 경우에 발생합니다.
+ **서버가 실제로 최대 용량에 도달.** 이 경우 서버의 대기열이 꽉 차게 되는데, 이는 [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 프로토콜을 사용할 때는 다음 모범 사례를 따르세요. openCypher를 Neptune과 함께 사용하는 방법에 대한 자세한 내용은 [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)
+ [

# 노드/관계에 사용자 정의 ID 사용 선호
](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>

장애 조치 시 DNS 이름이 특정 IP 주소로 확인되므로 Bolt 드라이버는 새 활성 인스턴스 대신 이전 라이터 인스턴스에 계속 연결할 수 있습니다.

이를 방지하려면 장애 조치 후 `Driver` 객체를 닫았다가 다시 연결하면 됩니다.

## 수명이 긴 애플리케이션의 연결 처리
<a name="best-practices-opencypher-long-connections"></a>

컨테이너 내에서 실행되거나 Amazon EC2 인스턴스에서 실행되는 애플리케이션과 같이 수명이 긴 애플리케이션을 구축할 때는 `Driver` 객체를 한 번 인스턴스화한 다음, 해당 객체를 애플리케이션 수명 기간 동안 재사용합니다. `Driver` 객체는 스레드 세이프이고 그 초기화의 오버헤드가 상당합니다.

## 에 대한 연결 처리 AWS Lambda
<a name="best-practices-opencypher-lambda-connections"></a>

볼트 드라이버는 연결 오버헤드 및 관리 요구 사항으로 인해 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분 후 또는 IAM 인증을 사용하는 경우 10일 후에 모든 유휴 Bolt 연결을 종료합니다.

Neptune은 1,000개 이상의 동시 Bolt 연결을 지원하지 않습니다. 완료 후 연결을 명시적으로 닫지 않고 활성 연결 수가 1,000개 제한에 도달하면 새로운 연결 시도가 실패합니다.

# 읽기 및 쓰기에 명시적 트랜잭션 모드 사용
<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)
    }
  }
);
```

두 경우 모두 [Neptune 읽기 전용 트랜잭션 체계](transactions-neptune.md#transactions-neptune-read-only)를 사용하여 [`SNAPSHOT` 격리](transactions-isolation-levels.md)가 이루어집니다.

읽기 전용 복제본은 읽기 전용 쿼리만 허용하므로, 읽기 전용 복제본에 제출된 모든 쿼리는 `SNAPSHOT` 격리 체계로 실행됩니다.

읽기 전용 트랜잭션에는 더티 읽기나 반복 불가능한 읽기가 없습니다.

## 변형 트랜잭션
<a name="best-practices-opencypher-mutation-txs"></a>

변형 쿼리의 경우 쓰기 트랜잭션을 생성하는 3가지 메커니즘이 있으며, 각 메커니즘은 다음과 같습니다.

**암시적 쓰기 트랜잭션 예제:**

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

재시도를 허용하는 모든 예외에 대해서는 일반적으로 `ConcurrentModificationException` 오류와 같은 일시적인 문제를 더 잘 처리하기 위해 재시도 간 대기 시간을 점진적으로 늘리는 [지수 백오프 및 재시도 전략](https://docs.aws.amazon.com/general/latest/gr/api-retries.html)을 사용하는 것이 가장 좋습니다. 다음은 지수 백오프 및 재시도 패턴의 예제입니다.

```
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: NUMBER, STRING 및 BOOLEAN을 포함하는 사이퍼 리터럴 목록으로 UNWIND.
+  시나리오 2: 사이퍼 리터럴(NUMBER, STRING, BOOLEAN)만 값으로 포함하는 평면화된 맵 목록으로 UNWIND.

 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 표현식 체인의 경우이 최적화는 첫 번째 표현식에만 적용할 수 있습니다. 다른 VLP는 왼쪽부터 평가를 시작합니다. 예를 들어 (a), (b)의 카디널리티가 1이고 (c)의 카디널리티가 1보다 크도록 합니다.
+  `(a)-[*1..]->(c)`: 평가는 (a)로 시작합니다.
+  `(c)-[*1..]->(a)`: 평가는 (a)로 시작합니다.
+  `(a)-[*1..]-(c)`: 평가는 (a)로 시작합니다.
+  `(c)-[*1..]-(a)`: 평가는 (a)로 시작합니다.

 이제 (a)의 수신 엣지가 2이고, (a)의 발신 엣지가 3이고, (b)의 수신 엣지가 4이고, (b)의 발신 엣지가 5가 되도록 합니다.
+  `(a)-[*1..]->(b)`: (a)의 발신 엣지가 (b)의 수신 엣지보다 작으면 평가가 (a)로 시작됩니다.
+  `(a)<-[*1..]-(b)`: (a)의 수신 엣지가 (b)의 발신 엣지보다 작으면 평가가 (a)로 시작됩니다.

 일반적으로 VLP 표현식의 왼쪽에 보다 제한적인 패턴을 배치합니다.

# 세분화된 관계 이름을 사용하여 중복 노드 레이블 확인 방지
<a name="best-practices-content-5"></a>

 성능 최적화를 위해 노드 패턴에 고유한 관계 레이블을 사용하면 노드에 대한 레이블 필터링을 제거할 수 있습니다. 그래프 모델에서 `likes` 관계가 두 `person` 노드 간의 관계 정의에만 사용된다고 가정해 보세요. 이 패턴을 찾기 위해 다음과 같은 쿼리를 작성할 수 있습니다.

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

 `person` 레이블 검사는 n과 m 모두 `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
```

 성능이 중요하고 모델링 과정에서 이러한 엣지 레이블이 다른 노드 레이블 관련 패턴에 재사용되지 않도록 확인 절차가 마련된 경우에만 이 패턴을 채택해야 합니다. 이후 `company`와 같은 다른 노드 레이블에 `email` 속성을 추가하면 두 버전의 쿼리 결과는 달라질 것입니다.

# 가능한 경우 엣지 레이블 지정
<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 절은 패턴 `(person)-[:lives_in]->(city)`(더 제한적인 조건)을 `(city)-[:part_of]->(country {name: 'India'})` 앞에 배치하도록 강제합니다. 이렇게 하면 계획이 최적화되지 않습니다. 이 쿼리를 최적화하려면 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` 노드에, `firstName`, `lastName`, `age`, `dept`, `company`의 5개 속성이 있다고 가정하면 다음 쿼리가 선호됩니다.

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

 클라이언트 측에서 정적 계산(단순 수학/문자열 작업)을 해결하는 것이 좋습니다. 작성자보다 1세 이하인 모든 사람을 찾으려는 경우 이 예제를 고려해 보세요.

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

 워크로드에 가장 적합한 것을 결정하려면 다양한 배치 크기로 실험하는 것이 좋습니다.

# 노드/관계에 사용자 정의 ID 사용 선호
<a name="best-practices-content-15"></a>

 Neptune은 사용자가 노드와 관계에 ID를 명시적으로 할당할 수 있도록 합니다. ID는 데이터세트에서 전역적으로 고유해야 하며 유용하도록 결정적이어야 합니다. 결정적 ID는 속성과 마찬가지로 조회 또는 필터링 메커니즘으로 사용할 수 있지만, ID 사용은 속성을 사용하는 것보다 쿼리 실행 관점에서 훨씬 더 최적화되어 있습니다. 사용자 정의 ID를 사용하면 여러 가지 이점이 있습니다.
+  속성은 기존 개체에 대해 null일 수 있지만 ID가 있어야 합니다. 이렇게 하면 쿼리 엔진이 실행 중에 최적화된 조인을 사용할 수 있습니다.
+  동시 변형 쿼리를 실행하면 강제 고유성으로 인해 속성보다 IDs에 대한 잠금이 더 적기 때문에 ID를 사용하여 노드에 액세스할 때 [동시 수정 예외](https://docs.aws.amazon.com//neptune/latest/userguide/transactions-exceptions.html)(CMEs)가 발생할 가능성이 크게 줄어듭니다.
+  ID를 사용하면 중복 데이터 생성 가능성을 피할 수 있습니다. 왜냐하면 Neptune은 속성과 달리 ID에 대해 고유성을 강제하기 때문입니다.

 다음 쿼리 예제에서는 사용자 지정 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>

 쿼리에서 사용자 지정 ID를 사용하는 경우 항상 쿼리 외부에서 정적 계산을 수행하고 파라미터에 이러한 값을 제공합니다. 정적 값이 제공되면 엔진이 조회를 최적화하고 이러한 값을 스캔하고 필터링하지 않아도 됩니다.

 데이터베이스에 있는 노드 간에 엣지를 생성하려는 경우 한 가지 옵션은 다음과 같습니다.

```
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 절을 사용하는 대신 단일 MERGE/CREATE 절과 함께 `UNWIND`를 사용하는 것이 좋습니다. 노드마다 하나의 절을 사용하는 쿼리는 각 행에 대한 최적화가 필요하기 때문에 비효율적인 실행 계획을 초래합니다. 이로 인해 쿼리 실행 시간의 대부분이 실제 업데이트가 아닌 정적 처리 단계에서 소모됩니다.

 노드당 하나의 절 방식은 노드 수가 증가함에 따라 확장성이 떨어지므로 최적의 방법이 아닙니다.

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

SPARQL 쿼리 언어를 Neptune과 함께 사용하는 경우 다음 모범 사례를 따르세요. SPARQL을 Neptune과 함께 사용하는 방법에 자세한 내용은 [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은 3개마다 이름이 있는 그래프를 연결합니다. 기본 그래프는 이름이 있는 모든 그래프의 조합으로 정의됩니다.

`GRAPH`와 같은 `FROM NAMED` 키워드 또는 구문을 통해 그래프를 명시적으로 지정하지 않고 SPARQL 쿼리를 제출하는 경우 Neptune은 항상 DB 인스턴스의 모든 트리플을 고려합니다. 예를 들어 다음 쿼리는 Neptune SPARQL 엔드포인트의 모든 트리플을 반환합니다.

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

1개 이상의 그래프에 나타나는 triples는 한 번만 반환합니다.

기본 그래프 사양에 대한 사항은 SPARQL 1.1 쿼리 언어 사양의 [RDF 데이터 세트](https://www.w3.org/TR/sparql11-query/#rdfDataset)를 참조하십시오.

# 로드에 대해 명명된 그래프 지정
<a name="best-practices-sparql-graph"></a>

Amazon Neptune은 3개마다 이름이 있는 그래프를 연결합니다. 트리플을 로드, 삽입 또는 업데이트할 때 이름이 있는 그래프를 지정하지 않으면 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`입니다.