Laravelからaws-sdk-for-php でDynamoDBからデータを取得する時の注意点

Laravelからaws-sdk-for-php でDynamoDBからデータを取得する時の注意点

実装して感じた課題を先に抽出します。

  • 1回当たり1MBの最大データサイズ制限
  • 5000件あるレコードを全て取得するにはどうすればいいの問題
  • Key以外のカラムは存在しない時があるよ問題
  • Viewでのリレーション先取得問題(既存システムがSQLだった場合実装されている事がある)
  • ページネーションどうする問題
  • 定時リロード辛いよ問題(読み書きの量でコストが変わるので、毎分リロードとか実装どうなの問題)

今回ぶち当たったネタだけでこれだけある状態です。実装するときには十分に調査/調整して動きましょう。
「大丈夫」とかノリで動くと大工事が待ってるかもしれません。

1つづつ見ていきます。

1回当たり1MBの最大データサイズ制限

1MBと言われてもピンと来なかったりします。
なので、今回の事例で記載してみます。

取得件数:2769件

  • テーブル内カラム数:最大24
  • 一部に長文テキストあり(Wifiの位置情報)
  • セカンダリインデックス非設定
  •  正規化して存在する値のみ格納、存在しないNULLな奴はカラム名もなし

24のカラムを持つ内容の場合でおよそ2500レコードが1度に取得できると考えていいと思います。

5,000件あるレコードを全て取得するにはどうすればいいの問題

上記のように2700件で終了してしまったけど4800件目も取得して欲しい場合、どうすれば良いか。

続きを読み込む時は ExclusiveStartKey に LastEvaluatedKeyを格納して再度読み込む

これだけで続きの取得をしてくれます。
具体的には次のようなコード(参考例は検索総数の取得)になります。

/* ------------------------------------ *
 * AWS_DYNAMODB 接続設定関連
 * ------------------------------------ */
 private $client;
 private $marshaler;
 public function __construct()
 {// Dynamodb接続用Headerを用意
     $awsSdk = new Sdk([
         'credentials' => [
             'key' => env("AWS_DYNAMODB_KEY"),
             'secret' => env("AWS_DYNAMODB_SECRET")
         ],
         'endpoint' => env("AWS_DYNAMODB_ENDPOINT"),
         'region'   => env("AWS_DYNAMODB_REGION"),
         'version'  => 'latest'
     ]);
     $this->client = $awsSdk->createDynamoDb();
     $this->marshaler = new Marshaler();
 }

/* ------------------------------------------------ *
  * 検索対象の総数カウント
  * ------------------------------------------------ */
 public static function countSearchItems($tableName,$did,$fromTs,$toTs){
     #Keyの調整
     $eav = $this->marshaler->marshalJson(
         '{
             ":id": "' . $id . '",
             ":from_dt":'. $fromDateTime .',
             ":to_dt":'. $toDateTime .'
         }'
     );

     # 日時要素の有無で検索方法を変動
     if(!empty($fromTs)){
         $KeyConditionExpression = '#id = :id and ts between :from_dt and :to_dt';
     }else{
         $KeyConditionExpression = '#id = :id';
     }

     $params = [
         'TableName' => $tableName,
         'KeyConditionExpression' => $KeyConditionExpression,
         'ExpressionAttributeNames'=> [ '#id' => 'id'],
         'ExpressionAttributeValues'=> $eav,
         // 総数の確認だけなので、COUNTだけを取得 ※レコードが欲しければSelectは外す
         'Select' => 'COUNT',
         'ScanIndexForward' => false,
     ];

     # データ取得
     try {
         $count = 0;
         while (true) {
             $result = $this->dynamodb->query($params);
             # LastEvaluatedKey が含まれなくなるまでループ処理を実行
             if (!empty($result['Count'])) {
                 $count = $count + $result['Count'];
             }
             # 存在する場合は次の値を取得する
             if (isset($result['LastEvaluatedKey'])) {
                 $params['ExclusiveStartKey'] = $result['LastEvaluatedKey'];
             } else {
                 break;
             }
         }
         return $count;
     } catch (DynamoDbException $e) {
         throw $e;
     }
 }

DynamoDBではレコードに続きがある場合、LastEvaluatedKeyが追加されています。
つまり【LastEvaluatedKey が含まれなくなるまでループ処理を実行】を実行すれば最後まで取得する事が可能です。

Key以外のカラムは存在しない時があるよ問題

DynamoDBの場合はあらかじめ決めたKey以外は意外なほど自由に設計できます。
そのため、直前のレコードが4カラム構成で、今のレコードは15カラムの構成なんてこともDynamoではあり得ます。

MySQLでデータを正規化して縦持ちのTableにしたりしますが、それを横持ちTableのまま簡単に実装できてしまうのがNoSQLの面白いところです。ということで、Key以外の値については自由なので【レコードのサイズ】や【正規化】といった内容を題目とし『NULLは削ってしまえ』とレコードを作る事があります。

旧システムがSQLで構成されていた場合、これを行うと修正箇所が多かったりします。
なぜかというと、存在しないときはNULLが格納されているものとしてコーディングされている事が多々あるからです。具体的にはこんなの( {{ $data->type }} )がViewにいたりとか。
カラムが存在しないとエラーを生む構造のコーディングはViewだけではありません。

解決策

知っていれば回避は簡単です。

@if(array_key_exists('type', $data))
    {{ $data ->type }}
@endifと

とか

if(!empty($data->type)){
    echo($data ->type);
}

とか。

ただ、こういったコードが書いていない場合は様々なシーンで同じ状態が発生している可能性がるので作業数が膨れたりましす。
工数計算には要注意です。

Viewでのリレーション先取得問題

これ、今回の実装で結構厄介な問題になりました。
既存システムがSQLだった場合下の様に実装されている事があります。

{{ optional($data->type)->typename }}

これ、Eloquentでリレーション結ぶと便利に使える記載方法で、リレーション先の情報を -> だけでできる優れものです。
Laravelで構築したシステムでは遭遇する事が多い記載方法かもしれません。
が、DynamoDBを利用するとこれが辛くなってきます。

基本的にリレーションしてないので、取得ができない。
そのため、取得した値を確認して、その値でfindをかけtypenameを取得するといった流れの実装になります。
Dynamo化すると「そこまでする意味あるのか?」というところと「そのままテキスト収めてしまえば?」というところの選択が出てくるでしょう。
当然その分工数が増えるので要注意です。

ページネーションどうする問題

今回一番悩んだのがコレです。

前回の案件で触ったときにはページネーションできていたのですが、Lambdaさんがよろしくやってくれていたようです。
aws-sdk-for-php で実装する場合、ページネーションに利用できるNextTokenも発行されません。
NextTokenはAppSyncさんが発行してくれていたようです。

という事で、手詰まり...。
今回の案件では「実際にページネーションが必要なのか」という議題を挙げさせていただき、結論として「不要」との判断になり事なきを得ました。
実際にやろうとすると下のような段取りを行うようです。

  1. Limitをつけ、表示したい個数のレコードを取得する
  2. 最終レコードのLastEvaluatedKeyを控えておく
  3. LastEvaluatedKeyがなくなるまでwhileでループ取得する
  4. LastEvaluatedKeyの数(順番)=ページねイターの番号数となるので、それをページネーションに活用しナンバリングとリンクを生成する

ここで判明した問題点は下の通り。

  • ページ読込みのたびに全件の取得してる => 読込数が多いため表示数の割にコストがかかる
  • ソートする場合などは全件取って並び替えを実行するのでLaravelサーバーのパワーが必要

つまりは「やる意味ある?」「フロー変えた方が良くない?」という発想が出てきて「不要」判断になったというわけです。
上記のようにやってやれないことはありません。
が、ページネーションが必要な場合はaws-sdk-fot-phpで直接取ってくる実装はお勧めしません。

定時リロード辛いよ問題

基本的にはページネーションと同じく【費用対コスト】の話になります。
読み書きの量でコストが変わるので「毎分リロードの実装どかどうなの?」と。

プロジェクトにて、スマホのウィジットにしようとの企画がありました。
1分に1レコード増えるような案件だったので、仮に気温のグラフを表示するウィジットだとしましょう。

「これ、どうとる?」という疑義がリロード問題です。

MySQLであれば毎回表示したい分だけ取得してきても良いかもしれません。
でも、Dynamoの場合は読込で課金が発生します。
枚分24時間分の気温を取得してきたとすると、1分毎に1440件のレコードを取得してくることになります。
これ、24時間で合計すると2,073,600件取得することになり、この分のコストが発生することになります。

  • 欲しいときに取得で良くね?
  • 1レコードだけ取得してきて、iOS側でレコード制御すればいいんじゃね?

まぁこういった発想になりますよね。だって、コストが1000倍以上違う可能性あるんですから。

まとめ

流行りなので「DynamoDB化しよう」という企画が増えているかもしれません。
でも、しっかり詰めて計画しないと、後で痛い目見る可能性があるのがDynamoDB化です。
設計だけではなく、運用も含めて考えないといけない事項も多々あるので、プロジェクトの打ち合わせ密度を高めチャントした所に落ち着ける事が大切です。
決まって仕舞えば、速度は早いですしBIGレコードには最適だと思います。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。