AWS完全に理解する

2022-03-05に作成

AWSに対して学習した際のアウトプットを記します。

基本的には、以下から対応するサービスの資料を参照してまとめてます。

サービス別資料 | AWS クラウドサービス活用資料集

所有者限定モードのためこのボードには投稿できません ボードとは?

Amazon DynamoDBを完全に理解したかった

Amazon DynamoDBとは

キーバリュー型のデータベースです。
よくLambdaで使うデータベースとして使われるイメージがあります。

Amazon DynamoDB とは - Amazon DynamoDB

学習成果

導入効果は高いものの、ハードルは高いというイメージが強くなりました。

テーブル設計基礎知識

ほぼ以下の用語に関する要約です。

AWS Black Belt Online Seminar 2017 Amazon DynamoDB (PDF)

  • Table(テーブル): RDBのテーブルと同等
  • Item(項目): テーブル内の1行、RDBのレコードと同等
  • Attribute(属性): 行内の1要素、RDBのフィールドと同等
  • Partition Key (PK): いわゆる主キー
  • Sort Key (SK): いわゆるセカンダリキーで、 PK+SK で主キーとすることが可能
    • ソートするためのキー、というわけではない
  • Local Secondary Index (LSI): SK以外のセカンダリキー
    • PK(+SK)の親テーブルとは別に、内部ではPK+LSIのテーブルとして保持される
    • 「ローカル」というのは、1つのパーティション内で完結する項目のみを対象とする(ように自動的に配置される)という意味で呼んでいる
    • LSIを増やせば増やすほど、テーブルがまるまる1つ増えることになる
      • 親テーブルとの結果整合性には短い伝播遅延があるが、基本的には同じデータを持つ
  • Global Secondary Index (GSI): PK以外の主キー
    • PK(+SK)の親テーブルとは別に、内部ではGSI(PK+SK)のテーブルとして保持される
    • 「グローバル」というのは、全てのパーティションを検索する可能性がある(対象の親テーブルは1つ)という意味で呼んでいる
    • GSIを増やせば増やすほど、テーブルがまるまる1つ増えることになる
      • 親テーブルとの結果整合性には短い伝播遅延があるが、基本的には同じデータを持つ
  • Query(クエリ): データ抽出操作方法の1つ
    • ページサイズと呼んでいる1MBサイズ上限で検索結果を取得できる
      • ページ内に目的のデータが存在しない場合は、次ページの検索を引続き実行する
    • 理想的には、こちらのみを使ってデータを抽出することが望ましい
  • Scan(スキャン): データ抽出操作方法の1つ
    • 単一のスキャンにより、最大で (1ページサイズ / 4KB項目サイズ) / 2 = 128RCUを消費する(結果整合性のある読込みの場合)
    • テーブル全体を読込むため、余計なデータを抽出してしまう

性能の算出方法

ほぼ以下の用語に関する要約と、理解しづらかった箇所の補足です。

AWS Black Belt Online Seminar 2017 Amazon DynamoDB (PDF)

ただ、性能の算出方法については、用語が多すぎて理解しづらかったです。
公式ドキュメントの方も読みましたが、英語で読んでも難しい......。

読み込み/書き込みキャパシティーモード - Amazon DynamoDB

Item size(項目サイズ)

  • 単位
  • 読込み・書込み時に、1回で取扱い可能なデータのサイズ
  • 読込み: 最大4KB
  • 書込み: 最大1KB
  • 少数による分割は不可であり、少数になる場合は繰上げ
    • E.g.) 2.2KBのデータの場合
      • 読込み: 2.2 / 4 = 0.55 -> 1 Item size
      • 書込み: 2.2 / 1 = 2.2 -> 3 Item size

RCU (Read Capacity Unit)

  • 単位
  • スループット(読込み数)を表す
  • 1秒間あたりの読込み項目数 * 項目サイズ
    • 単位の中に「毎秒」というものが暗黙的に含まれている
  • 1RCUとは、1秒間あたり最大4KBのItemデータを1回読込み可能であることを表す

WCU (Write Capacity Unit)

  • 単位
  • スループット(書込み数)を表す
  • 1秒間あたりの書込み項目数 * 項目サイズ
    • 単位の中に「毎秒」というものが暗黙的に含まれている
  • 1WCUとは、1秒間あたり最大1KBのItemデータを1回書込み可能であることを表す

Capacity Unit(キャパシティーユニット)

  • RCUとWCUをまとめたもの
    • 両者は合算可能
  • 文脈によっては、スループットと読替え可能

Throughput(スループット)

  • 読込み・書込み時の処理可能なデータ量
  • RCUやWCUの数値(逆数)と考えても間違いではない
  • 読込み・書込みスループットがn倍とは、RCUやWCUが1/n倍という意味

Provisioned Capacity

  • 事前に設定するキャパシティーユニットの数値
  • DynamoDBの設定値の1つ
  • キャパシティーユニットとは若干意味が異なるくせに、文脈によってはキャパシティと書かれるし、Provisioned Throughputと同義で書かれることもある
  • 今はオンデマンドモードによる自動的なスケーリング機能があるから、今後は考えなくても良くなりかも

Provisioned Throughput

  • 事前に設定するスループット
    • スループットの数値を直接指定はできないが、Provisioned Capacityは指定できるため、それを指している?
    • 文脈によってはスループットと書かれるし、キャパシティと同義で書かれることもある

パーティション

  • テーブルを論理的に分割した際の、分割された物
  • 文脈によっては、単位として読替え可能
  • 1つのパーティションでは、3000RCUまたは1000WCU分のスループットを割当て可能
    • 「または」というのは、RCUとWCUの片方どちらかしか割当てられないという意味ではない
      • RCUとWCUの数値の数に応じて、パーティションごとに割合を変えたうえで両方割当てられる
  • 1つのパーティションでは、約10GBのデータを保存可能
    • 「約」ってなんだよと思うが、そう書いてあるから仕方ない
  • パーティションをいじることはできない(ブラックボックス)が、算出は可能
    • 8GBのデータサイズ、5000RCU、500WCUの場合(恐らくRCUとWCUの値は事前設定の値)
      • スループットあたり必要なパーティション: 5000 / 3000 + 500 / 1000 = 1.67 + 0.5 = 2.17 -> 3
      • データサイズあたり必要なパーティション: 8 / 10 = 0.8 -> 1
      • 最大値: MAX(1 | 3) = 3
        • すなわち、3つのパーティションに分割される
      • RCUとWCUの値 (Provisioned Capacity) は均一に割当てられる
        • RCU: 5000 / 3 = 1666.67
        • WCU: 500 / 3 = 166.67
        • データ: 8 / 3 = 2.66
        • 注意: 1つの項目に2000RCUとか実測値で出てしまうような場合、1パーティションで捌ききれず破綻(バースト)する
  • Provisioned Capacity変更時の注意
    • 増やす場合: 各パーティションの余裕を超えるまでは変化なし、余裕を超えた場合はパーティション追加
    • 減らす場合: 各パーティションのCapacity削減(パーティションは減らない!)
    • テーブル作り直した方がいいのでは?

NoSQLデータモデリング

ほぼ以下の要約です。

AWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design Pattern (PDF)

基本的な考え方

  • RDBでよくある正規化は、DynamoDBでは(ひいてはNoSQLでは)するべきではない
  • 入力と出力の数に応じて切り分ける
    • 1 : 1
      • PK or GSI(PK+SK)によるデータ抽出
    • 1 : n
      • PK+SK or GSI(PK+SK)によるデータ抽出
    • n : m
      • PK+SKとGSI(PK+SK) or PK+SKの複数テーブル
    • あれ、LSIは......?
      • 1 : n で利用するSKを複数用意できるという意味では、テーブルを1つに抑えつつ複数のSKで 1 : n の検索ができるかも

Targeting queries: クエリの限定

  • Query filter
    • フィルター条件を使用する
  • Composite key
    • キーになるデータを文字列結合した上で1属性化する
    • 正直いけてないように思えるけど、文字列結合じゃなくてJSON辞書型にすればいいのかも?
      • でもそれを明記してないってことは、こっちのほうがイケてないって意味なんだろう
      • キー名の決め方が大変そうだ
  • Sparse indexes
    • 属性を空にできるキーをGSIとすることで、GSIのテーブルの項目をそもそも削減する
  • フィルターの代わりにインデックスを活用
    • 名前の通り、フィルターで検索結果を削減するよりインデックスキーを使いましょうとのこと
      • フィルターでは通信量の削減はできない

GSI Overloading: GSIの多重定義

GSIは親テーブルあたりデフォルト20までしか作成できないため、1つのGSIで複数の用で利用できるように定義する方法です。

  • RDB
    • プライマリキーに社員ID、インデックスに名前、取得したいデータに勤務地、といった形の構造を作る
    • この時、勤務地で検索したくなった場合は、勤務地をインデックスとしたテーブルを作る
  • DynamoDB
    • そもそも、PKは社員IDとして同じだが、SKはカラムAという形にして、値として名前や勤務地という構造を作る
      • PKに同じ値を設定できるという仕組みを活用する
      • その際の取得したいデータは、PK+SKで求まるデータ(RDBでいうインデックス名前の値)を入れる
    • GSI作成時には、カラムAをPK、データをSKとする

メッセージアプリの巨大項目

メッセージ内容のようなデータサイズの変動が大きいキーについては、素直に別テーブルとするのが良いみたいです。

GSIの使用パターン

下記記事の要約です。

DynamoDB グローバルセカンダリインデックスを使用してクエリのパフォーマンスを向上させ、コストを削減する方法 | Amazon Web Services ブログ

複数の属性によるデータのクエリとソート

  • フィルタリング属性をPKとしたデータの取得ができる
    • 通常のフィルタリング機能では通信量を削減できない
  • 常にPKがソート済みのデータを取得できる
    • クエリ結果の順序は自動的にソートされる
  • ソートキーで範囲を制限できる
    • フィルタリング機能とは別?

RCUの通信量削減

詳細情報を全てテーブルに記入しつつ、よく検索するキーを別にGSIで指定することで通信量を削減できるみたいです。

読取りワークロードの分離

互いに影響しないアクセスパターン(恐らくSKやフィルター機能などで検索対象が被らないようなクエリのこと?)がある場合に、親テーブルと同じPKを指定するGSIを作成しても効果があるらしいです。

ベストプラクティス

下記ドキュメントのうち、気になった部分をかいつまんで要約してます。

DynamoDB を使用した設計とアーキテクチャの設計に関するベストプラクティス - Amazon DynamoDB

パーティションキーの設計: 書込みシャーディング

  • ランダムなサフィックスを使用して、PKへの書込みを分散させる
    • 例えば、同じ日付のPKを持つ項目を追加する場合でも日付文字列の後ろにランダムな文字列を追加することで、書込み対象のパーティションを分散できる
    • 上記ランダムな文字列に、別のSKなどから生成したハッシュを用いれば、読込み時にも項目の特定が可能になる

パーティションキーの設計: データの効率的なアップロード

  • 複数のデータを一度に登録する際、PK固定で項目を1つずつ登録するより、PKを分散させながら登録するとよい
    • PKごとの項目登録先パーティションを分散できる

ソートキーの設計

  • begins_with, betweenなどのクエリを利用できるようなSKを設計するとよい
    • E.g.) [country]#[region]#[state]#[county]#[city]#[neighborhood]
  • バージョンコントロールでのSK
    • データを更新するたびに、バージョン番号を付与したSKを持つ項目 v1_hoge を作成し、最新版を表す項目 v0_hoge を最新版に上書きする
      • 最新のデータを取得したい場合は v0_ でクエリを限定して取得できる
      • 任意のデータの全履歴を取得したい場合は hoge でクエリを限定して取得した上で v0_ を除外することで取得できる

セカンダリインデックス: 一般的なガイドライン

  • インデックス数は最小限に抑える
  • インデックスサイズは最小限に抑える(1KB単位であるため、1KB未満であればサイズの変化は無い)
  • ほとんど必要にならない属性は設定しない
    • 別テーブルに分離した方がよい
  • ALLクエリは、異なるソートキーによってソートされるテーブル項目全体を取得する場合にのみ利用する
  • クエリで取得したい属性を計画しておく
    • 計画されていない属性をクエリ検索する際、テーブル全体を読取る可能性があり、通信量が増える

セカンダリインデックス: スパースなインデックス

  • スパースなインデックス: 項目に存在しない可能性があるソートキーのこと
  • スパースなインデックスをGSIのPKに設定することで、通信量を削減できる

セカンダリインデックス: 集計データ

  • 集計データのキーをGSIに設定し、親テーブルのPKに対する項目が追加されるごとにDynamoDB StreamsからLambdaを起動してGSIの項目を追加するフローを確立することで、スパースなインデックスをGSIのPKに設定でき、WCUを削減できる

セカンダリインデックス: レプリカの作成

  • 親テーブルのPKと同じキーをGSIのPKに設定することで、GSIを利用したレプリカテーブルを作成できる
  • 親テーブルに比べてGSIの整合性には伝播遅延がある
  • GSIのPKは親テーブルのPKと同じキーを設定したうえで、GSIのSKを別キーにすることで、親テーブルから読取る項目を削減できるレプリカテーブルを作成できる

大きな項目

  • Binary属性タイプを使えば、GZIPやLZO圧縮によるバイナリデータをDynamoDBに登録できる
  • S3にオブジェクト識別子を登録するのも良い

時系列データ

  • 基本的に、アプリケーション1つに対してテーブルは1つであることが望ましい
  • 時系列データの場合は、期間ごとにテーブルを作ると最適に処理できる
  • 期間終了前に、次の期間でテーブルを事前に作成する
  • 期間終了後に新しいテーブルにリダイレクトする
  • テーブルの書込みが減ったら、プロビジョンドキャパシティを削減する
  • ほとんど必要ないと判断したデータのテーブルは、アーカイブするか削除する
  • E.g.
    • 当月
      • WCU700, RCU300
    • 先月
      • WCU1, RCU100
      • WCU1, RCU1

多対多の関係

  • 隣接関係のリスト設計パターン
    • E.g.) 多対多の関係があるエンティティA, B(AとBは親子関係)
      • 親テーブルのPKとSKの両方にキーA, Bを登録し、キーAがPKの場合はAとBの値をSKに、キーBがPKの場合はBの値のみをSKに登録する
      • 親テーブルのSKをGSIのPKに設定することで、GSIからBをキーとして検索すると、Bに関連するAが全て抽出できる
  • マテリアライズドグラフパターン
    • 簡易的なグラフ指向DBを実装できる
    • 複雑なグラフ指向DBが必要な場合はAmazon Neptuneの使用を推奨する
    • 正直、理解できなかった......

リレーショナルモデル化

正直、この記事を読んだ方が理解は早いと思います。

DynamoDB でリレーショナルデータをモデル化するためのベストプラクティス - Amazon DynamoDB

  1. データのアクセスパターンを整理する
  2. 欲しいデータのPKとなるキーを複数決める
  3. 決まった複数キーを被らないように登録フォーマットを定め、PKとする
  4. PKに紐づけるSKとなるキーを、PKの各キーごとに複数決める
  5. 決まった服すーを被らないように登録フォーマットを定め、SKとする
  6. 親テーブルのSKをPKに、データをSKとするGSI1を設定する
  7. 集計データが必要な場合、ランダムな値をPKに、期間をSKとするGSI2を設定する

クエリとスキャン

  • スキャンはテーブル全体を読込むため、テーブルサイズに比例して時間も費用もかかる
    • フィルターは読込み後に結果を削減するため、通信量の削減にはならない
    • なるべくスキャンは避けて、クエリを利用するべき
  • 大量データへのスキャンによるスパイク回避方法
    • ページサイズを削減する
      • 目的のデータが見つからなければ、何度も通信する必要がある事には変わらない
    • スキャン対象のテーブルを分離する
    • 並列スキャンを活用する
      • 大量のリクエストが発生する可能性がある