Qtの応用 - AWS DynamoDB
概要
AWS DynamoDBは、AWSが提供するフルマネージド型のNoSQLデータベースサービスである。
キー・バリュー型およびドキュメント型のデータモデルをサポートし、高速で予測可能なパフォーマンスとシームレスなスケーラビリティを実現する。
主な特徴を以下に示す。
- フルマネージド
- サーバ管理、ソフトウェアパッチ、セットアップ不要
- 高パフォーマンス
- 一桁ミリ秒のレスポンスタイム
- 自動スケーリング
- トラフィックに応じて自動的に容量を調整
- 高可用性
- 複数のAZに自動的にデータを複製
- 柔軟なスキーマ
- 各アイテムが異なる属性を持つことが可能
AWS DynamoDBのデータモデルを以下に示す。
- テーブル
- データを格納する最上位のコンテナ
- アイテム
- テーブル内の個々のレコード
- 属性
- アイテムを構成するデータフィールド (カラムに相当)
- プライマリキー
- パーティションキー (必須) と ソートキー (オプション) で構成
AWS DynamoDBは永続的な無料利用枠を提供しており、個人利用や小規模なアプリケーションであれば無料枠内で運用できることが多い。
AWS SDK for C++のインストール
Qtの応用_-_AWS#AWS_SDK_for_C++のインストールのページを参照すること。
1件のデータ挿入 : PutItem
テーブルに新しいアイテムを追加する。
// アイテムを挿入
Aws::DynamoDB::Model::PutItemRequest putReq;
putReq.SetTableName(tableName);
putReq.AddItem("id", toAttr(1));
putReq.AddItem("name", toAttr("Taro Yamada"));
putReq.AddItem("age", toAttr(30));
putReq.AddItem("email", toAttr("taro@example.com"));
auto putResult = client.PutItem(putReq);
if (putResult.IsSuccess()) {
qDebug() << "✓ Item inserted successfully";
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(putResult.GetError().GetMessage());
}
1件のデータ取得 :GetItem
プライマリキーを指定して1件のアイテムを取得する。
// プライマリキーでアイテムを取得
Aws::DynamoDB::Model::GetItemRequest getReq;
getReq.SetTableName(tableName);
getReq.AddKey("id", toAttr(1));
auto getResult = client.GetItem(getReq);
if (getResult.IsSuccess()) {
const auto& item = getResult.GetResult().GetItem();
if (!item.empty()) {
printItem(item);
}
else {
qDebug() << "Item not found";
}
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(getResult.GetError().GetMessage());
}
複数件のデータ取得 : Query
パーティションキーを条件として複数件のアイテムを取得する。
// クエリで複数件取得 (パーティションキーでフィルタ)
Aws::DynamoDB::Model::QueryRequest queryReq;
queryReq.SetTableName(tableName);
queryReq.SetKeyConditionExpression("id = :value");
queryReq.AddExpressionAttributeValues(":value", toAttr(1));
// オプション : 取得件数制限
queryReq.SetLimit(10);
auto queryResult = client.Query(queryReq);
if (queryResult.IsSuccess()) {
const auto& items = queryResult.GetResult().GetItems();
qDebug() << "Found" << items.size() << "items";
for (const auto& item : items) {
printItem(item);
}
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(queryResult.GetError().GetMessage());
}
ソートキー条件を含むクエリ
パーティションキーとソートキーを組み合わせた条件でデータを取得する。
// パーティションキーとソートキーでクエリ
Aws::DynamoDB::Model::QueryRequest queryReq;
queryReq.SetTableName(tableName);
// 例: userId = 100 かつ timestamp > 1234567890
queryReq.SetKeyConditionExpression("userId = :uid AND #ts > :time");
// 属性名のエイリアス(予約語の場合に使用)
queryReq.AddExpressionAttributeNames("#ts", "timestamp");
// 属性値
queryReq.AddExpressionAttributeValues(":uid", toAttr(100));
queryReq.AddExpressionAttributeValues(":time", toAttr(1234567890));
auto queryResult = client.Query(queryReq);
if (queryResult.IsSuccess()) {
const auto& items = queryResult.GetResult().GetItems();
qDebug() << "Found" << items.size() << "items";
for (const auto& item : items) {
printItem(item);
}
}
全件取得 : Scan
テーブル内の全アイテムをスキャンして取得する。
Scanは全テーブルスキャンのため、大量データには使用しない。
Queryを優先すること。
// テーブル全体をスキャン
Aws::DynamoDB::Model::ScanRequest scanReq;
scanReq.SetTableName(tableName);
// オプション: 取得件数制限
scanReq.SetLimit(100);
auto scanResult = client.Scan(scanReq);
if (scanResult.IsSuccess()) {
const auto& items = scanResult.GetResult().GetItems();
qDebug() << "Found" << items.size() << "items";
for (const auto& item : items) {
printItem(item);
}
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(scanResult.GetError().GetMessage());
}
フィルタ条件付きスキャン
スキャン時に特定の条件でフィルタリングする。
// 条件付きスキャン(例: age > 25)
Aws::DynamoDB::Model::ScanRequest scanReq;
scanReq.SetTableName(tableName);
scanReq.SetFilterExpression("age > :minAge");
scanReq.AddExpressionAttributeValues(":minAge", toAttr(25));
auto scanResult = client.Scan(scanReq);
if (scanResult.IsSuccess()) {
const auto& items = scanResult.GetResult().GetItems();
qDebug() << "Found" << items.size() << "items with age > 25";
for (const auto& item : items) {
printItem(item);
}
}
データの更新 : UpdateItem
既存のアイテムの属性を更新する。
// アイテムを更新
Aws::DynamoDB::Model::UpdateItemRequest updateReq;
updateReq.SetTableName(tableName);
updateReq.AddKey("id", toAttr(1));
// 更新式: SET age = 31, email = "new@example.com"
updateReq.SetUpdateExpression("SET age = :newAge, email = :newEmail");
updateReq.AddExpressionAttributeValues(":newAge", toAttr(31));
updateReq.AddExpressionAttributeValues(":newEmail", toAttr("new@example.com"));
auto updateResult = client.UpdateItem(updateReq);
if (updateResult.IsSuccess()) {
qDebug() << "✓ Item updated successfully";
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(updateResult.GetError().GetMessage());
}
条件付き更新
特定の条件を満たす場合のみ更新を実行する。
// 条件付き更新(例: ageが30の場合のみ更新)
Aws::DynamoDB::Model::UpdateItemRequest updateReq;
updateReq.SetTableName(tableName);
updateReq.AddKey("id", toAttr(1));
updateReq.SetUpdateExpression("SET age = :newAge");
updateReq.AddExpressionAttributeValues(":newAge", toAttr(31));
// 条件式
updateReq.SetConditionExpression("age = :currentAge");
updateReq.AddExpressionAttributeValues(":currentAge", toAttr(30));
auto updateResult = client.UpdateItem(updateReq);
if (updateResult.IsSuccess()) {
qDebug() << "✓ Conditional update succeeded";
}
else {
if (updateResult.GetError().GetErrorType() == Aws::DynamoDB::DynamoDBErrors::CONDITIONAL_CHECK_FAILED) {
qDebug() << "Condition not met - update skipped";
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(updateResult.GetError().GetMessage());
}
}
データの削除 : DeleteItem
プライマリキーを指定してアイテムを削除する。
// アイテムを削除
Aws::DynamoDB::Model::DeleteItemRequest delReq;
delReq.SetTableName(tableName);
delReq.AddKey("id", toAttr(1));
auto delResult = client.DeleteItem(delReq);
if (delResult.IsSuccess()) {
qDebug() << "✓ Item deleted successfully";
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(delResult.GetError().GetMessage());
}
条件付き削除
特定の条件を満たす場合のみ削除を実行する。
// 条件付き削除
// 例: statusが"inactive"の場合のみ削除
Aws::DynamoDB::Model::DeleteItemRequest delReq;
delReq.SetTableName(tableName);
delReq.AddKey("id", toAttr(1));
delReq.SetConditionExpression("#status = :statusValue");
delReq.AddExpressionAttributeNames("#status", "status");
delReq.AddExpressionAttributeValues(":statusValue", toAttr("inactive"));
auto delResult = client.DeleteItem(delReq);
if (delResult.IsSuccess()) {
qDebug() << "✓ Conditional delete succeeded";
}
else {
if (delResult.GetError().GetErrorType() ==
Aws::DynamoDB::DynamoDBErrors::CONDITIONAL_CHECK_FAILED) {
qDebug() << "Condition not met - delete skipped";
}
}
バッチ書き込み : BatchWriteItem
複数のアイテムを1度に挿入または削除する。
BatchWriteItemは最大25件まで1度に処理可能である。
#include <aws/dynamodb/model/BatchWriteItemRequest.h>
#include <aws/dynamodb/model/WriteRequest.h>
// バッチ書き込み
Aws::DynamoDB::Model::BatchWriteItemRequest batchReq;
// 複数のPutRequestを作成
std::vector<Aws::DynamoDB::Model::WriteRequest> writeRequests;
for (int i = 1; i <= 5; i++) {
Aws::DynamoDB::Model::PutRequest putRequest;
Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue> item;
item["id"] = toAttr(i);
item["name"] = toAttr(QString("User %1").arg(i));
item["age"] = toAttr(20 + i);
putRequest.SetItem(item);
Aws::DynamoDB::Model::WriteRequest writeReq;
writeReq.SetPutRequest(putRequest);
writeRequests.push_back(writeReq);
}
batchReq.AddRequestItems(tableName, writeRequests);
auto batchResult = client.BatchWriteItem(batchReq);
if (batchResult.IsSuccess()) {
qDebug() << "✓ Batch write completed";
// 未処理のアイテムがあるか確認
const auto& unprocessed = batchResult.GetResult().GetUnprocessedItems();
if (!unprocessed.empty()) {
qDebug() << "Warning: Some items were not processed";
}
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(batchResult.GetError().GetMessage());
}
バッチ読み込み : BatchGetItem
複数のアイテムを1度に取得する。
#include <aws/dynamodb/model/BatchGetItemRequest.h>
#include <aws/dynamodb/model/KeysAndAttributes.h>
// バッチ取得
Aws::DynamoDB::Model::BatchGetItemRequest batchGetReq;
Aws::DynamoDB::Model::KeysAndAttributes keysAndAttrs;
// 取得したいキーのリストを作成
std::vector<Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue>> keys;
for (int i = 1; i <= 3; i++) {
Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue> key;
key["id"] = toAttr(i);
keys.push_back(key);
}
keysAndAttrs.SetKeys(keys);
batchGetReq.AddRequestItems(tableName, keysAndAttrs);
auto batchGetResult = client.BatchGetItem(batchGetReq);
if (batchGetResult.IsSuccess()) {
const auto& responses = batchGetResult.GetResult().GetResponses();
for (const auto& tablePair : responses) {
qDebug() << "Table:" << QString::fromStdString(tablePair.first);
qDebug() << "Items count:" << tablePair.second.size();
for (const auto& item : tablePair.second) {
printItem(item);
}
}
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(batchGetResult.GetError().GetMessage());
}
ページネーション (継続トークン)
大量のデータを取得する際に、ページネーションを使用して分割取得する。
// ページネーション付きスキャン
Aws::DynamoDB::Model::ScanRequest scanReq;
scanReq.SetTableName(tableName);
scanReq.SetLimit(10); // 1ページあたり10件
Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue> lastKey;
int pageNum = 1;
do {
if (!lastKey.empty()) {
scanReq.SetExclusiveStartKey(lastKey);
}
auto scanResult = client.Scan(scanReq);
if (scanResult.IsSuccess()) {
const auto& items = scanResult.GetResult().GetItems();
qDebug() << "\n=== Page" << pageNum << "===" << items.size() << "items";
for (const auto& item : items) {
printItem(item);
}
// 次のページがあるか確認
lastKey = scanResult.GetResult().GetLastEvaluatedKey();
pageNum++;
}
else {
qDebug() << "✗ Error: " << QString::fromStdString(scanResult.GetError().GetMessage());
break;
}
} while (!lastKey.empty());
qDebug() << "Total pages processed:" << (pageNum - 1);
トランザクション書き込み : TransactWriteItems
複数の操作をトランザクションとして実行する。(全成功または全失敗)
トランザクションは最大100項目まで可能である。
#include <aws/dynamodb/model/TransactWriteItemsRequest.h>
#include <aws/dynamodb/model/TransactWriteItem.h>
// トランザクション書き込み
Aws::DynamoDB::Model::TransactWriteItemsRequest transactReq;
std::vector<Aws::DynamoDB::Model::TransactWriteItem> transactItems;
// アイテム1: 挿入
Aws::DynamoDB::Model::TransactWriteItem item1;
Aws::DynamoDB::Model::Put put1;
Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue> newItem;
newItem["id"] = toAttr(100);
newItem["name"] = toAttr("Transaction User");
newItem["balance"] = toAttr(1000);
put1.SetTableName(tableName);
put1.SetItem(newItem);
item1.SetPut(put1);
transactItems.push_back(item1);
// アイテム2: 更新
Aws::DynamoDB::Model::TransactWriteItem item2;
Aws::DynamoDB::Model::Update update2;
update2.SetTableName(tableName);
Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue> key2;
key2["id"] = toAttr(101);
update2.SetKey(key2);
update2.SetUpdateExpression("SET balance = balance - :amount");
update2.AddExpressionAttributeValues(":amount", toAttr(100));
item2.SetUpdate(update2);
transactItems.push_back(item2);
transactReq.SetTransactItems(transactItems);
auto transactResult = client.TransactWriteItems(transactReq);
if (transactResult.IsSuccess()) {
qDebug() << "✓ Transaction completed successfully";
}
else {
qDebug() << "✗ Transaction failed: " << QString::fromStdString(transactResult.GetError().GetMessage());
}
エラーハンドリング
DynamoDB操作時の一般的なエラーハンドリングパターンを以下に示す。
auto result = client.GetItem(getReq);
if (!result.IsSuccess()) {
const auto& error = result.GetError();
switch (error.GetErrorType()) {
case Aws::DynamoDB::DynamoDBErrors::RESOURCE_NOT_FOUND:
qDebug() << "テーブルが見つかりません";
break;
case Aws::DynamoDB::DynamoDBErrors::PROVISIONED_THROUGHPUT_EXCEEDED:
qDebug() << "スループット制限を超えました。リトライしてください。";
break;
case Aws::DynamoDB::DynamoDBErrors::CONDITIONAL_CHECK_FAILED:
qDebug() << "条件チェックに失敗しました";
break;
case Aws::DynamoDB::DynamoDBErrors::VALIDATION:
qDebug() << "入力検証エラー: " << QString::fromStdString(error.GetMessage());
break;
default:
qDebug() << "エラー: " << QString::fromStdString(error.GetMessage());
break;
}
}
サンプルコード
以下の例では、AWS DynamoDBにアクセスしてデータを取得、挿入、更新、削除している。
#include <QCoreApplication>
#include <QDebug>
#include <aws/core/Aws.h>
#include <aws/dynamodb/DynamoDBClient.h>
#include <aws/dynamodb/model/PutItemRequest.h>
#include <aws/dynamodb/model/GetItemRequest.h>
#include <aws/dynamodb/model/DeleteItemRequest.h>
#include <aws/dynamodb/model/UpdateItemRequest.h>
#include <aws/dynamodb/model/ScanRequest.h>
// ヘルパー関数
Aws::DynamoDB::Model::AttributeValue toAttr(const QVariant& value)
{
Aws::DynamoDB::Model::AttributeValue attr;
if (value.typeId() == QMetaType::QString)
attr.SetS(value.toString().toStdString());
else if (value.typeId() == QMetaType::Bool)
attr.SetBool(value.toBool());
else
attr.SetN(value.toString().toStdString());
return attr;
}
void printItem(const Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue> &item)
{
qDebug() << "Item:";
for (const auto& pair : item) {
QString key = QString::fromStdString(pair.first);
QString value;
if (!pair.second.GetS().empty())
value = QString::fromStdString(pair.second.GetS());
else if (!pair.second.GetN().empty())
value = QString::fromStdString(pair.second.GetN());
else if (pair.second.GetBool())
value = "true";
qDebug() << " " << key << ":" << value;
}
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Aws::SDKOptions options;
Aws::InitAPI(options);
{
// DynamoDBクライアント作成 (~/.aws/configからリージョンを自動取得)
Aws::Client::ClientConfiguration config;
// リージョンの指定を削除することで、AWS設定ファイルから自動取得
Aws::DynamoDB::DynamoDBClient client(config);
const std::string tableName = "<DynamoDBのテーブル名>";
qDebug() << "=== DynamoDB CRUD Demo ===\n";
// 挿入 : PutItem
qDebug() << "### 1. Insert Item ###";
Aws::DynamoDB::Model::PutItemRequest putReq;
putReq.SetTableName(tableName);
putReq.AddItem("id", toAttr(1));
putReq.AddItem("name", toAttr("Taro Yamada"));
putReq.AddItem("age", toAttr(30));
putReq.AddItem("email", toAttr("taro@example.com"));
if (client.PutItem(putReq).IsSuccess())
qDebug() << "✓ Item inserted";
// 取得 : GetItem
qDebug() << "\n### 2. Get Item ###";
Aws::DynamoDB::Model::GetItemRequest getReq;
getReq.SetTableName(tableName);
getReq.AddKey("id", toAttr(1));
auto getResult = client.GetItem(getReq);
if (getResult.IsSuccess())
printItem(getResult.GetResult().GetItem());
// 更新 : UpdateItem
qDebug() << "\n### 3. Update Item ###";
Aws::DynamoDB::Model::UpdateItemRequest updateReq;
updateReq.SetTableName(tableName);
updateReq.AddKey("id", toAttr(1));
updateReq.SetUpdateExpression("SET age = :val, email = :email");
updateReq.AddExpressionAttributeValues(":val", toAttr(31));
updateReq.AddExpressionAttributeValues(":email", toAttr("taro.new@example.com"));
if (client.UpdateItem(updateReq).IsSuccess()) {
qDebug() << "✓ Item updated";
printItem(client.GetItem(getReq).GetResult().GetItem());
}
// 全件取得 : Scan
qDebug() << "\n### 4. Scan Table ###";
Aws::DynamoDB::Model::ScanRequest scanReq;
scanReq.SetTableName(tableName);
auto scanResult = client.Scan(scanReq);
if (scanResult.IsSuccess()) {
qDebug() << "Found" << scanResult.GetResult().GetItems().size() << "items";
for (const auto& item : scanResult.GetResult().GetItems())
printItem(item);
}
// 削除 : DeleteItem
qDebug() << "\n### 5. Delete Item ###";
Aws::DynamoDB::Model::DeleteItemRequest delReq;
delReq.SetTableName(tableName);
delReq.AddKey("id", toAttr(1));
if (client.DeleteItem(delReq).IsSuccess())
qDebug() << "✓ Item deleted";
}
Aws::ShutdownAPI(options);
return 0;
}