LoginSignup
103
87

More than 3 years have passed since last update.

【初心者向け】DynamoDBを理解する〜Pythonを用いた取得パターン特化編〜

Posted at

DynamoDBとは

Amazon DynamoDBは、フルマネージド型のNoSQLデータベースサービスで、
高速で予測可能なパフォーマンスとシームレスなスケーラビリティを特長としています。

大切なことは全部公式ドキュメントが言ってるので、
簡単にまとめると管理不要のNoSQLデータベースサービスのこと。

そもそもNoSQLって?

リレーショナルモデルの代わりに、キーと値のペアやドキュメントストレージなど、データ管理のための代替モデルを使用します。

RDB(リレーショナルデータベース)はデータを保持するテーブル間に関係性があり、
それぞれのトランザクションがデータの一貫性(ACID)を保つように設計されています。

具体的には物理的なレコードのロック(他のトランザクションからの取得・更新などを一時的に待機させる)やロールバック・コミットなどのトランザクション制御指示などによりACIDが実現されます。
しかし、実行速度や拡張性に難が有ります。

実行速度や拡張性に関わる要件が、需要を増して生まれたのがNoSQLです。

NoSQLには様々な要件ごとにソフトが開発され、それはRDBでできた機能を実現したりしなかったり、それまでになかった機能を実現したりと、
多岐にわたるバリエーションのソフトがあります。(当たり前ですが、全てのNoSQLサービスが同一の機能を有してません)
ユースケースとしては、堅実な一貫性を取るならRDB、実行速度や拡張性(フェーズによるデータ増加や、ACIDを犠牲にしたデータ保持)を重視するならNoSQLと考えれば良いでしょう。

DynamoDBの特徴

キー・バリュー形式の単純なデータを表形式で保持し、高速で拡張性(スケールインなど)の高いDBです。
メインの構成は、データを保持するテーブル・テーブル内に定義された項目・項目に定義する属性でできており、RDBと似たテーブル構造になります。
※RDBの構造化データに対し、DynamoDBは半構造化データのテーブルを作成できます。
そのため、主キーやインデックス以外はテーブル作成時に定義不要です

しかし、検索に制限が多く、基本的には後述するプライマリキー(主キー)を使用した検索になると考えてください。

DynamoDB用語解説

「プライマリキー」

テーブルのレコードを一意に識別するもので、テーブル作成時に以下の2種類から選択する。

・パーティションキー
→1つのキーで主キーとなる場合に採用

・パーティションキーとソートキー(別名:複合プライマリキー)
→2つのキーで主キーとなる場合に採用
※ソートキーに指定した項目は範囲検索が可能になる。

「セカンダリインデックス」

プライマリキー以外での検索に対応したい場合に設定する。
以下の2種類から選択する。

・グローバルセカンダリーインデックス(GSI)
→プライマリキーとは異なるキーで検索したい場合に採用
※設定したソートキーをパーティションキーにするようなことも可能

・ローカルセカンダリインデックス(LSI)
パーティションキーとLSI(任意の項目)の組み合わせで検索したい場合に採用
※複合プライマリキー設定時のみ設定可能
※テーブル作成時にのみ設定可能

データ取得パターン

以下の3パターンが存在する

・getitem(主キー検索)
・query(複合プライマリキー設定時の範囲検索・セカンダリインデックスでの検索)
・scan(全レコード取得)

Pythonからの検索

PythonからDynamoDBにアクセスする際は、AWS SDK for Python (Boto 3)を使用する。
以下のサンプルはlambdaを使用したサンプル例

準備

sample.py
#boto3のimport
import boto3

#lambdaのためのハンドラ(今回の解説対象外)
def lambda_handler(event, context):

    #boto3からDynamoDBアクセスのためのオブジェクト取得
    dynamodb = boto3.resource('dynamodb')

    #事前に作成したテーブル(今回は"sample1")へのアクセスオブジェクト取得
    table = dynamodb.Table('sample1')

getitem(主キー検索)

複合プライマリキー設定時の例
ちなみに、ソートキーを検索パラメータに設定しなかった場合、エラーになる。

sample.py
import boto3

def lambda_handler(event, context):

    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('sample1')

    #getItemメソッドの呼び出し(主キー検索)
    response = table.get_item(
        #パラメーターとして主キー情報(辞書型)を渡す
        #Keyという変数名?は固定(違う名前だとエラーになる)
        Key={
            #主キー情報を設定
            #今回はテーブルにid(プライマリーキー)・sex(ソートキー)を定義した
            'id': '001',
            'sex': 'man'
        }
    )
    #responseの正体は、Itemなどのキーが定義された辞書型オブジェクト
    print(response)

    #結果の取得
    item = response['Item']

    #辞書型オブジェクトとして取得できる(テーブルのカラムが定義されている)
    #キーに一致するものがない場合、エラーとなる
    print(item)

query

sample.py
import boto3
#Keyオブジェクトを使用できるようにする(これがないとエラーになる)
#謎エラーで詰まったら、importの有無を確認した方が良い
from boto3.dynamodb.conditions import Key

def lambda_handler(event, context):

    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('sample1')

    #複合プライマリキー設定時、プライマリーキーのみの検索
    #以下の記載でプライマリーキー以外を指定した場合はエラーとなる
    response = table.query(
        KeyConditionExpression=Key('id').eq('001')
    )
    #getItem同様、辞書型の情報が取得される
    print(response)

    #取得件数も取れる
    print(response['Count'])

    #取得レコードは"item"ではなく"items"!
    items = response['Items']

    #複数件取れるインターフェイスのためlistオブジェクトが格納されている
    #取得結果なしの場合は、0件のlistが取得される
    print(type(items))

    #その他検索パターン

    #ソートキーの範囲検索
    #条件が複数ある場合は、"&"or"|"を使用する
    response = table.query(
        KeyConditionExpression=Key('id').eq('001') & Key('sex').begins_with('m')
    )

    #LSIを使用した検索(基本的なやり方は複合プライマリキー時の検索と同じ)
    #LSIはでの検索は1件に絞れる保証がないため、getItemは使用不可
    response = table.query(
        #設定したLSIのインデックスNameを指定する
        IndexName='id-name-index',
        KeyConditionExpression=Key('id').eq('001') & Key('name').eq('mama')
    )

    #GSIを使用した検索(基本的なやり方はLSIの検索と同じ)
    response = table.query(
        #設定したGSIのインデックスNameを指定する
        IndexName='location-index',
        KeyConditionExpression=Key('location').eq('Tokyo')
    )

Scan(全件検索)

sample.py
import boto3

def lambda_handler(event, context):

    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('sample1')

    #シンプルにscanメソッドの呼び出しをする
    response = table.scan()

    #getItem同様、辞書型の情報が取得される
    print(response)

    #取得件数も取れる
    print(response['Count'])

    #取得レコードは"item"ではなく"items"!
    items = response['Items']

    #複数件取れるインターフェイスのためlistオブジェクトが格納されている
    #取得結果なしの場合は、0件のlistが取得される
    print(type(items))

また、scanでの全件取得結果をもとにフィルターをかけ、該当データのみ抽出することも可能です。
※キー設定でどうしようもなくなった場合の最後の手段としましょう。

まとめ

テーブルのキー設定のバリエーション
・プライマリキー × GSI
・複合プライマリキー
・複合プライマリキー × LSI
・複合プライマリキー × GSI
・複合プライマリキー × LSI × GSI

それぞれのキー設定で行える検索パターン

キー設定 scan getItem query
プライマリキー ×
複合プライマリキー ○ (PK(必須)+SK(任意・範囲指定化))
LSI - - ○(複合プライマリキーのquery検索パターンのソートキーを切り替えた検索)
GSI - - ○(任意の項目を用いた検索)

上記を意識して、適切なキー設定を行えるとRDB脳からの切り替えがうまくいくと思います。

103
87
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
103
87