

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

# Amazon Neptune과 함께 사용할 수 있는 Java 기반 Gremlin 클라이언트
<a name="access-graph-gremlin-client"></a>

Amazon Neptune에서는 [Apache TinkerPop Java Gremlin 클라이언트](https://search.maven.org/artifact/org.apache.tinkerpop/gremlin-driver)와 [Amazon Neptune용 Gremlin 클라이언트](https://search.maven.org/artifact/software.amazon.neptune/gremlin-client)라는 2개의 오픈 소스 Java 기반 Gremlin 클라이언트 중 하나를 사용할 수 있습니다.

## Apache TinkerPop Java Gremlin 클라이언트
<a name="access-graph-gremlin-java-driver"></a>

Apache TinkerPop Java [gremlin-driver](https://tinkerpop.apache.org/docs/current/reference/#gremlin-java)는 모든 TinkerPop 지원 그래프 데이터베이스에서 작동하는 표준 공식 Gremlin 클라이언트입니다. 광범위한 TinkerPop 개발 공간과의 호환성이 극대화되어야 하거나, 여러 그래프 데이터베이스 시스템으로 작업하거나, Neptune과 관련된 고급 클러스터 관리 및 로드 밸런싱 기능이 필요하지 않은 경우이 클라이언트를 사용합니다. 이 클라이언트는 단일 Neptune 인스턴스에 연결하는 간단한 애플리케이션이나 클라이언트 내부가 아닌 인프라 수준에서 로드 밸런싱을 처리하려는 경우에 적합합니다.

**중요**  
Neptune 엔진 버전과의 호환성을 위해서는 올바른 Apache TinkerPop Gremlin 드라이버 버전을 선택하는 것이 중요합니다. 호환되지 않는 버전을 사용하면 연결 실패 또는 예기치 않은 동작이 발생할 수 있습니다. 자세한 버전 호환성 정보는 섹션을 참조하세요[Gremlin을 사용하여 Neptune 그래프에 액세스](access-graph-gremlin.md).

**참고**  
Neptune과 함께 사용할 올바른 Apache TinkerPop 버전을 결정하는 데 도움이 되는 표가 로 이동되었습니다[Gremlin을 사용하여 Neptune 그래프에 액세스](access-graph-gremlin.md). 이 표는 이전에이 페이지에 수년 동안 위치했으며 이제 TinkerPop이 지원하는 모든 프로그래밍 언어에 대한 참조를 위해 더 중앙 집중화되었습니다.

## Amazon Neptune용 Gremlin Java 클라이언트
<a name="access-graph-neptune-gremlin-client"></a>

Amazon Neptune용 Gremlin 클라이언트는 표준 TinkerPop Java 클라이언트의 드롭인 대체 역할을 하는 [오픈 소스 Java 기반 Gremlin 클라이언트](https://github.com/aws/neptune-gremlin-client)입니다.

Neptune Gremlin 클라이언트는 Neptune 클러스터에 최적화되어 있습니다. 이를 통해 클러스터의 여러 인스턴스에 걸친 트래픽 분산을 관리하고, 복제본을 추가하거나 제거할 때 클러스터 토폴로지의 변경 사항에 맞게 조정할 수 있습니다. 역할, 인스턴스 유형, 가용 영역 또는 인스턴스와 관련된 태그를 기반으로 클러스터의 인스턴스 하위 세트에 요청을 분산하도록 클라이언트를 구성할 수도 있습니다.

[Neptune Gremlin Java 클라이언트의 최신 버전](https://search.maven.org/artifact/software.amazon.neptune/gremlin-client)은 Maven Central에서 사용할 수 있습니다.

Neptune Gremlin Java 클라이언트에 대한 자세한 내용은 [이 블로그 게시물](https://aws.amazon.com/blogs/database/load-balance-graph-queries-using-the-amazon-neptune-gremlin-client/)을 참조하세요. 코드 샘플과 데모를 보려면 [클라이언트의 GitHub 프로젝트](https://github.com/aws/neptune-gremlin-client)를 확인하세요.

Neptune Gremlin 클라이언트 버전을 선택할 때는 Neptune 엔진 버전과 관련하여 기본 TinkerPop 버전을 고려해야 합니다. 의 호환성 표를 참조하여 Neptune 엔진에 적합한 TinkerPop 버전을 [Gremlin을 사용하여 Neptune 그래프에 액세스](access-graph-gremlin.md) 확인한 다음 다음 다음 표를 사용하여 적절한 Neptune Gremlin 클라이언트 버전을 선택합니다.


**Neptune Gremlin 클라이언트 버전 호환성**  

| Neptune Gremlin 클라이언트 버전 | TinkerPop 버전 | 
| --- | --- | 
| 3.x | 3.7.x(Java 2.x/1.x용AWS SDK) | 
| 2.1.x | 3.7.x(Java 1.x용AWS SDK) | 
| 2.0.x | 3.6.x | 
| 1.12 | 3.5.x | 

# Java 클라이언트를 사용하여 Neptune DB 인스턴스에 연결
<a name="access-graph-gremlin-java"></a>

다음 섹션에서는 Neptune DB 인스턴스에 연결하고 Apache TinkerPop Gremlin 클라이언트를 사용하여 Gremlin 순회를 수행하는 전체 Java 샘플 실행에 대해 설명합니다.

이러한 지침은 Neptune DB 인스턴스와 동일한 Virtual Private Cloud(VPC)에 있는 Amazon EC2 인스턴스에서 따라야 합니다.

**Java를 사용하여 Neptune에 연결하려면**

1. EC2 인스턴스에 Apache Maven을 설치합니다. Amazon Linux 2023(기본 설정)을 사용하는 경우 다음을 사용합니다.

   ```
   sudo dnf update -y
   sudo dnf install maven -y
   ```

   Amazon Linux 2를 사용하는 경우 [https://maven.apache.org/download.cgi](https://maven.apache.org/download.cgi:)에서 최신 바이너리를 다운로드합니다.

   ```
   sudo yum remove maven -y
   wget https://dlcdn.apache.org/maven/maven-3/ <version>/binaries/apache-maven-<version>-bin.tar.gz
   sudo tar -xzf apache-maven-<version>-bin.tar.gz -C /opt/
   sudo ln -sf /opt/apache-maven-<version> /opt/maven
   echo 'export MAVEN_HOME=/opt/maven' >> ~/.bashrc
   echo 'export PATH=$MAVEN_HOME/bin:$PATH' >> ~/.bashrc
   source ~/.bashrc
   ```

1. **Java를 설치합니다.** Gremlin 라이브러리에는 Java 8 또는 11이 필요합니다. 다음과 같이 Java 11을 설치할 수 있습니다.
   + [Amazon Linux 2(AL2)](https://aws.amazon.com/amazon-linux-2)를 사용하는 경우:

     ```
     sudo amazon-linux-extras install java-openjdk11
     ```
   + [Amazon Linux 2023(AL2023)](https://docs.aws.amazon.com/linux/al2023/ug/what-is-amazon-linux.html)을 사용하는 경우:

     ```
     sudo yum install java-11-amazon-corretto-devel
     ```
   + 다른 배포판의 경우 다음 중 적절한 것을 사용하세요.

     ```
     sudo yum install java-11-openjdk-devel
     ```

     또는:

     ```
     sudo apt-get install openjdk-11-jdk
     ```

1. **Java 11을 EC2 인스턴스의 기본 런타임으로 설정:** Java 8을 EC2 인스턴스의 기본 런타임으로 설정하려면 다음을 입력합니다.

   ```
   sudo /usr/sbin/alternatives --config java
   ```

   메시지가 표시되면 Java 11에 대한 숫자를 입력합니다.

1. **다음과 같이 `gremlinjava`라는 새 디렉터리를 생성합니다.**

   ```
   mkdir gremlinjava
   cd gremlinjava
   ```

1.  다음과 같이 `gremlinjava` 디렉터리에서 `pom.xml` 파일을 생성한 다음, 텍스트 편집기에서 엽니다.

   ```
   nano pom.xml
   ```

1. 다음을 `pom.xml` 파일로 복사하고 저장합니다:

   ```
   <project xmlns="https://maven.apache.org/POM/4.0.0"
            xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
     <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.amazonaws</groupId>
     <artifactId>GremlinExample</artifactId>
     <packaging>jar</packaging>
     <version>1.0-SNAPSHOT</version>
     <name>GremlinExample</name>
     <url>https://maven.apache.org</url>
     <dependencies>
       <dependency>
         <groupId>org.apache.tinkerpop</groupId>
         <artifactId>gremlin-driver</artifactId>
         <version>3.7.2</version>
       </dependency>
       <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-jdk14</artifactId>
         <version>1.7.25</version>
       </dependency>
     </dependencies>
     <build>
       <plugins>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>2.5.1</version>
           <configuration>
             <source>11</source>
             <target>11</target>
           </configuration>
         </plugin>
           <plugin>
             <groupId>org.codehaus.mojo</groupId>
             <artifactId>exec-maven-plugin</artifactId>
             <version>1.3</version>
             <configuration>
               <executable>java</executable>
               <arguments>
                 <argument>-classpath</argument>
                 <classpath/>
                 <argument>com.amazonaws.App</argument>
               </arguments>
               <mainClass>com.amazonaws.App</mainClass>
               <complianceLevel>1.11</complianceLevel>
               <killAfter>-1</killAfter>
             </configuration>
           </plugin>
       </plugins>
     </build>
   </project>
   ```
**참고**  
기존 Maven 프로젝트를 수정할 경우 필요한 종속성이 이전 코드에서 강조 표시됩니다.

1. 명령줄에 다음을 입력하여 예제 소스 코드의 하위 디렉터리를 생성합니다(`src/main/java/com/amazonaws/`).

   ```
   mkdir -p src/main/java/com/amazonaws/
   ```

1. `src/main/java/com/amazonaws/` 디렉터리에서 `App.java` 파일을 생성한 다음 텍스트 편집기에서 엽니다.

   ```
   nano src/main/java/com/amazonaws/App.java
   ```

1. 다음을 `App.java` 파일로 복사합니다. *your-neptune-endpoint*를 Neptune DB 인스턴스의 주소로 바꿉니다. `addContactPoint` 메서드에 `https://` 접두사를 포함하지 *마세요*.
**참고**  
사용자의 Neptune DB 인스턴스 호스트 이름을 찾는 방법은 [Amazon Neptune 엔드포인트에 연결](feature-overview-endpoints.md) 섹션을 참조하세요.

   ```
   package com.amazonaws;
   import org.apache.tinkerpop.gremlin.driver.Cluster;
   import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
   import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
   import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
   import org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection;
   import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
   import org.apache.tinkerpop.gremlin.structure.T;
   
   public class App
   {
     public static void main( String[] args )
     {
       Cluster.Builder builder = Cluster.build();
       builder.addContactPoint("your-neptune-endpoint");
       builder.port(8182);
       builder.enableSsl(true);
   
       Cluster cluster = builder.create();
   
       GraphTraversalSource g = traversal().withRemote(DriverRemoteConnection.using(cluster));
   
       // Add a vertex.
       // Note that a Gremlin terminal step, e.g. iterate(), is required to make a request to the remote server.
       // The full list of Gremlin terminal steps is at https://tinkerpop.apache.org/docs/current/reference/#terminal-steps
       g.addV("Person").property("Name", "Justin").iterate();
   
       // Add a vertex with a user-supplied ID.
       g.addV("Custom Label").property(T.id, "CustomId1").property("name", "Custom id vertex 1").iterate();
       g.addV("Custom Label").property(T.id, "CustomId2").property("name", "Custom id vertex 2").iterate();
   
       g.addE("Edge Label").from(__.V("CustomId1")).to(__.V("CustomId2")).iterate();
   
       // This gets the vertices, only.
       GraphTraversal t = g.V().limit(3).elementMap();
   
       t.forEachRemaining(
         e ->  System.out.println(t.toList())
       );
   
       cluster.close();
     }
   }
   ```

   SSL/TLS(필수)를 사용하여 Neptune에 연결하는 데 도움이 필요하면 [SSL/TLS 구성](#access-graph-gremlin-java-ssl)을 참조하세요.

1. 다음 Maven 명령을 사용하여 샘플을 컴파일하고 실행합니다.

   ```
   mvn compile exec:exec
   ```

이전 예에서는 `g.V().limit(3).elementMap()` 순회를 사용하여 그래프의 처음 두 버텍스의 각 속성의 키와 값 맵을 반환했습니다. 다른 것을 쿼리하려면 해당하는 종료 메서드 중 하나를 사용하여 다른 Gremlin 순회로 바꿉니다.

**참고**  
Gremlin 쿼리의 최종 부분 `.toList()`에서 순회를 서버로 제출하여 평가를 받아야 합니다. 이 메서드 또는 이와 유사한 메서드를 포함시키지 않는 경우에는 쿼리가 Neptune DB 인스턴스로 제출되지 않습니다.  
또한 `addV( )` 단계를 사용할 때와 같이 버텍스 또는 엣지를 추가할 때도 적절한 종료를 추가해야 합니다.

다음 메서드는 쿼리를 Neptune DB 인스턴스로 제출합니다.
+ `toList()`
+ `toSet()`
+ `next()`
+ `nextTraverser()`
+ `iterate()`

## Gremlin Java 클라이언트를 위한 SSL/TLS 구성
<a name="access-graph-gremlin-java-ssl"></a>

Neptune에서는 SSL/TLS를 기본적으로 활성화해야 합니다. 일반적으로 Java 드라이버가 `enableSsl(true)`로 구성된 경우 인증서의 로컬 사본으로 `trustStore()` 또는 `keyStore()`를 설정하지 않고도 Neptune에 연결할 수 있습니다.

하지만 연결 중인 인스턴스가 퍼블릭 인증서를 확인하는 데 사용할 수 있는 인터넷 연결이 없거나 사용 중인 인증서가 퍼블릭 인증서가 아닌 경우에는 다음 단계에 따라 로컬 인증서 사본을 구성할 수 있습니다.

**SSL/TLS를 활성화하기 위한 로컬 인증서 사본 설정**

1. Oracle에서 [keytool](https://docs.oracle.com/javase/9/tools/keytool.htm#JSWOR-GUID-5990A2E4-78E3-47B7-AE75-6D1826259549)을 다운로드하여 설치합니다. 이렇게 하면 로컬 키 스토어를 훨씬 쉽게 설정할 수 있습니다.

1. `SFSRootCAG2.pem`CA 인증서를 다운로드합니다. Gremlin Java SDK에는 원격 인증서를 확인하기 위한 인증서가 필요합니다.

   ```
   wget https://www.amazontrust.com/repository/SFSRootCAG2.pem
   ```

1. JKS 또는 PKCS12 형식으로 키 스토어를 생성합니다. 이 예제에서는 JKS를 사용합니다. 프롬프트에서 이어지는 질문에 답하세요. 여기서 생성한 암호를 나중에 사용합니다.

   ```
   keytool -genkey -alias (host name) -keyalg RSA -keystore server.jks
   ```

1. 다운로드한 `SFSRootCAG2.pem` 파일을 새로 만든 키 스토어로 가져옵니다.

   ```
   keytool -import -keystore server.jks -file .pem
   ```

1. 프로그래밍 방식으로 `Cluster` 객체를 구성합니다.

   ```
   Cluster cluster = Cluster.build("(your neptune endpoint)")
                            .port(8182)
                            .enableSSL(true)
                            .keyStore(‘server.jks’)
                            .keyStorePassword("(the password from step 2)")
                            .create();
   ```

   원하는 경우 Gremlin 콘솔에서 수행할 수 있는 것처럼 구성 파일에서 동일한 작업을 수행할 수 있습니다.

   ```
   hosts: [(your neptune endpoint)]
   port: 8182
   connectionPool: { enableSsl: true, keyStore: server.jks, keyStorePassword: (the password from step 2) }
   serializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }}
   ```

## IAM 인증
<a name="access-graph-gremlin-java-iam"></a>

Neptune은 DB 클러스터에 대한 액세스를 제어하는 [IAM 인증을](iam-auth-enable.md) 지원합니다. IAM 인증을 활성화한 경우 서명 버전 4 서명을 사용하여 요청을 인증해야 합니다. Java 클라이언트에서 연결하기 위한 자세한 지침과 코드 예제는 섹션을 참조하세요[Gremlin Java와 함께 IAM을 사용하여 Amazon Neptune 데이터베이스에 연결](iam-auth-connecting-gremlin-java.md).

# 재연결 로직을 사용하여 Neptune DB 인스턴스에 연결하는 Java 예제
<a name="access-graph-gremlin-java-reconnect-example"></a>

다음 Java 예제는 재연결 로직을 통해 Gremlin 클라이언트에 연결하여 예상치 못한 연결 끊김을 복구하는 방법을 보여줍니다.

다음과 같은 종속성이 있습니다.

```
<dependency>
    <groupId>org.apache.tinkerpop</groupId>
    <artifactId>gremlin-driver</artifactId>
    <version>${gremlin.version}</version>
</dependency>

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>amazon-neptune-sigv4-signer</artifactId>
    <version>${sig4.signer.version}</version>
</dependency>

<dependency>
    <groupId>com.evanlennick</groupId>
    <artifactId>retry4j</artifactId>
    <version>0.15.0</version>
</dependency>
```

샘플 코드는 다음과 같습니다.

**중요**  
 Retry4j에서 `CallExecutor`는 스레드 세이프가 아닐 수 있습니다. 각 스레드가 자체 `CallExecutor` 인스턴스를 사용하거나 다른 재시도 라이브러리를 사용하도록 하는 것이 좋습니다.

**참고**  
 다음 예제는 requestInterceptor()를 사용하도록 업데이트되었습니다. 이는 TinkerPop 3.6.6에 추가되었습니다. TinkerPop 버전 3.6.6 이전에는 코드 예제에서 해당 릴리스에서 더 이상 사용되지 않는 handshakeInterceptor()를 사용했습니다.

```
public static void main(String args[]) {
  boolean useIam = true;

  // Create Gremlin cluster and traversal source
  Cluster.Builder builder = Cluster.build()
         .addContactPoint(System.getenv("neptuneEndpoint"))
         .port(Integer.parseInt(System.getenv("neptunePort")))
         .enableSsl(true)
         .minConnectionPoolSize(1)
         .maxConnectionPoolSize(1)
         .serializer(Serializers.GRAPHBINARY_V1D0)
         .reconnectInterval(2000);

  if (useIam) {
      builder.requestInterceptor( r -> {
         try {
            NeptuneNettyHttpSigV4Signer sigV4Signer =
                        new NeptuneNettyHttpSigV4Signer("(your region)", new DefaultAWSCredentialsProviderChain());
            sigV4Signer.signRequest(r);
         } catch (NeptuneSigV4SignerException e) {
            throw new RuntimeException("Exception occurred while signing the request", e);
         }
         return r;
      });
   }

  Cluster cluster = builder.create();

  GraphTraversalSource g = AnonymousTraversalSource
      .traversal()
      .withRemote(DriverRemoteConnection.using(cluster));

  // Configure retries
  RetryConfig retryConfig = new RetryConfigBuilder()
      .retryOnCustomExceptionLogic(getRetryLogic())
      .withDelayBetweenTries(1000, ChronoUnit.MILLIS)
      .withMaxNumberOfTries(5)
      .withFixedBackoff()
      .build();

  @SuppressWarnings("unchecked")
  CallExecutor<Object> retryExecutor = new CallExecutorBuilder<Object>()
      .config(retryConfig)
      .build();

  // Do lots of queries
  for (int i = 0; i < 100; i++){
    String id = String.valueOf(i);

    @SuppressWarnings("unchecked")
    Callable<Object> query = () -> g.V(id)
        .fold()
        .coalesce(
            unfold(),
            addV("Person").property(T.id, id))
        .id().next();

    // Retry query
    // If there are connection failures, the Java Gremlin client will automatically
    // attempt to reconnect in the background, so all we have to do is wait and retry.
    Status<Object> status = retryExecutor.execute(query);

    System.out.println(status.getResult().toString());
  }

  cluster.close();
}

private static Function<Exception, Boolean> getRetryLogic() {

  return e -> {

    Class<? extends Exception> exceptionClass = e.getClass();

    StringWriter stringWriter = new StringWriter();
    String message = stringWriter.toString();


    if (RemoteConnectionException.class.isAssignableFrom(exceptionClass)){
      System.out.println("Retrying because RemoteConnectionException");
      return true;
    }

    // Check for connection issues
    if (message.contains("Timed out while waiting for an available host") ||
        message.contains("Timed-out") && message.contains("waiting for connection on Host") ||
        message.contains("Connection to server is no longer active") ||
        message.contains("Connection reset by peer") ||
        message.contains("SSLEngine closed already") ||
        message.contains("Pool is shutdown") ||
        message.contains("ExtendedClosedChannelException") ||
        message.contains("Broken pipe") ||
        message.contains(System.getenv("neptuneEndpoint")))
    {
      System.out.println("Retrying because connection issue");
      return true;
    };

    // Concurrent writes can sometimes trigger a ConcurrentModificationException.
    // In these circumstances you may want to backoff and retry.
    if (message.contains("ConcurrentModificationException")) {
      System.out.println("Retrying because ConcurrentModificationException");
      return true;
    }

    // If the primary fails over to a new instance, existing connections to the old primary will
    // throw a ReadOnlyViolationException. You may want to back and retry.
    if (message.contains("ReadOnlyViolationException")) {
      System.out.println("Retrying because ReadOnlyViolationException");
      return true;
    }

    System.out.println("Not a retriable error");
    return false;
  };
}
```