AlibabaCloudのNoSQL TableStore入門

皆さん、はじめまして。最近猫を飼いはじめました、piyohikoと申します。

突然ですが、NoSQLってご存知ですか?一般に “Not only SQL”(No SQLじゃないんですね)と呼ばれるデータベースです。もちろんAlibaba CloudにもTableStoreという名前で提供されています。今回はこちらをご紹介したいと思います。

はじめに

TableStoreはAlibabaCloudが提供するNoSQLのデータベースサービスです。といっても、NoSQLが何か?どんなときに役立てるものなのか?まだまだ知られていないことも多いです。本記事では、Alibaba CloudのNoSQLサービスであるTableStoreの特徴を紹介するとともに、NoSQLの特徴などについてもご紹介していきます。

みなさんはRDBMS(リレーショナルデータベース。代表格としてMySQLやPostgreSQLなど)を利用されることが多いかと思います。RDBMSとNoSQLとの大きな違いはデータの構造とそれによるスケールアウト(サーバを増やす)のしやすさがあります。

従来のRDBMSでは一般的にスケールアップ(CPUやメモリなどハードウエアを高性能なものにして処理性能を上げる方法)でスケールの対応をすることが多いかと思います。しかし、スケールアップには物理的な限界があります。NoSQLは構造がシンプルなことからスケールアウトがしやすく、その特徴を最大限に活かしているサービスがTableStoreです。TableStoreはDBのスペックを気にすることなく、処理量に応じてチャージして利用できます。そのため、例えばIoTデバイスからのデータ書き込みなどに向いているといえます。

改めてTableStoreとは?

一言でいうと、AlibabaCloudが提供するSSDで構成されたフルマネージドなNoSQLデータベースサービスです。key-valueストア型データを管理することができ、主キーと属性列で構成されます。

もう一回だけ。NoSQLとは?

NoSQLとは「Not Only SQL」のことで、リレーショナルデータベースマネジメントシステム(RDBMS例:MySQL、OracleDB、PostgreSQL、等)以外のデータベースのことです。NoSQLの中にもたくさんの種類がありますが、代表例として、mongoDBやRedisなどがあります。

TableStoreの構成例

高速読み取り、高いスケーラビリティをもつTableStoreですが、適用するのに向いているデータと向いていないデータがあります。

どのような場合に活用できるのか、RDBMSと比較しながらご紹介します。

RDBMSとNoSQLの違い

RDBMS ・・・複雑なデータに向いている。データの肥大化による性能劣化が起きることがある。
NoSQL・・・単純なデータに向いている。データが肥大化しても性能劣化が起こりずらいが、複雑なデータは苦手。

RDBMS NoSQL
メリット デメリット メリット デメリット
データの一貫性 が保証されている

SQLによる複雑な条件での検索や集計が可能

拡張性が低い

インデックス追加時のテーブルロックが発生する

拡張性/柔軟性が高い

処理が高速

シンプルなデータ構造

データの一貫性が緩い

テーブル結合(JOIN)がない

有用な構成例

 TableStoreの特性を生かした活用例を2つ紹介します。

1. 大容量データとの連携

 TableStoreでは、高速な読み書きやスケーラビリティに特徴がありますが、一方で、単一属性列のサイズが64KBまでという制限があります。そのため、一般的にデータのサイズが大きくなる画像や動画、音声などは、Alibaba CloudのOSSというオブジェクトストレージサービスと併用して使うとより効果を発揮できます。大容量のデータはOSS格納して、TableStoreへはOSSのパス情報などを保存するといいです。

2. 高速アクセスへの対応

例えば、オンラインゲームなど大量のユーザのアクセスログを書き込むサイトがあるとします。アクセスログは後々分析に利用するので、集計しやすいRDBMSに書き込みたいところですが、そのパフォーマンスがネックになることがあります。そのようなときは、一度TableStoreへ書き込み、その後にデータを加工などしてRDBMSへ集計用に格納する、といった使い方も可能です。

基本操作

TableStoreの概念的な話をしたので、実際の利用方法をみていきましょう。

管理コンソール操作

「インスタンスを作成」より、作成します。
注意:インスタンス名はリージョン内で一意であること。

 

作成すると以下のようになります。
インスタンスアクセス URL
インターネット: http://<インスタンス名>.<リージョン名>.ots.aliyuncs.com
イントラネット: http://<インスタンス名>.<リージョン名>.ots-internal.aliyuncs.com

接続制限をするために、VPCをバインド(紐付け)します。

テーブルを作成します。

プライマリーキーの種類は「Integer:整数型」「String:文字列」「バイナリ」から選択することができます。

管理コンソールで設定できるのはここまでです。

SDK基本操作

続いて、SDKを利用して実際にTableStoreの中身を操作していきます。

SDK内のexampleファイルを実行して、基本動作(テーブル作成、行の追加 行の更新、行の取得)を確認してみます。

以下でpython用sdkをインストールします。https://pypi.python.org/pypi/ots2/2.1.0

コマンド例

#yum -y install git
# yum install git
# git clone https://github.com/aliyun/aliyun-tablestore-python-sdk.git
# cd aliyun-tablestore-pyhon-sdk/
# python setup.py  install

バージョン確認をして、環境変数を登録します。

# python
Python 2.7.5 (default, Nov  6 2016, 00:28:07) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import tablestore
>>> tablestore.__version__ #バージョン確認
'4.0.0'
>>> exit()
# export OTS_TEST_ACCESS_KEY_ID=<アクセスキー>
# export OTS_TEST_ACCESS_KEY_SECRET=<シークレットキー>
# export OTS_TEST_ENDPOINT=<管理コンソールで作成したDBのインターネットURL>
# export OTS_TEST_INSTANCE=<DB名>

注意:この時に「ImportError」が出たら、pythonを二重にインストールしていないか確認してください。

exampleファイルを実行してみる

exsampleファイルを試してみます。各コマンドが収納されているフォルダで、「python **.py」を実行します。

update_row.py 

実行内容:テーブル作成、行の追加 行の更新、行の取得(CreateTable、PutRow、UpdateRow、GetRow)

# -*- coding: utf8 -*-

from example_config import *
from tablestore import *
import time

table_name = 'python_sdk_4' #操作(作成)するテーブル名

def create_table(client): #テーブル作成
    schema_of_primary_key = [('gid', 'INTEGER'), ('uid', 'STRING')] #主キーの設定。(列名、数字か文字列か)
    table_meta = TableMeta(table_name, schema_of_primary_key)
    table_options = TableOptions()
    reserved_throughput = ReservedThroughput(CapacityUnit(0, 0))
    client.create_table(table_meta, table_options, reserved_throughput)
    print ('Table has been created.')

def delete_table(client):
    client.delete_table(table_name)
   print ('Table \'%s\' has been deleted.' % table_name)

def put_row(client): #行の追加
    primary_key = [('gid',1), ('uid',"101")]
    attribute_columns = [('name','John'), ('mobile',15100000000), ('address','China'), ('age',20)] #属性列の指定
    row = Row(primary_key, attribute_columns)
    condition = Condition(RowExistenceExpectation.EXPECT_NOT_EXIST) 
  # この行が存在しない場合にのみテーブルに入れます。
    consumed, return_row = client.put_row(table_name, row)
    print ('Write succeed, consume %s write cu.' % consumed.write)

def update_row(client): #行の更新
    primary_key = [('gid',1), ('uid',"101")]
    update_of_attribute_columns = {
        'PUT' : [('name','David'), ('address','Hongkong')],
        'DELETE' : [('address', None, 1488436949003)],
        'DELETE_ALL' : [('mobile'), ('age')],
    }
    row = Row(primary_key, update_of_attribute_columns)
    condition = Condition(RowExistenceExpectation.IGNORE, SingleColumnCondition("age", 20, ComparatorType.EQUAL)) # この行が存在する場合のみ行を更新する
    consumed, return_row = client.update_row(table_name, row, condition) 
    print ('Update succeed, consume %s write cu.' % consumed.write)

def get_row(client): # 行を表示する
    primary_key = [('gid',1), ('uid','101')]
    columns_to_get = ['name', 'address', 'age'] 
  # 取得する列のリストを指定するか、行全体を取得する場合は空のリストを取得します。
    consumed, return_row, next_token = client.get_row(table_name, primary_key, columns_to_get, None, 1)
    print ('Read succeed, consume %s read cu.' % consumed.read)

    print ('Value of attribute: %s' % return_row.attribute_columns)


if __name__ == '__main__':
    client = OTSClient(OTS_ENDPOINT, OTS_ID, OTS_SECRET, OTS_INSTANCE)
    try:
        delete_table(client)
    except:
        pass
    create_table(client)

    time.sleep(3) # wait for table ready
    put_row(client)
    print ('#### row before update ####')
    get_row(client)
    update_row(client)
    print ('#### row after update ####')
    get_row(client)
#    delete_table(client) #今回はスキップ

# python update_row.py 実行結果

Table has been created.
Write succeed, consume 1 write cu.
#### row before update ####
Read succeed, consume 1 read cu.
Value of attribute: [('address', 'China', 1498712974750), ('age', 20, 1498712974750), ('name', 'John', 1498712974750)]
Update succeed, consume 1 write cu.
#### row after update ####
Read succeed, consume 1 read cu.
Value of attribute: [('address', 'Hongkong', 1498712974795), ('name', 'David', 1498712974795)]

cu・・・読み書きスループットは、読み書き容量ユニット (cu) によって測定されます。CU はデータの読み書き操作に対する最小の課金単位です。

どのようなデータを入力して出力されたのか、実行結果だけだとわかりづらいので表で示します。

1.「create_table」と「put_row」で作成されたテーブル『python_sdk_4』

gui uid name mobile address age バージョン番号
1 101 Jhon 15100000000 China 20 1498712974750

2.「update_row」で実行された更新(name列:Jphn→David、address列:China→Hongkong、『mobile』列と『age』列を削除)

gui uid name mobile address age バージョン番号
1 101 David 15100000000 Hongkong 20  1498712974795

3.「get_row」で表示された行。『age』列も指定されていますが、列を削除したため表示されていません。

name address バージョン番号
David Hongkong  1498712974795

batch_get_row.py 

実行内容:存在するテーブルから10行、存在しないテーブルから10行を取得します。

# -*- coding: utf8 -*-

from example_config import *
from tablestore import *
import time

table_name = 'OTSBatchGetRowSimpleExample'

def create_table(client): #テーブル作成
    schema_of_primary_key = [('gid', 'INTEGER'), ('uid', 'INTEGER')]
#主キーの設定。(列名、整数)
    table_meta = TableMeta(table_name, schema_of_primary_key)
    table_option = TableOptions()
    reserved_throughput = ReservedThroughput(CapacityUnit(0, 0))
    client.create_table(table_meta, table_option, reserved_throughput)
    print ('Table has been created.')

def delete_table(client):
    client.delete_table(table_name)
    print ('Table \'%s\' has been deleted.' % table_name)

def put_row(client): #指定した行のデータを挿入
    for i in range(0, 10): #『i』の範囲を指定
        primary_key = [('gid',i), ('uid',i+1)] #主キーの指定
        attribute_columns = [('name','John'), ('mobile',i), ('address','China'), ('age',i)] #書き込む列の名前と中身
        row = Row(primary_key, attribute_columns) #列の順番
        condition = Condition(RowExistenceExpectation.EXPECT_NOT_EXIST) 
     # Expect not exist:この行が存在しない場合にのみテーブルに入れます。
        consumed, return_row = client.put_row(table_name, row, condition) 
     #この操作で消費される容量単位
        print (u'Write succeed, consume %s write cu.' % consumed.write)

def batch_get_row(client):
    # 存在するテーブルから10行、存在しないテーブルから10行を取得します
    columns_to_get = ['name', 'mobile', 'address', 'age']
    rows_to_get = []
    for i in range(0, 10):
        primary_key = [('gid',i), ('uid',i+1)]
        rows_to_get.append(primary_key)

    cond = CompositeColumnCondition(LogicalOperator.AND) #複合列条件(ここで抽出条件指定)
    cond.add_sub_condition(SingleColumnCondition("name", "John", ComparatorType.EQUAL))
    cond.add_sub_condition(SingleColumnCondition("address", 'China', ComparatorType.EQUAL))

    request = BatchGetRowRequest()
    request.add(TableInBatchGetRowItem(table_name, rows_to_get, columns_to_get, cond, 1))
    request.add(TableInBatchGetRowItem('notExistTable', rows_to_get, columns_to_get, cond, 1))

    result = client.batch_get_row(request)

    print ('Result status: %s'%(result.is_all_succeed()))
    
    table_result_0 = result.get_result_by_table(table_name)
    table_result_1 = result.get_result_by_table('notExistTable')

    print ('Check first table\'s result:')     
    for item in table_result_0:
        if item.is_ok:
            print ('Read succeed, PrimaryKey: %s, Attributes: %s' % (item.row.primary_key, item.row.attribute_columns))
        else:
            print ('Read failed, error code: %s, error message: %s' % (item.error_code, item.error_message))

    print ('Check second table\'s result:')
    for item in table_result_1:
        if item.is_ok:
            print ('Read succeed, PrimaryKey: %s, Attributes: %s' % (item.row.primary_key, item.row.attribute_columns))
        else:
            print ('Read failed, error code: %s, error message: %s' % (item.error_code, item.error_message))

if __name__ == '__main__':
    client = OTSClient(OTS_ENDPOINT, OTS_ID, OTS_SECRET, OTS_INSTANCE)
    try: 
        delete_table(client)
    except:
        pass

    create_table(client)

    time.sleep(3) # wait for table ready
    put_row(client)
    batch_get_row(client)
#    delete_table(client) #今回はスキップ

こちらも表を使って動きを示します。

1.「create_table」と「put_row」で作成されたテーブル『OTSBatchGetRowSimpleExample』 ※「i」は0から10の整数

gid uid name mobile address age バージョン番号
 0 1 Jhon 0 China 0 1498800570940
 1 2 Jhon 1 China 1 1498800570990
〜省略〜
 9 10 Jhon 9 China 9 1498800571012

2.「 batch_get_row」で取得した結果(条件が2つあります)

条件1:『OTSBatchGetRowSimpleExample』テーブルから、「i」の範囲が0から10で、「name列=John」と「address=China」の条件に合う、「gid」「uid」「name」「’mobile」「address」「age」列を表示します。実行すると、1.で作成された内容が表示されます。

条件2:存在しない『notExistTable』から、同条件で表示しようとするので、エラーになります。

#python batch_get_row.py 実行結果

Table has been created.
Write succeed, consume 1 write cu.
〜省略(10回表示されます)〜
Result status: False
Check first table's result:
Read succeed, PrimaryKey: [('gid', 0), ('uid', 1)], Attributes: [('address', 'China', 1498800570940), ('age', 0, 1498800570940), ('mobile', 0, 1498800570940), ('name', 'John', 1498800570940)]
Read succeed, PrimaryKey: [('gid', 1), ('uid', 2)], Attributes: [('address', 'China', 1498800570990), ('age', 1, 1498800570990), ('mobile', 1, 1498800570990), ('name', 'John', 1498800570990)]
Read succeed, PrimaryKey: [('gid', 2), ('uid', 3)], Attributes: [('address', 'China', 1498800570993), ('age', 2, 1498800570993), ('mobile', 2, 1498800570993), ('name', 'John', 1498800570993)]
Read succeed, PrimaryKey: [('gid', 3), ('uid', 4)], Attributes: [('address', 'China', 1498800570996), ('age', 3, 1498800570996), ('mobile', 3, 1498800570996), ('name', 'John', 1498800570996)]
Read succeed, PrimaryKey: [('gid', 4), ('uid', 5)], Attributes: [('address', 'China', 1498800570998), ('age', 4, 1498800570998), ('mobile', 4, 1498800570998), ('name', 'John', 1498800570998)]
Read succeed, PrimaryKey: [('gid', 5), ('uid', 6)], Attributes: [('address', 'China', 1498800571001), ('age', 5, 1498800571001), ('mobile', 5, 1498800571001), ('name', 'John', 1498800571001)]
Read succeed, PrimaryKey: [('gid', 6), ('uid', 7)], Attributes: [('address', 'China', 1498800571004), ('age', 6, 1498800571004), ('mobile', 6, 1498800571004), ('name', 'John', 1498800571004)]
Read succeed, PrimaryKey: [('gid', 7), ('uid', 8)], Attributes: [('address', 'China', 1498800571007), ('age', 7, 1498800571007), ('mobile', 7, 1498800571007), ('name', 'John', 1498800571007)]
Read succeed, PrimaryKey: [('gid', 8), ('uid', 9)], Attributes: [('address', 'China', 1498800571009), ('age', 8, 1498800571009), ('mobile', 8, 1498800571009), ('name', 'John', 1498800571009)]
Read succeed, PrimaryKey: [('gid', 9), ('uid', 10)], Attributes: [('address', 'China', 1498800571012), ('age', 9, 1498800571012), ('mobile', 9, 1498800571012), ('name', 'John', 1498800571012)]
Check second table's result:
Read failed, error code: OTSObjectNotExist, error message: Requested table does not exist.
〜省略(10回表示されます)〜

まとめ

TableStoreはRDBと違い、利用者でインスタンスのスケールを気にする必要は必ありません。データベースでのパフォーマンスのボトルネックの解消にうまく役立てると思います。
IoT機器で集めた「とりあえずためておきたいビッグデータ」や、頻繁に読み取るデータなどに活用することができます。代わりに複雑な処理は苦手なので、その部分はRDBMSに任せるような構成がおすすめです。

この記事をシェアする