2011年6月22日水曜日

Amazon SQSってなんじゃ?(その2 PHP SDK編)

前回、SQSをscratchpadから使用してみましたが、今回はPHPから利用してみます。

送信側

まずメッセージの送信側は以下のような流れになります。
  1. 指定したキュー名のキューオブジェクトを作る
  2. メッセージを送る

では早速コードを書いてみます。SDKのダウンロードと配置はSimpleDBの回と同様なので割愛します。

send_message.php
<?php

//ダウンロードしたSDKディレクトリのsdl.class.phpをロードします。
require_once('../../../sdk/sdk.class.php');
//アクセス用のキーを定義します。
$access_key = 'xxxxxxxxxxxxxx';
$secret_key = 'xxxxxxxxxxxxxxxxxxxxxxxx';
//キーをもとにSDKのSQSアクセサクラスを初期化します。
$sqs = new AmazonSQS($access_key, $secret_key);
//キュー名を定義します。
$queueName = 'moge';
//キュー名からキューURLを取得します。
$queueURL = $sqs->create_queue($queueName)->body->CreateQueueResult->QueueUrl;
//キューURLを指定してメッセージを送ります。
$data = $sqs->send_message($queueURL, 'mamimumemo');

//戻り値から成否を判断します。
if($data->isOK()){
  print_r('成功〜');
}
else{
  print_r('失敗〜');
}
?>
たったこれだけです。SimpleDBのときよりはるかにシンプルです。簡単。


受信側

次に受信側です。受信側の流れは以下が一般的です。
  1. 指定したキュー名のキューオブジェクトを作る
  2. メッセージの待機
  3. メッセージを受け取ったら処理
  4. メッセージを削除

受信側は送信側とくらべてやや特殊です。
なぜなら受信側はメッセージを待機し続ける必要があるため、メモリに常駐したワーカープロセスである場合がほとんどです。また、待機のためのポーリングなども必要です。

受信側のコードは以下のような形になります。
recieve_message.php
#!/usr/bin/php
<?php
//ダウンロードしたSDKディレクトリのsdl.class.phpをロードします。
require_once('../../../sdk/sdk.class.php');
//アクセス用のキーを定義します。
$access_key = 'xxxxxxxxxxxxxx';
$secret_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
//キーをもとにSDKのSQSアクセサクラスを初期化します。
$sqs = new AmazonSQS($access_key, $secret_key);
//送信側と同じキュー名を定義します。
$queueName = 'moge';
//キュー名からキューURLを取得します。
$queueURL = $sqs->create_queue($queueName)->body->CreateQueueResult->QueueUrl;

//メッセージを待機します
while(true){
 //メッセージの取得を試みます。
  $data = $sqs->receive_message($queueURL);
 //正常受信
  if($data->isOK()){
   //メッセージがあったら処理  
    if(isset($data->body->ReceiveMessageResult->Message)){
     //メッセージを取り出します。
      $msgdata = $data->body->ReceiveMessageResult->Message;
      $msg = $msgdata->Body;
     /*
      *ここで何か処理を行います。
      */
     
     //削除用のポインタを取得します。
      $handle = (string)$msgdata->ReceiptHandle;
     //メッセージを削除します。
      $sqs->delete_message($queueURL, $handle);
      print_r($msg . "\n");
    }
   //メッセージがなければ1秒待機します。
    else{
      sleep(1);
    }
  }
 //受信異常
  else{
   //エラーを出力します。
    print_r($data->body->Error->Message . "\n");
  }
}
exit(0);
?>
このPHPのコードはワーカプロセスにするため、コマンドラインからスクリプト起動します。
$ php recieve_message.php

このようにSQSはとてもシンプルなサービスですが、SimpleDBなどのほかのサービスとあわせて利用することでいろいろな用途に使え、とても便利です。

Amazon SQSってなんじゃ?(その1 Scratchpad編)

SimpleDBの回は途中で一休みして、SQSでも遊んでみました。
Amazon SQS(Simple Queue Service)は、AWSが提供するメッセージキューシステムです。
メッセージキューは、送信側と受信側が別のプロセスで非同期にメッセージのやりとりを行い、送信と受信の間でお互いの処理待ちをなくすことのできる仕組みをもっています。
これはたとえばWEBアプリケーションなどで、画像生成などの重い処理をするときに処理のタスクだけを指示して、レスポンスをすぐ返すことによりクライアントに処理待機をさせないようにするときなどに有用です。

SQSではメッセージの送信、受信側での処理は以下のような流れになります。
使い方はとてもシンプルで、送信側でメッセージを送り、受信側でメッセージの受信を待機するだけです。

送信側
  1. 指定したキュー名のキューオブジェクトを作る
  2. メッセージを送る

受信側
  1. 指定したキュー名のキューオブジェクトを作る
  2. メッセージの待機
  3. メッセージを受け取ったら処理
  4. メッセージを削除

さっそく利用してみます。
まずSQSのページで利用申し込みします。

申し込むと登録完了メールが届いて、すぐつかえるようになります。

このSQSにもscratchpadというJavascriptで書かれたツールがあるので、使用してみます。
Javascript Scratchpad for Amazon SQSをダウンロードし、解凍したフォルダのindex.htmlをブラウザで開きます。

ヘッダにアクセスキーとシークレットアクセスキーを入力して、右側のリストから仕様したいAPIを選びます。

キューの作成

まず送信側の処理に必要なキューの作成をします。右側のAPIリストから"CreateQueue"を選択します。


パラメータは以下のとおりです。
  • QueueName(必須):キュー名。SQSではキューはラジオのチャンネルのようなもので自分が使用するSQSインスタンスの中でユニークである必要があります。あるキュー名を指定するといつでも同じキューが取得でき、同じキューに対してメッセージの送受信を行います。
  • Default Visibility Timeout(オプション):受信側クライアントのインスタンスが複数ある場合、1つのインスタンスがメッセージを受信するとここで指定した時間(秒)の間は、他のインスタンスからは隠されます。それによってその間重複することなく受信処理を完了することができます。デフォルトは30秒で、非常に重い処理をする場合はこの秒数を多くします。

ここではQueueNameに"hoge"と入力し、Invoke Requestを押します。
すると、以下のようなレスポンスが返ります。
<CreateQueueResponse>
  <CreateQueueResult>
    <QueueUrl>https://queue.amazonaws.com/821635308497/hoge</QueueUrl>
      </CreateQueueResult>
    <ResponseMetadata>
    <RequestId>e490c631-b519-4e02-a610-55ab66b2fac2</RequestId>
  </ResponseMetadata>
</CreateQueueResponse>
ここでQueueURLというのが"hoge"というキューにアクセスするための識別子になります。このURLはキュー名に対し一意なもので、送信するときも受信するときにもこのURLを指定してメッセージのやりとりを行います。CreateQueueを何度行ってもキュー名が同じならばURLは同じものになります。つまりCreateQueueというのはキューをいくつもつくるのではなく、与えたキュー名から一意なキューURLを取得する処理だといえます。これは76.1MHzにあわせれば毎回interfmにつながるのと同じです。

メッセージの送信

次はメッセージの送信です。ヘッダのAPIリストから"SendMessage"を選択します。
パラメータは以下の2つです。
Queue Url(必須)::キューの作成で取得したキューURLを指定します。メッセージはこのURLをキューの識別子として送信され、受信側でもこのURLを指定して受信します。
Message Body(必須):メッセージの内容です。

ここではQueueUrlに先ほど作成した"hoge"というQueueのQueuUrl "
https://queue.amazonaws.com/821635308497/hoge"を、MessageBodyには"Hello World!"を入力してInvoke Requestを押します。
すると以下のようなレスポンスが返ります
<SendMessageResponse>
  <SendMessageResult>
    <MD5OfMessageBody>ed076287532e86365e841e92bfc50d8c</MD5OfMessageBody>
    <MessageId>f5735801-1b32-44f6-a86b-416a04bb969a</MessageId>
  </SendMessageResult>
  <ResponseMetadata>
    <RequestId>233b5572-e2a6-4e30-8ee3-27250276168f</RequestId>
  </ResponseMetadata>
</SendMessageResponse>
これでメッセージがキューに送られました。

メッセージの受信

次にメッセージを受信してみます。
APIリストから"ReceiveMessage"を選びます。


パラメータが以下のとおりです。
  • QueueUrl(必須):送信時と同じキュー名に該当するキューURLを指定します。CreateQueueで取得します。
  • Max Number Of Messages(オプション):取得する最大のメッセージ件数を指定します。デフォルトは1です。一度にたくさんのメッセージを処理したい場合はこれを増やします。
  • Visibility Timeout(オプション):一度メッセージを受信するとこの秒数の間は、他のインスタンスのReceiveMessageリクエストからこのメッセージを隠します。デフォルトはキューのVisibility Timeoutと同じになります。
 ここではQueueUrlに先ほどの送信時のキューURL"https://queue.amazonaws.com/821635308497/hoge"を指定して、InvokeRequestを押すと以下のレスポンスが返ります。
<ReceiveMessageResponse>
  <ReceiveMessageResult>
    <Message>
      <MessageId>f5735801-1b32-44f6-a86b-416a04bb969a</MessageId>
      <ReceiptHandle>0NNAq8PwvXtwwfdMtBtd6ZxXkxF39Z/IYeGA072QQSZ0DJ3gVOmjI2Gh/oFnb0IeJqy5Zc8kH4JARkzdFBpldzaAPSeOkXQZwFSsJUhd5VMVLzK3+eRCyfzyRC5taEby2sLs6d2PAFOgJcNQRNWCRx2ZBrIVvHrDEKIvb6EDD5Mzd5Tn7Sx1Kdc/TnR7K9fu</ReceiptHandle>
      <MD5OfBody>ed076287532e86365e841e92bfc50d8c</MD5OfBody>
      <Body>Hello World!</Body>
    </Message>
  </ReceiveMessageResult>
    <ResponseMetadata>
      <RequestId>5115c13c-5c58-4f35-aae0-505c892a9c00</RequestId>
    </ResponseMetadata>
</ReceiveMessageResponse> 
ここで、ReceiveMessageResultの中にMessage要素が含まれており、その中のBody要素に先ほど送信した"Hello World!"が確認できます。このBody要素がメッセージ本体になります。
また、 Message要素の中のReceptHandleはメッセージの削除に使用されます。

メッセージの削除

メッセージは受信しただけでは削除されません。
通常メッセージは受信した後に明示的に削除する必要があります。
APIリストからDeleteMessageを選択します。


パラメータは以下の2つです。
Queue Url(必須):削除したいメッセージが含まれているキューのURLです。送信や受信時のキューURLと同じものです。
Recept Handle(必須):受信したメッセージに含まれるReceptHandleを指定します。これはそのキューの中のメッセージへのポインタのようなものです。

QueueUrlに受信時のキューURLとして指定したのと同じ"https://queue.amazonaws.com/821635308497/hoge"、Recept Handleに受信のレスポンスに含まれていたReceptHandleである"0NNAq8PwvXtwwfdMtBtd6ZxXkxF39Z/IYeGA072QQSZ0DJ3gVOmjI2Gh/oFnb0IeJqy5Zc8kH4JARkzdFBpldzaAPSeOkXQZwFSsJUhd5VMVLzK3+eRCyfzyRC5taEby2sLs6d2PAFOgJcNQRNWCRx2ZBrIVvHrDEKIvb6EDD5Mzd5Tn7Sx1Kdc/TnR7K9fu"を指定して、Invoke Requestを押します。

すると以下のレスポンスが返り、メッセージが削除されます。
<DeleteMessageResponse>
  <ResponseMetadata>
    <RequestId>bb6333a8-bc6f-4151-ace4-efbca103316d</RequestId>
  </ResponseMetadata>
</DeleteMessageResponse>

他にもいくつかAPIが存在しますが、通常メインで使用するのは、上記の4つになります。
次回はこれをプログラムから使用してみます。

2011年6月20日月曜日

Amazon SimpleDBってなんじゃ?(その2 PHP SDK編)

前回、SimpleDBをscratchpadから使用してみましたが、実際にはプログラムからSDKを通じて使用するケースがほとんどだと思うので、今回はPHPのSDKを使用してアイテム(レコード)のCRUD(作成、読込、更新、削除)をしてみたいと思います。

準備

今回はPHPのSDKを使用するので、ここからSDKをダウンロードしてロードしやすい場所に配置します。
$ mkdir src
$ cd src
$ wget http://pear.amazonwebservices.com/get/sdk-latest.zip
$ unzip sdk-latest.zip
$ cd sdk-1.3.4
$ mkdir /var/www/html/api
$ mv sdk-1.3.4 /var/www/html/api/
$ cd /var/www/html/api
$ ln -s sdk-1.3.4 sdk
SDKのドキュメンテーションを参考にいろいろなクエリを試して見ます。


ドメイン(テーブル)の新規作成

パラメータは  前回、ドメイン(テーブル)を作成した際と同様ドメイン名です。
<?php
  
  //ダウンロードしたSDKディレクトリのsdl.class.phpをロードします。
  require_once '../../sdk/sdk.class.php';
  //アクセス用のキーを定義します。
  $access_key = "xxxxxxxxxxxxxxxxxx";
  $secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
  //キーをもとにSDKのSDBアクセサクラスを初期化します。
  $sdb = new AmazonSDB($access_key, $secret_key);
  //ドメイン作成します。
  $data = $sdb->createDomain("member");
  //戻り値から成否を判断し、成功なら戻り値のダンプを返します。
  if($data->isOK()){
    print_r($data);
  }
  else{
    print_r("失敗〜");
  }
?>
成功した場合、戻り値のオブジェクトのisOK()がtrueで返ります。
戻り値のダンプは以下のとおりです。
CFResponse Object
(
    [header] => Array
        (
            [content-type] => text/xml
            [transfer-encoding] => chunked
            [content-encoding] => gzip
            [vary] => Accept-Encoding
            [date] => Mon, 20 Jun 2011 08:09:44 GMT
            [server] => Amazon SimpleDB
            [_info] => Array
                (
                    [url] => https://sdb.amazonaws.com/
                    [content_type] => text/xml
                    [http_code] => 200
                    [header_size] => 205
                    [request_size] => 691
                    [filetime] => -1
                    [ssl_verify_result] => 0
                    [redirect_count] => 0
                    [total_time] => 2.144966
                    [namelookup_time] => 0.208893
                    [connect_time] => 0.403431
                    [pretransfer_time] => 0.804195
                    [size_upload] => 0
                    [size_download] => 193
                    [speed_download] => 89
                    [speed_upload] => 0
                    [download_content_length] => 0
                    [upload_content_length] => 0
                    [starttransfer_time] => 2.144638
                    [redirect_time] => 0
                    [method] => POST
                )

            [x-aws-stringtosign] => 略
            [x-aws-request-headers] => Array
                (
                    [Content-Type] => application/x-www-form-urlencoded; charset=utf-8
                )

            [x-aws-body] =略
        )

    [body] => CFSimpleXML Object
        (
            [@attributes] => Array
                (
                    [ns] => http://sdb.amazonaws.com/doc/2009-04-15/
                )

            [ResponseMetadata] => CFSimpleXML Object
                (
                    [RequestId] => 673f1239-14be-ff54-9115-8ede692b7dc9
                    [BoxUsage] => 0.0055590278
                )

        )

    [status] => 200
)
このようにアクションの戻り値はヘッダ、ボディ、ステータス部に分かれて構成されたXMLのオブジェクトとして返され、実際のレスポンスデータはボディ部に含まれます。
このアクションはドメイン作成でデータの取得ではないため、返却値はなくメタデータのみが返されます。
また、戻り値のisOKメソッドでアクションの成否の判断ができます。


アイテム(レコード)の追加

SimpleDBではアイテムの追加と更新は同じメソッドを利用します。
アイテムの追加は、ドメイン名(テーブル名)とアイテム名(プライマリキー)と各属性(カラム名)とその値の連想配列をパラメータとします。戻り値はドメインの作成と同様更新アクションなので、返却値はなくメタデータのみになります。
<?php

  require_once '../../sdk/sdk.class.php';

  $access_key = "xxxxxxxxxxxxxxxxxx";
  $secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

  $sdb = new AmazonSDB($access_key, $secret_key);
  
  //1件ずつのインサート
  $data1 = $sdb->put_attributes('member',//ドメイン名
                                     'memorycraft',//アイテム名(プライマリキー)
                                     array(//各属性名と値の連想配列
                                       'country' => 'Japan',
                                       'location' => 'Tokyo',
                                     )
                                );

  /*
   * 複数件同時にインサート
   * ・ドメイン名
   * ・アイテム名(プライマリキー)をキーにした属性と値の連想配列
  */
  $data2 = $sdb->batchPutAttributes('shop', //ドメイン名
                                            array('David' => array('country' => 'USA',
                                                               'location' => 'Seattle'
                                                              ),
                                                 'Satoru' => array('country' => 'Japan',
                                                              'location' => 'Saitama'
                                                              )
                                            )//アイテム名をキーにした属性と値の連想配列
                                   );

    if($data1->isOK() && $data2->isOK()){
      print_r($data1);
      print_r($data2);
    }
    else{
      print_r("失敗〜");
    }

?>
値のセットは、1件の場合はput_attributeメソッドで、複数アイテム同時にセットする場合はbatchPutAttributesメソッドを使用します。

最後の引数としてreplace引数をtrue/falseで追加すると、指定したアイテム名が該当するアイテムが既に存在した場合の挙動をコントロールできます。replace引数はデフォルトfalseです。
//普通のインサート
$data1 = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => 'Tokyo',
                                     )
                                );
//値を変えて再投入
$data2 = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => 'Chiba',
                                     )
                                );
この場合、2回目のput_attributeの時点では既にmemorycraftというアイテムは存在しているので、location属性はTokyoという値を上書きせずに更にChibaという値も持つことになります。SELECTするとlocation属性の値としてTokyo, Chibaの2つが返ることがわかります。

また、最初から複数値をセットすることもできます。
$data = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => array('Tokyo', 'Chiba'),
                                     )
                                );

このようにデフォルトの状態では既存アイテムに対して複数値のセットになってしまうことを避けたい場合は、put_attributeの第4引数であるreplace引数をtrueにセットします。
//普通のインサート
$data1 = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => 'Tokyo',
                                     )
                                );
//値を変えて再投入
$data2 = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => 'Chiba',
                                     ),
                                     true//replace引数をtrueに
                                );
この場合、locationは複数値にならずTokyoがChibaに変更されます。


アイテム(レコード)の検索

アイテムの検索はRDBでいうところのSELECTにあたります。
戻り値には検索結果としての返却値が含まれています。
>?php
  require_once '../../sdk/sdk.class.php';

  $access_key = "xxxxxxxxxxxxxxxxxx";
  $secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";

  $sdb = new AmazonSDB($access_key, $secret_key);
  $sql = "select * from member where country = 'Japan'";//country属性がJapanのものを検索
  $data = $sdb->select($sql, array("ConsistentRead" => "true"));

    if($data->isOK()){
      print_r($data);
    }
    else{
      print_r("失敗〜");
    }

?<

この場合、戻り値$dataのダンプは以下のようになります。
CFResponse Object
(
    [header] => Array
        (
            [content-type] => text/xml
            [transfer-encoding] => chunked
            [content-encoding] => gzip
            [vary] => Accept-Encoding
            [date] => Mon, 20 Jun 2011 11:19:32 GMT
            [server] => Amazon SimpleDB
            [_info] => Array
                (
                    [url] => https://sdb.amazonaws.com/
                    [content_type] => text/xml
                    [http_code] => 200
                    [header_size] => 205
                    [request_size] => 753
                    [filetime] => -1
                    [ssl_verify_result] => 0
                    [redirect_count] => 0
                    [total_time] => 0.808396
                    [namelookup_time] => 0.002231
                    [connect_time] => 0.195101
                    [pretransfer_time] => 0.595389
                    [size_upload] => 0
                    [size_download] => 285
                    [speed_download] => 352
                    [speed_upload] => 0
                    [download_content_length] => 0
                    [upload_content_length] => 0
                    [starttransfer_time] => 0.808132
                    [redirect_time] => 0
                    [method] => POST
                )

            [x-aws-stringtosign] => 略
            [x-aws-request-headers] => Array
                (
                    [Content-Type] => application/x-www-form-urlencoded; charset=utf-8
                )

            [x-aws-body] => 略
        )

    [body] => CFSimpleXML Object
        (
            [@attributes] => Array
                (
                    [ns] => http://sdb.amazonaws.com/doc/2009-04-15/
                )

            [SelectResult] => CFSimpleXML Object
                (
                    [Item] => Array
                        (
                            [0] => CFSimpleXML Object
                                (
                                    [Name] => memorycraft
                                    [Attribute] => Array
                                        (
                                            [0] => CFSimpleXML Object
                                                (
                                                    [Name] => location
                                                    [Value] => Tokyo
                                                )

                                            [1] => CFSimpleXML Object
                                                (
                                                    [Name] => country
                                                    [Value] => Japan
                                                )

                                        )

                                )

                            [1] => CFSimpleXML Object
                                (
                                    [Name] => Satoru
                                    [Attribute] => Array
                                        (
                                            [0] => CFSimpleXML Object
                                                (
                                                    [Name] => location
                                                    [Value] => Saitama
                                                )

                                            [1] => CFSimpleXML Object
                                                (
                                                    [Name] => country
                                                    [Value] => Japan
                                                )

                                        )

                                )

                        )

                )

            [ResponseMetadata] => CFSimpleXML Object
                (
                    [RequestId] => d2c6f796-5d0d-cc2f-3b63-e461e108d38e
                    [BoxUsage] => 0.0000320033
                )

        )

    [status] => 200
)
このように、body要素のSearchResultの中に検索結果が出力されてきます。
この場合はアイテム(レコード)が2件取得されたので、Item要素はCFSimpleXML Objectの配列になりますが、1件の場合は配列ではなくCFSimpleXML Objectになります。

これらはXMLオブジェクトなどを含んでいるため結果セットのパースや操作がやや面倒です。
そのため、こちらのサンプルのようにレスポンスを連想配列セットに再構築する汎用メソッドやライブラリなどを用意しておくと便利です。
$data = $sdb->select($sql, array("ConsistentRead" => "true"));//クエリ実行
$data = reorganize_data($data->body->Item());//戻り値を再構築
print_r($data["rows"][0]["country"]);//1番目のアイテムの国を出力

また、SimpleDBはデフォルトでは非アトミックなため、SELECTなどでその前の更新内容の伝播が遅れていても読み込まれる場合があります。それを防ぐためには、このサンプルのように第2引数のオプション配列でConsistentReadを有効にするように指定します。ポイントはTRUEを文字列として渡すことです。ブール値で渡すとエラーが発生します。
$data = $sdb->select($sql, array("ConsistentRead" => true));//エラーが発生
$data = $sdb->select($sql, array("ConsistentRead" => "true"));//正しい指定のしかた

アイテム名(プライマリキー)を直接参照、指定する際はitemName()という関数を使用します。
$sql = "select * from member where itemName() = 'memorycraft'";

そのほかSELECTで使用できるクエリ構文に関してはSimpleDBのデベロッパーガイドが参考になります。


続き(アイテムの更新、削除)はまた次回~

2011年6月17日金曜日

Amazon SimpleDBってなんじゃ?(その1 Scratchpad編)

いまさらですが、さいきんAWSで遊んでます。AmazonWebServiceというAmazonが貸し出すインフラをAPIを使って構築できるダイナミックなサービスです。

その中でSimpleDBというサービスがあります。普段ぼくらが使っているMySQLやOracleなどのリレーショナルデータベースとはちがって、結合や複雑なトランザクションがなく、単体の表がたくさんあるような、エクセルに似たデータの持ち方をしています。

つまり複雑さを捨てた代わりに、スピードや拡張性を追求しているということができます。

SimpleDBはエクセルやRDBMSに該当する以下の概念があります。
  • ドメイン:エクセルではシート、DBでいうテーブル
  • アイテム:エクセルでは行、DBではレコード
  • アイテムネーム:プライマリキー
  • 属性:エクセルでは列、DBではカラム
そのほかいろいろ制限やクセはあるようですが、とりあえず遊んでみました。


既にAWSのアカウントを保持していることが前提ですが、
SimpleDBのページで利用申し込みをします。




 申し込むと登録完了メールが届いて、すぐつかえるようになります。

 普通はプログラムから使用するんですが、その前にまず簡単なクライアントツールが用意されているので、それで触ってみます。

Javascript Scratchpad for Amazon SimpleDBというツールをダウンロードして、解凍した中にあるindex.htmlをブラウザで開きます。

ヘッダにアクセスキーとシークレットアクセスキーを入力して、右側のリストから仕様したいAPIを選びます。



ドメイン(テーブル)の作成

まずは表(ドメイン)を作りたいので、"CreateDomain"を選択します。これはDBでいうところのcreate tableに該当します。

Domain Name(テーブル名)を入力し、"Display Signed URL"を押すと、APIリクエストのURLが表示され、"Invoke Request"を押すと実際にそのURLで別窓をターゲットにドメイン生成のAPIアクセスが行われ、XMLのレスポンスが返ります。
ここではDomain Nameに"member"と入力しました。

 すると、
<CreateDomainResponse>
  <ResponseMetadata>
    <RequestId>813d1434-dc52-b4ae-2616-0f3e57dbfcec</RequestId>
    <BoxUsage>0.0055590278</BoxUsage>
  </ResponseMetadata>
</CreateDomainResponse>
というレスポンスが返ってきました。

まだ慣れていないので、このレスポンスで正常なのかわかりません。
ドメインの一覧を見て"member"ドメインが作成されたか調べてみましょう。



ドメイン(テーブル)の一覧

今度はヘッダ右のリストから"ListDomains"というAPIを選択します。これは現在登録中のドメインの一覧を取得するものです。mysql でいうところのshow tablesでしょうか。


ここで、2つの入力項目"Max Number Of Domains" と "Next Tokens"があります。
"Max Number Of Domains"はそのまま取得したい最大件数の制限をかけるものです。
"Next Token"に関しては後ほどアイテムの一覧で説明します。

ここではMax Number Of Domainsに10と入れて"Invode Request"を押します。
すると以下のようなXMLレスポンスが返ります。
<ListDomainsResponse>
  <ListDomainsResult>
    <DomainName>member</DomainName>
  </ListDomainsResult>
  <ResponseMetadata>
    <RequestId>1af6eec5-5ab5-2b84-c4f1-7a731f874dab</RequestId>
    <BoxUsage>0.0000071759</BoxUsage>
  </ResponseMetadata>
</ListDomainsResponse>
ここで、さっきのCreateDomainのレスポンスとの違いを見ると、レスポンスの意味がわかってきました。ListDomainsResultという要素がDomainNameという要素を1つ含みDomainNameはmemberというテキストを持っています。これが実際に知りたかったドメイン一覧の部分で、残りのResponseMetadataの部分は、それ以外のメタデータのようです。つまりさっきのCreateDomainは返却値のあるクエリではなく更新のクエリだったため、メタデータのみが返ってきたということがわかります。この感じはDBのSELECTとDDLの関係にとても似ています。



アイテム(行)の追加

 "member"というドメインが正常に作成されたようなので、次は行(アイテム)を追加していきましょう。APIリストから"PutAttribute"を選びます。


 上部にDomain NameとItem Nameという項目があります。memberドメインに行(アイテム)を追加するので、"member"と入力します。Item Nameですが、これはmemberドメインのプライマリキーにあたるものです。member内で一意ならばなんでもいいので、メールアドレスなどでもいいですが、ここではIDっぽく1と入れておきます。

そして、以下Attributeというブロックがあり、Name、Value、Replaceという項目があり、右上の+と-のマークがあります。ここで属性(カラム)を好きなだけ追加します。
ここでは、nameとcountryの属性を設定します。

まず最初のAttributeのボックスにname属性を追加します。
Name:name
Value:memorycraft
Replace:
そして、右上の+ボタンを押し、次のAttributeボックスにcountry属性を追加します。
Name:country
Value:Japan
Replace:
このように属性名とその値をセットで登録して、"Invoke Request"を押します。するとCreateDomainのときと同じようにResponseMetadataだけのレスポンスが返ってきます。更新系のクエリのためです。

このようにして以下のように3アイテム追加しました。

ItemName, name, country
1, memorycraft, Japan
2, David, USA
3, Pierre, France

これらが正常に登録されているか見てみましょう。



アイテムの検索

さきほどはドメインの一覧をListDomainsで調べました。これは、create table したものをshow tablesで調べるようなものです。今度は行を調べるのでselect文にあたるクエリを使います。
SimpleDBには、SQLと似たSelect構文があり、それを使います。

APIリストからSelectを選びます。

 ここには
"Select Expression"、"Next Token"、"Consistent Read"という項目があります。
ここでは"Select Expression"に単純に"select * from member"と入力して結果を見てみます。
<SelectResponse>
  <SelectResult>
    <Item>
      <Name>1</Name>
      <Attribute><Name>name</Name><Value>memorycraft</Value></Attribute>
      <Attribute><Name>country</Name><Value>Japan</Value></Attribute>
    </Item>
    <Item>
      <Name>2</Name>
      <Attribute><Name>name</Name><Value>David</Value></Attribute>
      <Attribute><Name>country</Name><Value>USA</Value></Attribute>
     </Item>
     <Item>
       <Name>3</Name>
       <Attribute><Name>name</Name><Value>Pierre</Value></Attribute>
       <Attribute><Name>country</Name><Value>France</Value></Attribute>
      </Item>
    </SelectResult>
  <ResponseMetadata>
    <RequestId>694e492f-feec-e860-258e-29086b644585</RequestId>
    <BoxUsage>0.0000411449</BoxUsage>
  </ResponseMetadata>
</SelectResponse>
このように3件登録されているのがわかります。

ここで、"NextToken"とはなんでしょうか。
調べてみると、次の結果セットへのポインタのようなものだとわかりました。
SimpleDBには、スピードや冗長性を保つために他のものを犠牲にしており、たとえば1度のクエリ実行によりフェッチできる件数は最大2500件までです。
そのため、SimpleDBでは結果の一部だけを取得した場合、APIレスポンスの中にそれに続く結果セットへのポインタとしてNextTokenという文字列を含めて返却します。

たとえば、この場合select * from member limit 2というクエリを発行した場合、レスポンスには2件分の結果セットのほかに以下のような要素が入っています。
<NextToken>rO0ABXNyACdjb20uYW1hem9uLnNkcy5RdWVyeVByb2Nlc3Nvci5Nb3JlVG9rZW7racXLnINNqwMA
C0kAFGluaXRpYWxDb2ddGVudExTTnQAEkxqYXZhL2xhbmcvU3Ry
aW5nO0wAEmxhc3RBdHRyaWJ1dGVWYWx1ZXEAfgABTAAJc29ydE9yZGVydAAvTGNvbS9hbWF6b24v
c2RzL1F1ZXJ5UHJvY2Vzc29yL1F1ZXJ5JFNvcnRPcmRlcjt4cAAAAAAAAAAAAAAAAAIAAAAAAAAA
AAAAAAAAAAAAAABwcHB4</NextToken>
そして、次の結果セットを取得する場合は、このNextTokenをセットして同じクエリで、再度クエリ実行します。

すると、残りの1件が以下のように取得することができます。
<SelectResponse>
  <SelectResult>
    <Item>
      <Name>3</Name>
      <Attribute><Name>name</Name><Value>Pierre</Value></Attribute>
      <Attribute><Name>country</Name><Value>France</Value></Attribute>
    </Item>
  </SelectResult>
  <ResponseMetadata>
    <RequestId>ac269ea7-4733-8da2-6705-41a069a88d0f</RequestId>
    <BoxUsage>0.0000228616</BoxUsage>
  </ResponseMetadata>
</SelectResponse>
複数に分割された結果セットにおいて、NextToken要素は、最後の結果セットになるまでレスポンスに含まれ、常にその次の結果セットへのポインタを示します。

つまり、APIをプログラム内で仕様する際には、すべての結果を一度に表示したい場合、NextTokenがなくなるまでループしながら結果の取得をするようなロジックを組む必要があります。
また、ページネーションを明示的に行う場合も、limit句とNextTokenを組み合わせて使用します。

また、"Consistent Read"という項目も、同様にスケーラビリティを優先したためあえて捨てられたデータの一貫性に関する項目です。分散されたデータストレージへ更新が伝播しきるまえに取得されると最新のデータではないものが取得されるようになっており、それでスピードを出しているのですが、それではまずい場合もあります。
その場合はこの"Consistent Read"をtrueに設定することにより、取得スピードは遅くなるけれども最新のデータを取得できるようになっています。
 あまり使いすぎるとSimpleDBのよさがなくなってしまうので、要所で使うのが好ましいようです。
また、結局全部trueになってしまった、という場合はそもそもSimpleDBではなくRDBMSを使用するこを検討したほうがいいかもしれません。


今日はここまでー。
いきなりがんばりすぎて続く気がしない。。。

2011年6月16日木曜日

memorycraftふたたび

なんかxreaにのっけてた前のMTブログがsqliteのmt_session壊れてて、なおしてアップしようと思ったら"Disk quota exceeded"が返ってきて、もうめんどくさいので、こっちに引っ越してきました。

グーグル様お世話になります。