Couchbaseの導入検討（後編）


Couchbaseの導入検討（後編）

## **はじめに**
この記事は[株式会社アイスタイルアドベントカレンダー](http://qiita.com/advent-calendar/2015/istyle) 19日目の記事です。
やっと入社2年目に突入したインフラエンジニア @imaiy がゆるい感じで2回に分けてお届けします。

[前回の記事](http://qiita.com/imaiy/items/8e01f380c53ce2eda1da)は導入の背景やCouchbaseの説明がメインでした。
今回はCouchbaseの導入編です。

インストールから、データベースへのクエリ発行(NiQL)、PHPでの接続やデータ取得を試します。

# **環境**
サーバ環境は以下のとおりです。

- Couchbase Server
 - CentOS release 6.7
 - couchbase-server-community-4.0.0-centos6.x86_64
- Clientサイド
 - libcouchbase-devel-2.5.3-1.el6.x86_64
 - libcouchbase2-core-2.5.3-1.el6.x86_64
 - PHP 5.5
 - memcached 1.4.4

# **Couchbase Serverのインストール**
[Couchbase公式](http://www.couchbase.com/)からrpmを落としてきてサーバ内に配置、インストール。
```
rpm -ivh couchbase-server-community-4.0.0-centos6.x86_64.rpm
```

インストール後は自動的にCouchbase Serverが起動します。
netstat -l とか叩くとわかりますが、[色々なポート](http://developer.couchbase.com/documentation/server/4.0/install/install-ports.html)がオープンになりますのでご注意下さい。

```
# /etc/init.d/couchbase-server status
couchbase-server-community is not running
```

今後は管理ページ(http://<hostname>:8091/)にて初期設定など行っていきます。
それぞれの項目をよく読み設定して下さい。

- Couchbaseで利用可能なメモリ量
- データを永続保管するためのディレクトリ指定
- Sample Bucketはいつでも消せるので作っておきます。

# **とりあえず使ってみる**
インストールが済んだらローカルから使ってみましょう。
サンプルの*beer-sample* Bucketを使ってNiQLを叩いてみます。

```
# /opt/couchbase/bin/cbq
Couchbase query shell connected to http://localhost:8093/ . Type Ctrl-D to exit.
cbq>  SELECT name, type FROM `beer-sample` limit 2;
{
    "requestID": "310b452a-1ce1-4046-99ec-55f58b47fc84",
    "errors": [
        {
            "code": 4000,
            "msg": "No primary index on keyspace beer-sample. Use CREATE PRIMARY INDEX to create one."
        }
    ],
    "status": "fatal",
    "metrics": {
        "elapsedTime": "79.274704ms",
        "executionTime": "79.234254ms",
        "resultCount": 0,
        "resultSize": 0,
        "errorCount": 1
    }
}
```

結果がJSON形式で返ってきました。
"No primary index on keyspace beer-sample. Use CREATE PRIMARY INDEX to create one." と言われている通り、
NiQLを発行するためにはPrimary Keyを設定して上げる必要があります。
 ※なお、NiQLを発行しない単なるKVSとして利用する分にはPrimary indexを作る必要はありません。

```
cbq> CREATE PRIMARY INDEX ON `beer-sample` USING GSI;
{
    "requestID": "7e87ef4e-baa9-46b7-a68a-1a014a24041d",
    "signature": null,
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "3.906325722s",
        "executionTime": "3.906271638s",
        "resultCount": 0,
        "resultSize": 0
    }
}

```

成功したようです。
改めてNiQLを発行します。

```
cbq> SELECT name, type FROM `beer-sample` limit 2;
{
    "requestID": "b2cb0935-d8a5-4084-bd4b-07b915648591",
    "signature": {
        "name": "json",
        "type": "json"
    },
    "results": [
        {
            "name": "South Park Blonde",
            "type": "beer"
        },
        {
            "name": "563 Stout",
            "type": "beer"
        }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "20.777558ms",
        "executionTime": "20.726616ms",
        "resultCount": 2,
        "resultSize": 150
    }
}

cbq> UPDATE `beer-sample` SET name = "563 Stout -X" WHERE name = "563 Stout";
{
    "requestID": "4bdfd577-f692-4bf3-a500-48f313c3213a",
    "signature": null,
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "621.796863ms",
        "executionTime": "621.755243ms",
        "resultCount": 0,
        "resultSize": 0,
        "mutationCount": 1
    }
}

cbq> SELECT * FROM `beer-sample` WHERE name = "563 Stout -X";
{
    "requestID": "c6842a1e-e0aa-435d-83b1-2d33b46d6060",
    "signature": {
        "*": "*"
    },
    "results": [
        {
            "beer-sample": {
                "abv": 5,
                "brewery_id": "21st_amendment_brewery_cafe",
                "category": "North American Ale",
                "description": "Deep black color, toasted black burnt coffee flavors and aroma. Dispensed with Nitrogen through a slow-flow faucet giving it the characteristic cascading effect, resulting in a rich dense creamy head.",
                "ibu": 0,
                "name": "563 Stout -X",
                "srm": 0,
                "style": "American-Style Stout",
                "type": "beer",
                "upc": 0,
                "updated": "2010-07-22 20:00:20"
            }
        }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "657.178757ms",
        "executionTime": "657.060166ms",
        "resultCount": 1,
        "resultSize": 674
    }
}
```

このような感じで、一般的なSQLクエリに近いものが発行可能です。
[細かいNiQLのリファレンスはこちら](http://developer.couchbase.com/documentation/server/4.0/n1ql/index.html)を御覧ください。

# PHPクライアントの構築
さて、ここからはPHPでCouchbaseに繋いでみます。
まずはPHP用のSDKをインストールしましょう。

```
wget -O /etc/yum.repos.d/couchbase.repo http://packages.couchbase.com/rpm/couchbase-centos62-x86_64.repo
yum install libcouchbase-devel
pecl install couchbase
echo "extension=couchbase.so" >> /etc/php.d/json.ini

# php -i | grep -A5 couchbase
couchbase

Version => 2.0.7
```

解説はしませんがphp-infoでcouchbaseが出てくる状態ならおそらく大丈夫でしょう。

いよいよPHPコードです。
なお、私はプログラマではないのでコードの質には目を瞑って下さい・ω・

```hello_couchbase.php
<?php
// Connect to Couchbase Server

$cluster = new CouchbaseCluster('http://127.0.0.1:8091');
$bucket = $cluster->openBucket('beer-sample');

// Retrieve a document

try {
    $result = $bucket->get('21st_amendment_brewery_cafe');
} catch (Exception $e) {
    echo "CouchbaseException: " . $e->getMessage() . "\n";
}

$doc = json_decode($result->value, true);

var_dump($doc);
```

個別に説明します。

- Couchbaseクラスタに接続：```$cluster = new CouchbaseCluster('http://127.0.0.1:8091');```
- BucketをOpen：```$bucket = $cluster->openBucket('beer-sample');```
- KeyからValueを取得：```$result = $bucket->get('21st_amendment_brewery_cafe');```

ここでは先程のNiQLでの取得ではなく、KVS的な「KeyからValueを取得」する方法でデータを得ています。
変数 "$result" には先ほどのNiQLでの結果と同様のデータが "CouchbaseMetaDoc" クラスとして格納されています。
なお、getを用いる際はKeyが存在しないとExceptionを吐きます。

```var_dump($result)
object(CouchbaseMetaDoc)#4 (4) {
  ["error"]=>
  NULL
  ["value"]=>
  string(666) "{"name":"21st Amendment Brewery Cafe","city":"San Francisco","state":"California","code":"94107","country":"United States","phone":"1-415-369-0900","website":"http://www.21st-amendment.com/","type":"brewery","updated":"2010-10-24 13:54:07","description":"The 21st Amendment Brewery offers a variety of award winning house made brews and American grilled cuisine in a comfortable loft like setting. Join us before and after Giants baseball games in our outdoor beer garden. A great location for functions and parties in our semi-private Brewers Loft. See you soon at the 21A!","address":["563 Second Street"],"geo":{"accuracy":"ROOFTOP","lat":37.7825,"lon":-122.393}}"
  ["flags"]=>
  int(0)
  ["cas"]=>
  resource(4) of type (CouchbaseCAS)
}
```

ここからvalueを取り出すことでNiQLの結果でいうResultsと同等のデータが取り出せます。
errorがnull出ない場合のエラーハンドリングなんかも必要になりそうですね。

続けて取得したデータの編集からupsertまでをやってみます。
既存カラムの編集はもちろん、新規カラムの追加もDecodeしたデータからそのまま編集できます。

```hello_couchbase.php(続き)
$doc["comment"] = 'Random beer from Norway';
$doc["name"] = '563 Stout -X2';

$result = $bucket->upsert('21st_amendment_brewery_cafe_commented', $doc, array('expiry' => 60));
```

upsertにはKeyとArrayを渡すとそのKeyに対してArrayをそのValueとしてinsertもしくはupdateされます。
このとき、後ろに有効期限(Expiry)をセットしてやると期限付きのデータとして扱われ、get時に有効期限切れのデータは得られなくなります。

# NiQLからデータ取得

細かくは述べませんがこんな感じ。
ちょっと困るのが、データの並びは固定ではなさそうで、複数結果を含む場合のデータ順序が毎回変わります。
必要に応じてソートを掛ける必要がありそう。

```
    $query = CouchbaseN1qlQuery::fromString('SELECT * FROM `beer-sample` LIMIT 1');
    $result = $bucket->query($query);
```

# memcached互換機能を使ってみる

公式上は「そのままmemcached側のライブラリで使えるゼ！」っと謳っているので試してみます。

- CouchbaseServerの管理ページ上から、[Data Buckets]->[Create New Data Bucket]を選択し、[Bucket Type]はmemcachedを選択しましょう。
- Access ControlはSASL authとかしないので[Dedicated port]にして、利用していないTCPポートを指定しましょう。

設定後、クラスタに属するCouchbaseServerで指定したTCPポートがListenされているのを確認して、memcachedでアクセスしてみます。

```hello_couchbase_memcached.php
<?php
$m = new Memcached();
$m->addServer('192.168.0.1', 11212);
$m->set('key2', 'wankoooo', 10);

if ($val = $m->get('key2')) {
    echo "$val\n";
}

sleep(15);

if ($val = $m->get('key2')) {
    echo "$val\n";
}
```

以下が実行結果です。

```
# php mem.php
wankoooo
```

Expirationも動いているようなので、問題なさそうです。

# おわりに

CouchbaseServerの基本的な機能をざっと使ってみました。
memcachedをそのままカバー出来るため、開発側への負担少なく導入が進められそうかなと思っていたんですが、
作ったり使ったりしていると色々運用負荷も下げられそうな予感です。

memcacheに対応していないとか速度的にはmemcachedに劣るとかマイナス点もありますが
今後もう少し見極めて行く予定です。

また、今回書ききれませんでしたが、性能試験の為に5台のクラスタを構築して、
サービスデータのキャッシュテストやアクセスログ解析なども行っているので、機会があれば書いてみたいと思ってます。