# 처음 사용자용 가이드

이 문서는 ARCUS를 처음 접하는 자바 개발자를 위해 작성되었습니다. [Apache Maven](http://maven.apache.org/)의 개념과 기본 사용법을 알고 있다고 가정하고 있으며, 자세한 설명을 하기 보다는 Copy\&Paste를 통해 ARCUS를 사용해볼 수 있는 내용으로 되어 있습니다.

## ARCUS 소개

ARCUS는 오픈소스 memcached를 기반으로 데이터 분산과 fault-tolerance를 구현한 메모리 기반의 캐시 클러스터 입니다.

* memcached : 구글, 페이스북 등에서 대규모로 사용하고 있는 메모리 캐시 서버입니다.
* 캐시 : 자주 사용되는 데이터를 고속의 저장소에 넣어둠으로써, 느린 저장소로의 요청을 줄이고 보다 빠른 응답성을 제공할 수 있는 서비스입니다.
* 메모리 기반 : ARCUS는 데이터를 메모리에만 저장합니다. 따라서 모든 데이터는 휘발성이며 언제든지 삭제될 수 있습니다.
* 클러스터 : 각 서비스는 초기 캐시 클러스터를 구성한 후 동적으로 캐시 서버를 추가하거나 삭제할 수 있습니다. (단, 일부 데이터는 유실됩니다)
* fault-tolerance : ARCUS는 캐시 서버들의 이상 상태를 검사하면서 고장 서버를 탐지, 제거하여 정상 캐시 서버만으로 캐시 서비스를 지속합니다.

또한 ARCUS는 key-value 형태의 데이터뿐만 아니라 하나의 키로 List, Set, Map, B+Tree 등의 자료구조를 저장할 수 있는 Collection 기능을 제공합니다.

## 미리 알아두기

* 키(key)
  * ARCUS의 key는 prefix와 subkey로 구성되며, 이를 콜론(:)으로 구분합니다. (예) *users:user\_12345*
  * ARCUS는 prefix 기준의 통계를 수집합니다. prefix 개수의 제한은 없으나 통계 수집이 필요할 경우는 많지 않은 개수(20개 이하)로 생성하는 것을 권합니다.
  * 키는 prefix, subkey를 포함하여 4000자를 넘을 수 없습니다. 따라서 응용에서 키 길이를 제한하셔야 합니다.
* 값(value)
  * 하나의 키에 대한 값은 바이트 스트림 형태로 최대 1MB 까지 저장될 수 있습니다.
  * 자바 객체를 저장하는 경우, 해당 객체는 반드시 Serializable 인터페이스를 구현해야 합니다.
* ARCUS 접속 정보
  * ARCUS admin: ZooKeeper 주소이며, 캐시 서버들의 `IP:PORT` 정보를 조회하고 변경이 있을 때 클라이언트에게 알려주는 역할을 합니다.
  * ARCUS service code: 사용자 또는 응용에게 제공된 ARCSU 캐시 클러스터를 각각 구분하는 식별자입니다.

## Hello, ARCUS!

기본적인 key-value 캐시 요청을 수행해보도록 하겠습니다. 아커스 서버가 구성되어 있다고 가정합니다. 우선 다음과 같이 비어 있는 자바 프로젝트를 생성합니다.

```bash
$ mvn archetype:generate -DgroupId=com.navercorp.arcus -DartifactId=arcus-quick-start -DinteractiveMode=false
$ cd arcus-quick-start
$ mvn eclipse:eclipse // 이클립스 IDE를 사용하는 경우 실행하여 이클립스 프로젝트를 생성하여 활용합니다.
```

### pom.xml

프로젝트가 생성되면 pom.xml에서 ARCUS 클라이언트를 참조하도록 변경합니다.

```xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.navercorp.arcus</groupId>
    <artifactId>arcus-quick-start</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>arcus-quick-start</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-api</artifactId>
          <version>5.10.2</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-engine</artifactId>
          <version>5.10.2</version>
          <scope>test</scope>
        </dependency>

        <!-- ARCUS 클라이언트 의존성을 추가합니다. -->
        <dependency>
            <groupId>com.navercorp.arcus</groupId>
            <artifactId>arcus-java-client</artifactId>
            <version>1.16.1</version>
        </dependency>
        
        <!-- 로거 의존성을 추가합니다. -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.8.2</version>
        </dependency>
    </dependencies>
</project>
```

### HelloArcus.java

이제 ARCUS와 통신하는 클래스를 생성해봅시다. 시나리오는 다음과 같습니다.

* HelloArcus.sayHello(): Arcus 캐시 서버에 "Hello, Arcus!" 값을 저장합니다.
* HelloArcus.listenHello(): Arcus 캐시 서버에 저장된 "Hello, Arcus!" 값을 읽어옵니다.

```java
// HelloArcusTest.java
package com.navercorp.arcus;

import org.junit.jupiter.api.BeforeEach;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class HelloArcusTest {

    HelloArcus helloArcus = new HelloArcus("127.0.0.1:2181", "test");
    
    @BeforeEach
    protected void sayHello() {
        helloArcus.sayHello();
    }
    
    @Test
    public void listenHello() {
        assertEquals("Hello, Arcus!", helloArcus.listenHello());
    }
    
}
```

```java
// HelloArcus.java
package com.navercorp.arcus;

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import net.spy.memcached.ArcusClient;
import net.spy.memcached.ConnectionFactoryBuilder;

public class HelloArcus {

    private String arcusAdmin;
    private String serviceCode;
    private ArcusClient arcusClient;

    public HelloArcus(String arcusAdmin, String serviceCode) {
        this.arcusAdmin = arcusAdmin;
        this.serviceCode = serviceCode;
        
        // log4j logger를 사용하도록 설정합니다.
        // 코드에 직접 추가하지 않고 아래의 JVM 환경변수를 사용해도 됩니다.
        //   -Dnet.spy.log.LoggerImpl=net.spy.memcached.compat.log.Log4JLogger
        System.setProperty("net.spy.log.LoggerImpl", "net.spy.memcached.compat.log.Log4JLogger");

        // Arcus 클라이언트 객체를 생성합니다.
        // - arcusAdmin : Arcus 캐시 서버들의 그룹을 관리하는 admin 서버(ZooKeeper)의 주소입니다.
        // - serviceCode : 사용자에게 할당된 Arcus 캐시 서버들의 집합에 대한 코드값입니다. 
        // - connectionFactoryBuilder : 클라이언트 생성 옵션을 지정할 수 있습니다.
        //
        // 정리하면 arcusAdmin과 serviceCode의 조합을 통해 유일한 캐시 서버들의 집합을 얻어 연결할 수 있는 것입니다.
        this.arcusClient = ArcusClient.createArcusClient(arcusAdmin, serviceCode, new ConnectionFactoryBuilder());
    }

    public boolean sayHello() {
        Future<Boolean> future = null;
        boolean setSuccess = false;

        // Arcus의 "test:hello" 키에 "Hello, Arcus!"라는 값을 저장합니다.
        // 그리고 Arcus의 거의 모든 API는 Future를 리턴하도록 되어 있으므로
        // 비동기 처리에 특화된 서버가 아니라면 반드시 명시적으로 future.get()을 수행하여
        // 반환되는 응답을 기다려야 합니다.
        future = this.arcusClient.set("test:hello", 600, "Hello, Arcus!");
        
        try {
            setSuccess = future.get(700L, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            if (future != null) future.cancel(true);
            e.printStackTrace();
        }
        
        return setSuccess;
    }
    
    public String listenHello() {
        Future<Object> future = null;
        String result = "Not OK.";
        
        // Arcus의 "test:hello" 키의 값을 조회합니다.
        // Arcus에서는 가능한 모든 명령에 명시적으로 timeout 값을 지정하도록 가이드 하고 있으며
        // 사용자는 set을 제외한 모든 요청에 async로 시작하는 API를 사용하셔야 합니다.
        future = this.arcusClient.asyncGet("test:hello");
        
        try {
            result = (String)future.get(700L, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            if (future != null) future.cancel(true);
            e.printStackTrace();
        }
        
        return result;
    }

}
```

### src/test/resources/log4j2.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%-35c{1}:%-3L) %m%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="WARN">
            <AppenderRef ref="console" />
        </Root>
        <Logger name="net.spy.memcached.StatisticsHandler" level="INFO" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.ArcusClient" level="INFO" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.BTreeGetBulkOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.CollectionInsertOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.CollectionPipedInsertOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.CollectionGetOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.BTreeSortMergeGetOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.CollectionDeleteOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.CollectionUpdateOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.CollectionPipedExistOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.SetAttrOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.StoreOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        <Logger name="net.spy.memcached.protocol.ascii.CollectionCountOperationImpl" level="DEBUG" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
    </Loggers>
</Configuration>
```

### 테스트

위 예제는 127.0.0.1:2181 에 ZooKeeper 가 작동하고 있고 memcached 서버가 구동하고 있다고 가정합니다. 아직 준비가 안 되어 있다면, 다음 페이지 Running Test Cases 를 따라 준비합니다.

<https://github.com/naver/arcus-java-client/blob/master/README.md>

테스트가 통과하는지 확인해봅니다.

```bash
$ mvn test
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
...
Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.885s
[INFO] Finished at: Mon Dec 17 14:13:22 KST 2012
[INFO] Final Memory: 4M/81M
[INFO] ------------------------------------------------------------------------
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jam2in.gitbook.io/arcus-java-client/arcus-java-client-getting-started.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
