echo("備忘録");スマートフォン用の表示で見るecho("備忘録");IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。DynamoDBきっかけ先日、とあるサーバーレスアプリを作っていた際、DynamoDBについて下記のツイートをしました。ここに来て、DynamoDBの構成を変えなきゃいけない可能性が。2020年2月4日そうしたら、下記の突っ込みリプを頂きました。プライマリーキーではなくパーティションキーなのですこのキーの値によってデータが配置されるノードが決まります。主要なキー群(プライマリインデックス)DynamoDBの主要なキーには、下記の3種類があります。パーティションキーDynamoDBのデータをどのパーティションに配置するかを決定するキー。(BeginWith)」及び「範囲検索」(値の大小など)ソートキーは任意(なくてもよい)ソートキーだけでの検索は不可(必ずパーティションキーとセット)これが結構面倒だったりするプライマリキーパーティションキー、またはパーティションキーとソートキーのセット。プライマリキーは各データでユニークでなければならない。RDBの主キーと同じ※DynamoDBの「パーティション」の概念、及び「なぜパーティションキーが"完全一致"固定なのか」などについては、下記公式ページが参考になります。https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html設計するときに注意すること上記「パーティションキー」と「ソートキー」を踏まえ、DynamoDBテーブルを設計する際は、下記の点に注意する必要があります。「パーティションキー」には、テーブル内の情報の主属性を一意に特定できる値を設定する。「ソートキー」には、テーブル内の同一主属性のデータについて、並び替え、または範囲指定するような値を指定する※例:会員制Webサイトのアクセスログを記録するテーブルの場合パーティションキー:会員IDソートキー:アクセス日時てか、DynamoDBはパーティションキーの設計が本当に肝になるので、開発時は慎重に検討する必要があります。その他のキー群(セカンダリインデックス)プライマリインデックス(主に「パーティションキー」と「ソートキー」)は上記の通りですが、場合によっては下記のようなケースが出てきます。パーティションキー&ソートキー以外の組み合わせでも検索をかけたいソートキー1個ではソート条件が足りない※例えば、先述の「会員制Webサイトのアクセスログを記録するテーブル」の場合、下記のケースが出てくると思います。)で検索をかけたいアクセスしたデバイスでも並び替えを行いたいそんな「プライマリインデックスだけじゃ足りない」時に必要になるのが「セカンダリインデックス」です。セカンダリインデックスには下記の2種類があり、どちらも「プライマリとは別で作成できる、パーティションキー(またはパーティションキーとソートキーのペア)」です。グローバルセカンダリインデックスローカルセカンダリインデックスそしてどちらも「プライマリとは別で作成できる、パーティションキー(またはパーティションキーとソートキーのペア)」です。何が違うかというと、以下の点が異なります。下記「GameScores」テーブルがあったとして、この構成だと例えば「各ゲーム別のハイスコアラーの確認(昔のゲームでデモプレイ後に出てくるアレ)」ができません。パーティションキーは「UserId」、ソートキーは「GameTitle」その場合、下記構成のグローバルセカンダリインデックスを作成すれば、それが実現できます。パーティションキー:GameTitleソートキー:TopScoreまた「あるプレーヤーのハイスコアを出したプレイ履歴」を閲覧したい場合は、下記構成のローカルセカンダリインデックスを作成すればOKです。処理速度の低下セカンダリインデックスを作成すると、プライマリ以外にもセカンダリにも処理を行うため、スループットが低下する(特に書き込み)。RDBで言えば「別途コピーテーブルを作成し、そちらにも各種処理を行う」イメージに近いストレージ容量も追加で必要になる個数の制限セカンダリインデックスは、20個までという制限がある。(さすがに20個作成することはなかなかないけど)大量にセカンダリインデックスが必要な場合は、そもそもテーブル設計を見直す必要がある。(RDBでいう「正規化」)query()メソッドとsort()メソッドDynamoDBで複数データの取得によく使われるのが、以下の2つのメソッドです。メソッドどちらも「DynamoDBからデータを取り出す」点は同じですが、処理内容に下記の違いがあります。query():「KeyConditionExpression」の条件でデータをフィルタリング→条件に合致したデータを取り出す→返却scan():全データを取り出す→「FilterExpression」の条件でデータをフィルタリング→条件に合致したデータを返却つまり、scan()はフィルタリングの有無に関わらずまず全データを取り出すため、下記のことが言えます。(もちろんデータ量次第ですが)query()メソッドより、処理に時間がかかるquery()メソッドより、課金額が多くなるDynamoDBの読み込み時の課金対象は「読み込まれたデータ(上で言う「取り出されたデータ」)」なのでなので、極力下記の方針にした方が良いです。※公式サイトや各種ブログにも書かれている通りです。場合によってはセカンダリキーの使用も検討するうまく使えば、データの扱いが便利になる。(ただし多用は避ける)セカンダリキーが多量に必要な場合、まずテーブル設計を見直すデータ取得は極力query()やgetItem()メソッドで行う。scan()を多用しない。言ったとこでしょうか。これ書きながら自分でも「業務で使ってるあのテーブル、設計ガバガバじゃねえか!」とか思い返してたりするんですけどね...というか、(DynamoDBに限らず)時間をおいて見返すと「設計がむちゃくちゃ」とか「1から設計し直したい...」と思うものが、本当に多いです。スキルアップしないとですね。#16」イベントにて、スピーカーをさせて頂くことになりました。大トリ当日は業務終了後に名古屋から直行予定と、あわただしい感じですが、皆さま、どうぞよろしくお願いします。FrameworkAthena&QuickSightそれではまた。Frameworkでは、serverless.ymlだけでなく、外部ファイルに定義を設定して読み込むことも可能です。そのやり方についてです。読み込み可能なファイルは、以下の通りです。YAML(*.yml)JSON(*.json)JavaScript(*.js)serverless.comYAML&JSONファイルの読み込みYAMLやJSONなど「キー:値」形式のファイルは、以下の方法で読み込みます。※以下はYAMLで読み込んでいますが、JSONも(構造さえ気をつければ)同じです。${file(./settings.yml).fuga}上記のように、外部ファイルの値を読み込みたいキーに対して、値を${file(外部ファイルのパス).値を読み込みたいキー}と設定すれば、外部ファイルの値を読み込んでくれます。値:fugafugaという2つのタグが登録されます。上記のように「複数のキーを読み込む」場合、「custom」セクションの独自変数をうまく使うことで、読み込み回数を1回にできます。独自変数「settings」に、settings.ymlの定義を格納する。キーの値を読み込みできる。${self:custom.settings.fuga}使いどころ使いどころとしては、例えば下記ケースです。)上記の場合、まず下記のようにフォルダ&ファイルを構成します。各「settings.yml」には環境ごとの定義がされている[Root][dev]settings.yml[prod]settings.yml次に、serverless.ymlのcustom.settingsを下記のように設定する。独自変数「settings」に、settings.ymlの定義を格納する。${file(./${opt:env}/settings.yml)}最後に、デプロイ時のCLIコマンドで、引数「env」に環境に対応したフォルダ名を指定すればOKです。devJavaScriptファイルの読み込みJavaScriptファイルの読み込み(=関数の戻り値を設定)も、大きな違いはないです。定義としては、下記の書式となります。(=デプロイ時の日時情報)を格納する。関数に引数を渡すことはできない戻り値をJavaScriptオブジェクトにして、そのキーを参照することは可能のようです。使いどころ使いどころとしては、例えば下記ケースです。毎回値が変わる情報を設定したいデプロイ日時情報など(上記の例のように)serverless.ymlに、生の設定値を書きたくない。${file(./token.js):getToken}実際のJavaScriptでは、生の設定値を記載しなくてよいような処理を施しておけばOKです。//「encryptedTextを復号化した値」を指定する。decryptedText;};こうすれば、生の設定値を書かなくて済む分、良くなると思います。※もちろん下記ケースがあるとばれる可能性があるので、そこは注意です。Frameworkに関する情報でした。Frameworkに限らず、AWS/IaC/クラウドなどのナレッジがかなりたまってきたので、少しずづ公開したいなあと考えています。近々イベントで登壇するかもしれないので、確定したら合わせて公開します。定期的にブログを公開したいなあ。関して、下記のようなツイートをしました。Cと書く事がないように、若手を指導しなければなあ。2020年1月9日このツイートに対して、思った以上に反響を頂いた(と思う)ので、今回もう一度async/awaitのことを記事にしました。ちなみに前回書いたasync/await関連の記事はこちら。makky12.hatenablog.comなお今回の記事を書くにあたり、下記ブログを参考にしました。(ブログを書こうと思ったきっかけもこれを見たから)「メソッド実行とawaitの分離」の書き方など、役立つソースが多いです。前提:async/awaitの基本async/awaitですが、ざっくり書くとPromiseによる非同期処理を、同期処理っぽく書ける仕組みです。これを利用することで、例えば下記のメリットがあります。同期処理の様にコードが書けるので、ソースが直感的にわかりやすい。async/awaitだと、こんな感じでシンプルに書ける。c;}本題で、元の僕のツイートにある非同期処理A〜Cの3つ全て成功した場合のみ処理Dを実行A〜Cは依存関係なしという処理を書く場合、ついつい「とりあえず生中」ならぬ「とりあえずawait」な感じで、条件反射的に下記ソースを書いてしまいがちです。D」となってしまいます。今回の場合、処理A~Cは非同期処理、かつ依存関係はないので、それならば下記の流れで、A~Cは並列に実行すべきです。D」ですみますので、結果としてA~Cの処理時間に関しては、他の2処理の処理時間分、短い時間で実行可能です。B)時間、処理時間を短縮できます)これをコードにすると、下記のようになります。(3パターンのソースを書きました)なお、「ラーメンで理解するasync/await」にある通り、非同期処理の実行とawaitは、絶対にセットで書かなきゃいけない...なんてことはありません。下記のように、別々に書いても問題なく動きます。awaitするのは、レスポンスの受け取りのみにする。リクエストだけは一括で投げるのは同じ。then()内の処理の実行を待つメソッド。リクエストだけは一括で投げるのは同じ。}結局、どれで書くべき?「ラーメンで理解するasync/await」のコメントでも、こう書けばPromise.all()はいらんのかPromise.all()いらんけど、並列実行がわかりにくいから微妙。awaitばっか羅列する書き方()だと、並列実行が分かりにくいなどなど、いろいろ書かれてました。個人的には、それぞれのメリットとしては、例えば下記だと思います。「前提:async/awaitの基本」で書いたような、前の非同期処理のレスポンスを利用する、などPromise.all並列処理ということが一目でわかりやすいawaitの乱用を防げるただ、正直どれを使っても大差はない、と思います。(好みやプロジェクトに応じて使い分ければいいかと)※個人的には「async/awaitとPromise.all併用」が、一番分かりやすいかな...と思いますが。まとめ正直、「結局、どれで書くべき」については、好みやらこだわりやら宗教やらもあるので、どれでも構わないと思います。「とりあえずawait」で非同期処理を書いていると、今回書いたような「不要な時間をかけてる処理」が出来てしまうので注意しましょう、という話でした。実装する機能の仕様や設計から、処理フローの構成をしっかり検討することが大切だと思います。Framework前回の更新が2019/10/12...ずいぶん間が空いてしまったなあ。実際のところ、前回から本番リリース遅い夏休み(お遍路結願)謎の体調不良(1か月ほど)で、全然ブログもスキルアップ活動も勉強会参加もできませんでした。回復してきたので、リハビリがてら。Framework(以下SF)を使って、クラウド上のソースをテストする。Lambda(などのAWSリソース)デプロイをしておく。その1AWS上のLambdaを実行するとりあえず、最低限のコマンドがこちら。※「funcName」には、SFの「functions」セクションで定義したLambda関数の定義名が入ります。funcName※必須のパラメータは「--function」だけですが、それ以外にも以下のような任意パラメータがあります。--logを指定すると、CloudWatchのログをターミナルに出力できる。(これがすごい便利)--pathオプションについて--pathオプションに指定するjsonファイルには、ハンドラの「event」引数の中身を定義することができます。Lambdaに渡す値は、この「event」引数の「queryStringParameters」「body」キーなどに格納されているので、このjsonファイルの中身を変えることで、いろいろな値を指定してテストを行うことができます。event引数自体にはたくさんのキー&値がありますが、テストする場合は最低限のキー&値を定義すればOKです。jsonファイルは、最低限下記定義さえあればOK。下記コマンドでテストすると、以下のようなレスポンスが表示される。Makky12\"}"}--logオプションについて通常、テストを実行した場合にターミナルに表示されるのはレスポンスの内容だけですが、「--log」オプションを付けることで、CloudWatchの内容を追加で表示できます。個人的にはこれがかなり便利な機能だと思っていて、いちいちAWSにアクセスしなくても、エラーの原因がある程度わかりますので、デバッグにもかなり役立ちます。--logオプションありだと、レスポンスの下にCloudWatchのログが表示される。ローカルのLambda関数を実行する先程までは実際にAWS上のLambdaに接続しましたが、ローカルのLambdaをテストすることもできます。ローカルのLambdaを実行するコマンドはこちら。funcNameオプションもAWS上で実行する場合とほとんど同じですが、下記が追加されてます。serverless.ymlで「${opt:env}」(コマンド引数envの値を参照する)定義があるとエラーになる。この場合省略形(-e)を使用する。Dockerで実行する際、Dockerに渡す引数を指定できるenvオプションについて上記に記載の通り、--envオプションで、Lambdaに定義した環境変数の値を変更できます。先述のLambda関数のresBody.greetingの値に下記の環境変数を定義した場合、下記のような差があります。この場合、--envの指定は無視され、Lambdaで定義した「hogehoge」になる。この場合、--envの指定が適用され、--envで指定した「fugafuga」になる。・エラーの特定が直接実行より難しい(ログが出力できないので)・実運用に即したという点で、直接実行には劣る※最初「ローカル実行だから、実際のAWSリソース(S3やDynamoDBとか)にはアクセスできない(=エラーになる)」と考えていましたが、実際は普通にSecretManagerやAuroraの値を取得できました。pluginでローカル実行用の(Mock)プラグインの定義をしなかった場合、普通にリソースの値はAWSに接続して取得するのか?(この辺りは、要調査です)まとめ結論としては、結構使いやすいと思います。「--log」によるログ出力機能が便利だなあ、と感じました。「ローカル実行時でも、(実運用時と同様に)ハンドラ関数呼び出しによるブラックボックステストしか行えない(privateな内部関数のテストが行えない)」点はありますが、それを差し引いても便利だなあと感じます。その辺は、Jestなどと併用して解決していけばよいと思います。体調不良やらいろいろありましたが、何とか年内にブログを書けて良かった。それでは、良いお年を。Cognito概要その1で、Cognitoユーザープールの説明の前半部分を書きましたが、今回はその後半になります。アプリクライアントの作成このユーザープールを使用して認証を行うアプリの設定をします。