🦔

DynamoDBを少し学んだ際のメモ

2023/02/26に公開

概要

DynamoDBのパーティションキーソートキーローカルセカンダリインデックグローバルセカンダリーインデックスなどの仕様についての理解がいまいちだったので、学んだ結果をメモする。

CAP定理について

RDBとDynamoの違い(どちらを採用するか)を理解するにはCAP定理を理解する必要がある。
CAP定理は、分散システムにおける3つの特性である一貫性(Consistency)可用性(Availability)分断耐性(Partition tolerance) のうち、2つしか実現できないという定理。

一貫性

分散データベース内のすべてのノードが同じデータを持っていること。
他のノードでの更新処理を別のノードで取得した際にも、同じデータを取得できることを保証する。

可用性

いつでもデータにアクセスできること。(取得、追加、更新、削除など)
単一のノードに障害が起こっても、他のノードが処理を引き継ぎデータベース処理の継続性が失われないことを保証する。

分断耐性

ネットワーク上の障害が発生してノード間の通信が遮断されるなどが起こっても、システム全体が停止することなくデータにアクセスできることを保証する。

ノードについて

データベースを分担して処理する物理的または論理的なコンピュータ。大体複数のノードで一つのデータベースを構成する
書き込み処理、システムのコントロールを行うマスターノード、マスターノードからデータを受信して読み取り処理を担当するスレーブノード、マスター&スレーブノードからデータを受信して複製を作成し可用性を高めるレプリカノードなどがある。

RDBMSのACID特性は上記の一貫性(C)、可用性(A)、分断耐性(P)のうち、一貫性と可用性(CA)をとっている。一方DynamoDBなどのNoSQL(BASE特性)は一般に可用性と分断耐性の(AP)となるが、場合によってはCPとすることもできる。

RDBMS NoSQL
CAP定理 一貫性&可用性(CA) 可用性&分断耐性(AP)or 一貫性&分断耐性(CP)
デザイン原則 ACID BASE

DynamoDBの可用性、分断耐性のざっくりとした仕様を挙げると以下がある
・自動レプリケーションとフェイルオーバー
自動的にデータを複数のAZにレプリケーションする。これにより一部のAZに障害が起こっても、フェイルオーバーを行い、他のAZに切り替えることができる。またDynamoDBはデータをパーティションという単位で分割し、レプリケートしている。

・読み込み/キャパシティモード
オンデマンドとプロビジョンドのキャパシティーモードがテーブルごとに用意されている。高使用率になるかどうか不明で、予測が困難なワークロードについては、オンデマンドキャパシティーモードによって容量を管理しすることができる。支払うのは実際に消費した分だけ。

https://aws.amazon.com/jp/dynamodb/features/?pg=dynamodbt&sec=hs

BASE特性について

BASE特性はRDBMSのACID特性と同じように分散システムのデザイン原則である。
BASE特性では以下の3つの原則がある

基本的な可用性(Basically Available)

障害や負荷増大時でもシステムがダウンせずに機能することができればいい。全てのノードが常に利用可能である必要はなく、部分的に利用可能でシステムが機能することが保証できればいい。

柔軟な状態(Soft State)

ノードの状態が変更などに柔軟であること。全てのノードが完全に一致する必要はなく、一時的に異なっていることを許容する。
これによって、書き込み、更新処理の高速化を実現している

結果整合性(Eventually Consistent)

最終的に全てのノードが完全一致するが、その過程での追加、更新処理では一時的な不一致を認める。

DynamoDBの強い整合性モデルはCAPを全て満たす?

DynamoDBは通常、結果整合性であり最近の書き込みが反映されない時がある。がDynamoDBは「強い整合性」もサポートしている。
となると、強い整合性を採用した場合、CAPを全て満たすことになると考えられるが、、、

また整合性(一貫性)をとる場合、可用性が犠牲になり、CPとなるはずであるが、少し調べてみる。

「強い整合性」の説明には以下の記述がある。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html

強力な整合性のある読み込みでは、結果整合性のある読み込みよりも多くのスループット容量が使用されます。強力な整合性のある読み込みは、ネットワークの遅延または停止があった場合に、DynamoDB はサーバーエラー (HTTP 500) を返す場合があります。

最初の試行で読み取りリクエストがリーダーノードに到達しない場合、強力な整合性のある読み込みの待ち時間が長くなる可能性があります。

「ネットワーク遅延または停止があった際にサーバーエラーとなり、リーダーノードに到達しない場合、待ち時間が長くなる可能性がある」と可用性が落ちるような説明がある。

またこちらの記事を参考にさせていただくと、

DynamoDBは2箇所を書き込んだ時点で「書き込みOK」という返答をします。残り1箇所は「そのうち時間が経てば(DynamoDBの場合は1秒以内)結果的に書き込まれる」という考え方

DynamoDBの結果整合性では、2個のノードに書き込んだ時点で書き込みが完了したとみなし、強い整合性での読み込みでは、

書き込まれている3箇所より2箇所の値を読み取り、一致すればその値を、一致しなければもう1箇所の値を読んで2箇所に書き込まれている値を返す

とある。つまり強い整合性での読み取りでは、ノードの値を複数みて比較しているためパフォーマンスが落ちているという意味で可用性が落ちていると解釈することにする。

が設計次第ではCAP全てをある程度バランスよくする手段もあるみたい。

テーブル設計

DynamoDBのテーブルは、プライマリーキーセカンダリーインデックス属性という構成要素を持つ。

プライマリーキー

dynamoDBは必ず1つのプライマリーキーをもち、データを一意に特定するためのキーとなる。プライマリキーに以下の2つがある。

パーティションキー

テーブル内でデータを一意に特定するためのキーで必ず指定する必要がある。

ソートキー

パーティションキーと組み合わせて、データを一意に特定するためのキー。必須ではない。

この二つはRDBMSでいう主キーと複合主キーみたいなもの?と解釈しそうになったが、RDDBMSとは少し違った。
まずプライマリーキーはデータを一意に特定する役割というところは似ていて、「パーティションキー」のみor「パーティションキーandソートキー」の組み合わせで必ず一意になるように構成する。

DynamoDBではデータをパーティションという単位で分散され保存される。 そのためデータがどのパーティションに分類されるかを指定するのがパーティションキーである。
ソートキーに関してはデータをパーティション内でソートし、範囲を指定(フィルタ)しての検索に使用される。

・パーティションキーを顧客ID、ソートキーを購入日として購入履歴テーブルを作った例
以下のようにパーティション内でソートされる

パーティションの分類ロジックに関しては公式引用

テーブルに複合プライマリキー (パーティションキーとソートキー) がある場合、DynamoDB は データ分散: パーティションキー で説明したのと同じ方法でパーティションキーのハッシュ値を計算します。ただし、同じパーティションキー値を持つ項目はすべて、ソートキー値順に物理的に近くに格納されます。

プライマリーキーを設定する際に考慮すべきこと

ここで重要になるのは、基本的に検索または更新はプライマリーキーを指定してでしかできないということである。(データの全検索、全削除などを除く)

RDBMSではたとえば

id(pk) name created_at
1 hoge 2022/01/01 00:00:00
2 hogehoge 2023/01/01 00:00:00

上記のようなテーブルがあったとして、pkのid以外のnameやcreated_atを指定して検索や更新、削除ができるが、DynamoDBの場合はプライマリーキーでしかできない。

そのため、プライマリーキーの選定の際には、どの項目で取得(ソート)、更新、削除したいのかを考慮する必要がある

セカンダリーインデックス

DynamoDBの中でもセカンダリーインデックスは難しい概念である。役割としては、データへのアクセスをプライマリーキー以外の属性で行うことを可能にするもの検索の効率性、柔軟性を実現するものと解釈する。

公式から引用

多くのアプリケーションでは、プライマリキー以外の属性を使って、データに効率的にアクセスできるようにセカンダリ(または代替)キーを 1 つ以上設定することで、メリットが得られることがあります。これに対応するために、1 つのテーブルで 1 つ以上のセカンダリインデックスを作成して、それらのインデックスに対して Query または Scan リクエストを実行することができます。

ローカルセカンダリインデックス(LSI)

設定されているパーティションキーはそのままにして、ソートキーのみを設定されているものとは別のキーに設定できる仕組みのことである

例えば、以下のようにパーティションキーとソートキーをデフォルトで設定しているテーブルがあるとする。(このテーブルをベーステーブルと呼ぶ)

顧客ID(パーティションキー) 購入日(ソートキー) 金額(属性)
1 2022/01/01 00:00:00 300
1 2023/02/03 12:00:00 600
1 2023/02/06 01:00:00 600
2 2023/02/12 13:46:05 1500
3 2023/02/05 12:00:00 130
3 2023/02/10 18:00:00 500

追加で、金額順で並び替える要件もあるとすると、パーティションキーはそのままで、ソートキーを金額にしたテーブルを作成することができる。

顧客ID(パーティションキー) 金額(ソートキー)
1 600
1 600
1 300
2 1500
3 500
3 130

このようにデフォルトで設定したソートキー以外での検索を実現したいとき、ローカルセカンダリインデックスは活用できる。更新、追加などはベーステーブルに対して行われ、LSIを設定して作られたテーブルにはデータが自動で同期され読み込みには強力な整合性もサポートしている。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LSI.html

DynamoDB によって、すべてのローカルセカンダリインデックスがそれぞれのベーステーブルと自動的に同期されます。アプリケーションがインデックスに直接書き込むことはありません。

また、LSIで設定したソートキーとパーティションキーは必ず一意になる必要性はなく、Queryオペレーションでは全て返却される。

ローカルセカンダリインデックスに同じソートキーを持つ複数の項目がある場合、Query オペレーションは、同じパーティションキーの値を持つすべての項目を返します。

一見、いいことづくしと思われる機能であるが、以下のような大きな制約がいくつかある、
・テーブル作成後の追加はできない(グローバルセカンダリインデックスは後から設定できる)
・GetItem および BatchGetItem オペレーションは使用できず、QueryおよびScanのみ使用可能

後からの追加はできないので、設計時に気をつけないといけない。

グローバルセカンダリインデックス(GSI)

LSIと同じような機能であるので詳細は割愛するが、LSIと大きく異なるのは、
・パーティションキーおよびソートキーどちらも設定ができる。
・ベーステーブルの作成後でも追加することができる。
・GSIは常にベーステーブルと同じテーブルクラスを使用する。

セカンダリインデックスは検索の柔軟性を高めるが、色々と考慮事項もあるので確認は必須である。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html

参考
DynamoDB深かったです!ありがとうございました!
公式
DynamoDBの基本についてまとめてみた
DynamoDB セカンダリインデックス
【DynamoDB】プライマリーキーを複合プライマリーキーにした方が良い場合
CAP理論についてのまとめ
「RDBMS」と「NoSQL」の比較
DynamoDBと「強力な整合性のある読み込み」とCAP定理のこと
DynamoDBの整合性モデル

Discussion