技術解説

データ・ダイバーシティと KVSの利用 Riak KV の活用例(後編)

Riak KVの応用

Data Typesの利用

前編では、クラスターのセットアップと基本的なバケット操作方法を紹介しました。
後編はRiak KVのData Typesの機能について紹介します。Data TypesはCRDT(Conflict-free Replicated Data Type)から派生したもので、可用性の高い可換データ型*に対する分散処理フレームワークになります。
*可換データ(足し算や掛け算のように順番を入れ替えても同じ結果が得られるデータ)
Data Typesで利用できるデータの型を下記の表にまとめました。

 

 Data Typesで利用できるデータ型の種類

 

本編では、Data Typesのユースケースとしてユーザーの属性情報を管理する場合の例を紹介します。ここでの登場人物は太郎と花子で、花子は太郎の配偶者である設定です。
下記の表にある各々の属性情報をMAPに登録します。

 

属性情報のMAP

 

Data Typesを使う準備

Data Typesを使う場合、最初にbucket typeを作ります。作成するbucket typeにはデータ型を指定します。以下の方法でMAP型のbucket type(maps)を作ります。

bucket type(maps)の作成:

riak-admin bucket-type create maps '{"props":{"datatype":"map"}}'

 

bucket type(maps)のアクティベート:

riak-admin bucket-type activate maps

 

作成したbucket typeがactiveでデータ型がMAPになっていることを確認します。

riak-admin bucket-type status maps |grep -e datatype -e maps

maps is active

datatype: map

 

データを投入

ここでは、HTTPインターフェースを使ってデータを操作します。尚、開発者向けにBasho社公式のクライアントライブラリーがOSSで提供されており、利用方法なども紹介されています。
http://docs.basho.com/riak/kv/latest/developing/client-libraries/

 

前編同様、curlを使いファイル(JSON)からデータを投入します。最初に太郎のMAPを準備します。bucket typeにmapsを指定し、Key(datatypes)をtaro_infoにしてデータを書き込みます。

cat taro_info.json
{
"update": {
        "first_name_register": "Taro",
        "phone_number_register": "5551234567",
        "enterprise_customer_flag": "disable",
        "family_plan_flag": "enable",
        "free_plan_flag": "disable",
        "interests_set": {
             "add_all": [
                "music",
                "car",
                 "furniture"
               ]
         },
        "page_visits_counter": 100
     }
}

 

*各要素の suffix にデータ型を指定します。

curl -XPOST http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/\

taro_info -H "Content-Type: application/json" -T

taro_info.json

 

値を取得すると、一番下にCausal Contextが戻ります。これは、Riak KV内部のVector Clocksによって紐づけられる値のバージョンに相当するものです。このバージョンの差を比較することでデータの新旧を判別できます。

curl http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/taro_info |jsonpp
{
  "type": "map",
  "value": {
    "enterprise_customer_flag": false,
    "family_plan_flag": true,
    "first_name_register": "Taro",
    "free_plan_flag": false,
    "interests_set": [
      "car",
      "furniture",
      "music"
    ],
    "page_visits_counter": 100,
    "phone_number_register": "5551234567"
  },
  "context": "g2wAAAABaAJtAAAADJxBDecT5pkSAADqYWEDag=="
}

 

次に花子のMAPを準備します。bucket typeにmapsを指定し、Key(datatypes)はhanako_infoにしてデータを書き込みます。

cat hanako_info_includeTaro.json
{
"update": {
    "hanako_info_map": {
      "update": {
        "first_name_register": "Hanako",
        "phone_number_register": "5559876543",
        "enterprise_plan_flag": "disable",
        "family_plan_flag": "disable",
        "free_plan_flag": "enable",
        "interests_set":{
              "add_all": [
                "fashion"
              ]
         },
        "page_visits_counter": 2,
        "purchase_map": {
            "update": {
              "first_purchase_flag": "enable",
              "items_set":{
                  "add_all": [
                    "tote bag"
                 ]
                }
              }
            }
          }
        }
      }
}
curl -XPOST http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/taro_info -H "Content-Type: 
application/json" -T hanako_info_includeTaro.json

 

太郎の属性情報に花子の情報を含める場合、太郎のMAP に花子の MAP をネストすることで花子の属性情報を含めることができます。Key(datatypes)taro_infoに花子のMAPを追加します。

cat hanako_info_includeTaro.json
{
"update": {
    "hanako_info_map": {
      "update": {
        "first_name_register": "Hanako",
        "phone_number_register": "5559876543",
        "enterprise_plan_flag": "disable",
        "family_plan_flag": "disable",
        "free_plan_flag": "enable",
        "interests_set":{
              "add_all": [
                "fashion"
              ]
         },
        "page_visits_counter": 2,
        "purchase_map": {
            "update": {
              "first_purchase_flag": "enable",
              "items_set":{
                  "add_all": [
                    "tote bag"
                 ]
                }
              }
            }
          }
        }
      }
}
curl -XPOST http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/taro_info -H 
"Content-Type: application/json" -T hanako_info_includeTaro.json

 

値を取得すると、一番下のCausal Contextが更新されていることが分かります。

curl http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/taro_info |jsonpp
{
  "type": "map",
  "value": {
    "enterprise_customer_flag": false,
    "family_plan_flag": true,
    "first_name_register": "Taro",
    "free_plan_flag": false,
    "hanako_info_map": {
      "enterprise_plan_flag": false,
      "family_plan_flag": false,
      "first_name_register": "Hanako",
      "free_plan_flag": true,
      "interests_set": [
        "fashion"
      ],
      "page_visits_counter": 2,
      "phone_number_register": "5559876543",
      "purchase_map": {
        "first_purchase_flag": true,
        "items_set": [
          "tote bag"
        ]
      }
    },
    "interests_set": [
      "car",
      "furniture",
      "music"
    ],
    "page_visits_counter": 100,
    "phone_number_register": "5551234567"
  },
  "context": "g2wAAAABaAJtAAAADJxBDecT5pkSAADqYWEEag=="
}

 

次に花子がハンドバッグを購入したとします。この時、下記のように属性情報を変更します。

 花子の属性情報の変更

 

花子の変更を花子の MAP と太郎の MAP に反映します。今度はファイルからではなく直接 Raw データで変更します。

花子の MAP の変更:

curl -XPOST
http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/hanako_info -H
"Content-Type: application/json" -d ' { "update": { "purchase_map": { "update": { "first_purchase_flag": "disable", "items_set": { "add": "hand bag" } } } } }'

 

太郎の MAP の変更:

curl -XPOST http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/taro_info -H "Content-Type: 
application/json" -d ' { "update": { "hanako_info_map": { "update": { "purchase_map": { "update": { "first_purchase_flag": "disable", "items_set": { "add": "hand bag" } } } } } } }'

 

値を取得して、変更されていることを確認します。

curl http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/hanako_info |jsonpp
~ 途中省略 ~
    "purchase_map": {
      "first_purchase_flag": false,
      "items_set": [
        "hand bag",
        "tote bag"
      ]
    }
~ 途中省略 ~

curl http://10.20.0.51:8098/types/maps/buckets/customers/datatypes/taro_info |jsonpp
{
~ 途中省略 ~
    "hanako_info_map": {
~ 途中省略 ~
      "purchase_map": {
        "first_purchase_flag": false,
        "items_set": [
          "hand bag",
          "tote bag"
        ]
      }
    },
~ 途中省略 ~
  },
  "context": "g2wAAAABaAJtAAAADJxBDecT5pkSAADqYWEFag=="

Causal Contextも更新されます。

 

競合解決

Riak KV は Eventually Consistent なシステムであるため、ある Key/Value に対して同時に別の更新処理が行われた場合、競合を解決する仕組みを考慮する必要があります。

Data Typesの値に更新の衝突が発生した場合、Riak KVは下記のロジックで競合解決します。

 

Data types(CRDT)の競合解決

 

Data Typesがサポートされる以前は、Data Typesで取り扱うようなデータで競合が起こった場合、アプリケーション側で解決ロジックを実装する必要がありました。

Data Typesを使うと競合解決をサーバー側で自律的に行えるため、アプリケーションのコードをシンプルにすることができます。

 

Data Typesの検索

Riak KVには Riak Searchと呼ばれる全文検索機能があります。Apache Solrがバンドルされており、JSON や XML、Plain Textなどのテキストサーチが可能ですが、Data Types の検索にも対応しています。以下に、Data Types 内のデータの検索方法を紹介します。

 

Riak Searchの有効化

Riak Searchを有効にするには、設定ファイル(riak.conf)の下記の箇所を変更します。

変更前:

search = off

変更後:

search = on

 

デフォルトでは Solr の JMXが有効になっているため DNS か hostsファイルでサーバーのホスト名を名前解決できるようにします。

設定変更後は、riak のサービスを再起動します。

riak サービスの再起動:

riak stop

riak start

 

インデックスの作成と適用

まず、インデックス(attribute)を作成します(予め用意された _yz_defaultスキーマを使用しています)。

curl -X PUT 10.20.0.51:8098/search/index\

/attribute -H 'Content-Type: application/json' -d '{"schema":"_yz_default"}'

 

bucket type maps にインデックス attribute を設定します。

riak-admin bucket-type update maps '{"props":{"search_index":"attribute"}}'

riak-admin bucket-type activate maps

 

bucket type のステータスで search_index が設定されていることを確認します。

riak-admin bucket-type status maps |grep search

search_index: <<"attribute">>

 

検索の実行

Data Types の要素に対して検索を実行します。

例1:サイト訪問回数(page_visit_counters)が 15 を超えている件数
※[15 TO *]" → (URL エンコーディング) → %5b15%20TO%20%2a%5d

curl "http://10.20.0.51:8098/search/query/\
attribute?wt=json&q=page_visits_counter:%5b15%20TO%20%2a%5d" |jsonpp { "responseHeader": { "params": { ~ 途中省略 ~ "q": "page_visits_counter:[15 TO *]", ~ 途中省略 ~ } }, "response": { "numFound": 1, ~ 途中省略 ~ "page_visits_counter": 100, ~ 途中省略 ~ "_yz_id": "1*maps*customers*taro_info*32", "_yz_rk": "taro_info", "_yz_rt": "maps", "_yz_rb": "customers" } ] } }

 

1件ヒット("numFound": 1)しました。下記のフィールドから、Key とバケット情報を確認できます。

    "_yz_id": Solr ドキュメントのユニークな ID

    "_yz_rk": Riak KV の Key

    "_yz_rt": Riak KV の bucket type

    "_yz_rb": Riak KV の bucket

 

例2:電話番号が555から始まるユーザーを検索

curl "http://10.20.0.51:8098/search/query/\
attribute?wt=json&q=phone_number_register:555*" | jsonpp { "responseHeader": { ~ 途中省略 ~ "q": "phone_number_register:555*", ~ 途中省略 ~ } }, "response": { "numFound": 2, ~ 途中省略 ~ "docs": [ { ~ 途中省略 ~ "phone_number_register": "5559876543", ], ~ 途中省略 ~ "_yz_id": "1*maps*customers*hanako_info*35", "_yz_rk": "hanako_info", "_yz_rt": "maps", "_yz_rb": "customers" }, { "phone_number_register": "5551234567", ~ 途中省略 ~ "_yz_id": "1*maps*customers*taro_info*32", "_yz_rk": "taro_info", "_yz_rt": "maps", "_yz_rb": "customers" ~ 途中省略 ~

 

例3:太郎の MAP から花子の購入品の中にバッグが含まれているかを検索

curl "http://10.20.0.51:8098/search/query/\
attribute?wt=json&q=hanako_info_map.purchase_map.items_set:*bag" | jsonpp { "responseHeader": { ~ 途中省略 ~ "q": "hanako_info_map.purchase_map.items_set:*bag", ~ 途中省略 ~ } }, "response": { "numFound": 1, ~ 途中省略 ~ "hanako_info_map.purchase_map.items_set": [ "hand bag", "tote bag" ], ~ 途中省略 ~ "_yz_id": "1*maps*customers*taro_info*32", "_yz_rk": "taro_info", "_yz_rt": "maps", "_yz_rb": "customers" } ] } }

 

例4:太郎の MAP から花子の購入品の中にシューズが含まれているかを検索

curl "http://10.20.0.51:8098/search/query/\
attribute?wt=json&q=hanako_info_map.purchase_map.items_set:*shoes" | jsonpp { "responseHeader": { ~ 途中省略 ~ "q": "hanako_info_map.purchase_map.items_set:*shoes", } }, "response": { "numFound": 0, ~ 途中省略 ~ } }

 

まとめ

今回は、Riak KVの特徴とData Typesについて紹介しました。Riak KVはデータをバイナリーで格納するため、文字列やテキストデータ、画像など様々なデータを格納することができます。また、リレーショナルデータベースと比較すると、厳密なデータの型や関係性を意識しなくてよいので自由にデータを管理できるだけでなく、拡張性にも優れています。一方、用途によってはデータがある程度構造化されている方が便利な場合があります(データの集計や集約など)。
アプリケーションデータがサーバー1台で収まりきれない場合や、データを失わない高い可用性が要求されるシステムなどで NoSQLの利用シーンが広がっています。

掲載製品の特長・仕様はこちらから