DynamoDBの基礎知識とRDB(リレーショナルデータベース)との違い

DynamoDBとは?フルマネージド型NoSQLデータベースの特徴

DynamoDBは、AWSが提供するフルマネージド型のNoSQLデータベースです。「フルマネージド」という言葉、ちょっと難しく聞こえるかもしれませんが、要するに「裏側の面倒なことは全部AWSがやってくれるよ」ということです。

具体的に何が嬉しいかというと、サーバーの構築やディスク容量の追加、OSのパッチ適用、バックアップの設定といったインフラ関連の作業が一切不要になります。データベースを触ったことがある方ならご存じかもしれませんが、MySQLやPostgreSQLのようなデータベースを本番環境で運用しようとすると、これらのインフラ管理だけで週の半分くらい持っていかれることもあります。でもDynamoDBなら、そうした作業から解放されてアプリケーション開発に集中できます。

そして何より特徴的なのが、そのパフォーマンスです。データ量が数件でも数億件でも、常に1桁ミリ秒(つまり0.001秒〜0.009秒)の応答速度を維持します。これは裏側でデータが自動的に複数のサーバーに分散保存される仕組みになっているからで、アクセスが増えても自動的に処理能力を拡張してくれます。

サーバーレスの考え方

DynamoDBはサーバーレスなデータベースとも呼ばれます。サーバーがないわけではなく、「サーバーの存在を意識しなくていい」という意味です。使った分だけ課金されるので、アクセスがない時はコストもほぼゼロになります。

RDB(MySQLやPostgreSQLなど)との根本的な考え方の違い

DynamoDBを理解する上で、これまで主流だったRDB(リレーショナルデータベース)とどこが違うのかを押さえておくのはとても大切です。ここが分かると、DynamoDBの設計で迷わなくなります。

RDBの基本的な考え方は「データを正規化して重複をなくすこと」です。例えば、ユーザー情報と注文情報を別テーブルに分けて、ユーザーIDを共通キーにして結合(JOIN)する、といった設計が典型的です。この方がデータの一貫性が保ちやすいからですね。

一方DynamoDBは、全く逆のアプローチをとります。「データの重複を許容して、読み取りパフォーマンスを優先する」という考え方です。JOIN操作がそもそも存在しないので、必要なデータは1回の検索で取得できるように、あえて同じデータを複数のアイテムに持たせたりします。

もう一つ大きな違いは、データの探し方です。RDBなら「年齢が20代のユーザーを抽出して、名前順にソート」といった柔軟なクエリがSQLで書けます。でもDynamoDBは、基本的には「このキーでデータを取る」というシンプルな操作しか得意ではありません。複雑な条件での検索や集計(SUMやAVGなど)は苦手分野です。

これは欠点のように聞こえるかもしれませんが、逆に言えば「単純なキー検索に特化することで、圧倒的なスケーラビリティを実現している」というのがDynamoDBの強みです。

DynamoDBが向いているユースケースと適していないケース

じゃあ、具体的にどんな時にDynamoDBを選べばいいのか。実はここが一番大事なところかもしれません。

DynamoDBが本当に力を発揮するのは、以下のような場面です。

向いているユースケース:

  • セッション管理:ログイン状態を保持するセッションデータは、有効期限があり、キー(セッションID)での読み書きがメインなので最適です
  • チャットや通知:リアルタイムで大量のメッセージが書き込まれるような場面でも、ミリ秒単位の応答を維持できます
  • IoTセンサーデータ:温度センサーなどから1秒間に何百件ものデータが送られてくるような場合でも、自動スケーリングで対応可能です
  • ゲームのプレイヤーデータ:プレイヤーのスコアや所持アイテムなど、アクセスパターンが明確なデータに向いています

一方で、こんな用途には不向きです。

向いていないユースケース:

  • 複雑なレポート作成:「今月の売上を店舗別・商品カテゴリ別に集計して…」といった分析クエリは苦手です(この用途にはAmazon Redshiftなどが適しています)
  • 多対多の複雑なリレーション:SNSのフォロー関係のように、複雑な関連性をたどるような検索は不向きです
  • アドホックなデータ分析:「このデータとあのデータを掛け合わせてみたい」といった、その場での柔軟な検索が求められる場面には向きません

適材適所の判断を

「DynamoDBは万能ではない」ということを知っておくことが重要です。複雑な集計やリレーションが必要な部分はRDBを使い、高速な読み書きが求められる部分はDynamoDBを使う、といったように、システム要件に応じて使い分けるのが現実的なアプローチです。

DynamoDBを構成するデータモデルの構造

RDB(リレーショナルデータベース)に慣れ親しんでいると、DynamoDBのデータの持ち方は少し新鮮に感じるかもしれません。ここでは、DynamoDBがどのような部品でデータを構成しているのか、その基本的なルールを紐解いていきましょう。

テーブル・アイテム・属性の関係

まずは大前提となるデータの構造要素です。DynamoDBもRDBと同じように、主に3つの階層でデータを管理します。

  • テーブル:データを入れる大きな箱です。RDBの「テーブル」と同じ役割を持ちます。
  • アイテム:テーブルの中にある1件分のデータです。RDBの「レコード(行)」に相当します。
  • 属性:アイテムが持っている個々のデータ項目です。RDBの「カラム(列)」に相当します。

言葉だけだと同じように聞こえますが、決定的な違いがあります。RDBでは、1つのレコードに含まれるカラムはテーブル定義によって固定されていますが、DynamoDBのアイテムは階層的な構造を持てるという点です。

例えば、RDBで「ユーザーの設定情報」を持とうとすると、別のテーブルを用意して外部キーで結合する必要があることが多いです。しかしDynamoDBでは、1つのアイテムの中に「設定情報」というまとまりをそのまま格納できます。データ構造がフラット(平面的)である必要がないため、アプリケーションコードで扱っているデータの形を、そのままデータベースに落とし込むことができるんです。

スキーマレスによる柔軟なデータ格納

DynamoDBのデータモデルを語る上で外せないのが「スキーマレス」という特徴です。

RDBでは、テーブルを作る際に「名前は文字列で最大100文字」「年齢は整数型」といった厳格なスキーマ(型や制約の定義)を事前に決める必要があります。新しいカラムを追加したくなったら ALTER TABLE を実行して、テーブルの構造自体を変更する手間がかかりますよね。

一方、DynamoDBにはそのような厳格な事前定義がありません。アイテムごとに、持っている属性の数や種類が違っていても全く問題ありません。

例えば、同じ「ユーザーテーブル」の中に、以下のような2つのアイテムが同居できます。

  • ユーザーA:user_idnameemailagephone_number を持っている
  • ユーザーB:user_idnameemail のみ持っている(年齢も電話番号も未入力)

RDBなら「電話番号がない場合はNULLを入れる」といった対応になりますが、DynamoDBではそもそも属性自体を書かなければよいだけです。そのため、アプリケーションの仕様変更で新たな項目が必要になった際も、データベース側の面倒な移行作業(マイグレーション)を最小限に抑えられます。コード側で新しい属性を保存するようにするだけで、システムがスムーズに拡張していくのが嬉しいポイントです。

スキーマレスの例外

スキーマレスとはいえ、すべてが完全に自由というわけではありません。テーブル作成時に決める「プライマリキー」(データを一意に識別するためのキー)だけは、必ずすべてのアイテムに存在している必要があります。この例外だけは押さえておきましょう。

ドキュメント形式でのデータ表現

階層構造を持てるという話と繋がりますが、DynamoDBは「ドキュメントデータベース」の性質も備えており、データをJSON形式に非常に近い形で格納できます。

最大の利点は、ネストされた構造(オブジェクト)や配列をそのまま保存できることです。

具体的な例を見てみましょう。あるユーザーのプロフィール情報を保存するケースを考えます。

{
  "user_id": "user_1234",
  "name": "山田太郎",
  "email": "yamada@example.com",
  "age": 28,
  "hobbies": ["プログラミング", "読書", "キャンプ"],
  "settings": {
    "theme": "dark",
    "receive_mail": true,
    "language": "ja"
  }
}

このように、hobbies には文字列の配列を、settings にはさらに階層化されたオブジェクトを直接含めることができます。

RDBでこれを表現しようとすると、「ユーザーテーブル」「趣味テーブル」「設定テーブル」のように複数のテーブルに分割し、取得する際に JOIN を使って結合するのが一般的です。しかしDynamoDBでは、この1つの塊(ドキュメント)として1回の操作でそのまま保存・取得できます。

ユーザー画面でプロフィールを表示するとき、関連するデータがすべて1つのアイテムに詰まっているため、無駄な読み込みが発生せず、高速な表示につながるわけです。データの形がアプリケーション側のJSONと似ているため、開発者にとっても非常に扱いやすいモデルと言えます。

設計の要となるキー(プライマリキー)の役割と考え方

DynamoDBのテーブル設計において、もっとも重要で後から変更できないのが「プライマリキー」の設計です。前のセクションでデータモデルの基本構造について触れましたが、ここでは「データがどこにどう配置されるか」を決定するプライマリキーの役割にフォーカスして掘り下げていきます。

データの物理的な分散を決めるパーティションキー

DynamoDBの裏側では、データは「パーティション」と呼ばれる物理的な保存領域に分けて格納されています。パーティションキーは、このデータを「どのパーティションに入れるか」を決めるためのハッシュ値の元になる値です。

イメージとしては、巨大な図書館に本をしまっていく際に、「本のタイトルの頭文字」を見てA〜Zの棚に振り分けているような状態です。パーティションキーの値にハッシュ関数が適用され、その結果に応じてデータが自動的に特定のパーティションに配置されます。

ここで大切なのが「カーディナリティ(値の種類の多さ)」です。データとトラフィックを全体にまんべんなく散らばすためには、できるだけばらつきのある値を選ぶ必要があります。

良い例と悪い例を比較してみましょう。

  • 良い例: user_iduser_001user_002…のようにユーザーごとに固有の値)
  • 悪い例: statusactiveinactiveなどのように数種類しかない値)

もしstatusをパーティションキーにしてしまった場合、システムの大半のデータが「active」のパーティションに押し込められることになります。これでは、図書館の「ア」の棚だけに本が山積みになってしまうのと同じ状態です。

パーティションのキャパシティ上限

DynamoDBの1つのパーティションが処理できる上限は、1秒あたり約3,000の読み取りキャパシティ、約1,000の書き込みキャパシティです。パーティションキーの設計を誤ると、この上限に早い段階で引っかかり、システム全体の性能が頭打ちになってしまいます。

データが均等に分散されていれば、テーブル全体で1秒間に数万〜数百万のリクエストを処理することも可能です。パフォーマンスの限界を引き上げるための第一歩は、「ばらつきのあるキーを選ぶこと」にあります。

同一パーティション内の並び替えに使うソートキー

パーティションキーだけでもテーブルは作れますが、より柔軟なデータ検索を行うために「ソートキー」を組み合わせることもできます。パーティションキーとソートキーの2つをセットにしたものを「複合プライマリキー」と呼びます。

ソートキーは、同じパーティションキーを持つデータの中で「並び順」を決めるものです。この並び順を利用すると、特定の範囲のデータをまとめて取り出す「範囲検索」が非常に効率的に行えます。

具体的なユースケースで考えてみましょう。SNSアプリで「特定のユーザーの投稿履歴を最新順に取得する」機能を実装するとします。このとき、以下のようにキーを設計します。

  • パーティションキー: user_id(例:user_123
  • ソートキー: created_at(例:2025-05-14T10:30:00Z

user_iduser_123のパーティションの中には、そのユーザーの投稿がすべて時系列順に並んで保存されています。そのため、「最新の10件だけ取得する」といった操作や、「2025年5月の投稿だけを取得する」といった期間指定の検索が、一瞬で完了します。

RDBではORDER BY句を使って検索後に並び替えますが、DynamoDBではデータを保存する時点で既にソート済みの状態で置かれているため、余計な処理なしに高速で結果を返せるのが特徴です。

ホットパーティション問題と書き込みシャーディング

パーティションキーの設計を誤ったり、やむを得ずアクセスが偏るデータ構造になったりした場合、「ホットパーティション」という問題に直面します。

ホットパーティションとは、特定のパーティションにだけアクセスやデータ量が集中してしまい、そのパーティションの処理能力の限界(先述の1秒あたり3,000読み取り・1,000書き込み)に達してしまう現象です。1つのパーティションがボトルネックになると、他のパーティションがまだ余裕を持っていても、エラーが発生してシステムが正常に動作しなくなります。

たとえば、リアルタイムで何百万ものログが送られてくるIoTシステムで、パーティションキーにdevice_type(センサーの種類)を使ってしまったとします。種類が「温度センサー」「湿度センサー」の2種類しかない場合、全ログがたった2つのパーティションに殺到し、あっという間に書き込み上限に達してしまいます。

スロットリングの危険性

ホットパーティションが発生すると、DynamoDBはスロットリング(リクエストの拒否)を開始します。エラーの原因が「たった1つのパーティションの限界」であるにもかかわらず、システム全体がダウンしたように見えてしまうため、設計段階での回避が必須です。

なお、現在のDynamoDBでは「アダプティブキャパシティ」という機能により、一時的なアクセス偏りは自動的に緩和される仕組みが導入されています。これにより、予期せぬアクセス集中に対してはシステム側が自動でキャパシティを調整してくれるようになりました。しかし、根本的に特定のキーにアクセスが集中し続けるほど極端な設計になっている場合は、この自動調整でも限界を超えてしまいます。

そのような限界を超えるケースを解決するための設計上の工夫が「書き込みシャーディング」です。

書き込みシャーディングとは、パーティションキーの先頭にランダムな文字列や数字を付与して、意図的にデータを複数のパーティションに分割して保存する手法です。

先ほどのログの例であれば、device_typeの値をそのまま使うのではなく、温度センサー#1温度センサー#2…のようにランダムなサフィックスを付与します。これにより、同じ「温度センサー」のデータでも、異なるパーティションに均等に散らばるようになります。

ただし、この方法にはトレードオフがあります。データの読み取り時に「どのサフィックスが付与されているか分からない」という問題が生じます。そのため、読み取り時には複数のパーティションにまたがって並列で検索を行うなどの工夫が必要になり、実装の複雑さが増す点には注意が必要です。書き込みシャーディングは「読み取りよりも書き込みのスループットが圧倒的に重要なケース」で選択するべき手段です。

アクセスパターンから逆算するDynamoDBのデータモデリング

「スキーマ先行」ではなく「アクセスパターン先行」の設計

これまでRDB(MySQLやPostgreSQLなど)を使ってきた方にとって、DynamoDBの設計で一番最初につまずくのが「設計のスタート地点」の違いだと思います。RDBでは「このシステムにはどんなデータが必要だろう?」とテーブルの構造(スキーマ)を決めることから始めますよね。でも、DynamoDBではそのアプローチが180度変わります。

DynamoDBでは、「どのようにデータを検索・取得したいか」というアクセスパターンを最初にすべて洗い出すところから設計をスタートさせます。

なぜこんなことをするのかというと、DynamoDBには「後から自由にクエリを書ける」という柔軟性がないからです。RDBなら、テーブルを作った後で「あ、今月登録されたユーザーだけ抽出しよう」というSQLをその場で思いついて実行できます。しかしDynamoDBでは、プライマリキーやインデックスの設定で想定されていない検索条件は、原則として極めて効率が悪い、あるいは事実上不可能です。

具体的には、例えばECサイトを作るなら「ユーザーIDから購入履歴を新しい順に5件取得したい」「商品IDに関連するレビューをすべて取得したい」といったリストを、エンジニアとデザイナー、プロダクトマネージャーで徹底的に話し合って出し切ります。このアクセスパターンの網羅が、DynamoDB設計の絶対的な成功の鍵になります。

設計変更の困難さ

DynamoDBのテーブル構造(特にプライマリキーとソートキーの設計)は、一度作成して運用を始めると後からの変更が困難になることが多いです。設計段階で「将来追加されるかもしれない機能」も含めてアクセスパターンを漏れなく洗い出すことが、目標達成に不可欠です。

Single Table Design(単一テーブル設計)の考え方

アクセスパターンが出揃ったら、次に驚くのがテーブルの分割ルールです。RDBでは「ユーザーテーブル」「商品テーブル」「注文テーブル」と、エンティティ(実体)ごとにテーブルを分けるのが当たり前でした。

しかし、DynamoDBではこれらを1つのテーブルに全部まとめてしまうSingle Table Design(単一テーブル設計)という考え方を標準的に採用します。「ユーザーと注文を同じテーブルに入れるの?」と最初は違和感があるかもしれませんが、これには明確な理由があります。

DynamoDBには、複数のテーブルを結合する「JOIN」という機能が存在しません。もし別々のテーブルにデータを分けてしまうと、アプリケーションのコード側で「まずユーザーテーブルを取得して、次に注文テーブルを取得して…」と複数回の通信を行わなければならず、せっかくのミリ秒単位の速度が台無しになってしまいます。

これを防ぐために、パーティションキー(PK)とソートキー(SK)を PK: USER#1234, SK: PROFILE のような形式にし、同じ PK: USER#1234 の中に SK: ORDER#5678 などの注文データも一緒に保存してしまいます。

実際にデータを取得する際は、Query APIを実行してパーティションキーに USER#1234 を指定します。さらに、ソートキーに対して begins_with(SK, 'ORDER#') のようなプレフィックス検索(前方一致検索)を活用することで、そのユーザーの注文データだけを一瞬でまとめて取得できます。このように、異なるエンティティを1つのテーブルに集約しつつ、キーの設計とPrefix検索を組み合わせることで、ネットワーク往復の少ない高速なデータ取得を実現します。

データの非正規化と結合(JOIN)の代替手段

Single Table Designを実践していく上で、絶対に避けて通れないのが「データの非正規化」です。

RDBの世界では、「同じデータを2つの場所に保存するのはムダだから、正規化して1箇所にまとめよう」と教えられますよね。例えば注文テーブルには「商品ID」だけを保存し、商品名は商品テーブルからJOINで取得するといった形です。しかし、先述した通りDynamoDBにはJOINがないため、この手法は使えません。

そこで、注文データの中に「商品ID」だけでなく「商品名」や「その時点での価格」まで、そのまま丸ごとコピーして保存します。これが非正規化です。

これには明確なトレードオフが伴います。データが重複するため、「商品名がタイポしていた!」と気づいて商品テーブル(または商品のアイテム)を修正したとしても、過去の何百件という注文データに保存されている商品名は自動的には更新されません。読み取り時のパフォーマンスは圧倒的に高くなりますが、データの更新時にはアプリケーション側で整合性を保つための仕組み(例えばイベント駆動で関連する注文データを一斉に更新する仕組みなど)を自分で実装するコストが発生します。

不整合リスクへの注意

データの非正規化を行うと、更新時に一部のデータだけが変更され、別のデータに古い情報が残ってしまう「不整合」を引き起こすリスクが高まります。システムの要件を満たすために「読み取りの速度」を優先するのか、「常に最新の正確なデータを保つこと」を優先するのかを十分に検討した上で設計を行ってください。

データを効率的に取得するためのクエリとインデックス

前のセクションでは、アクセスパターンから逆算してデータを設計する重要性をお話ししました。では実際に、設計したテーブルから「どうやって目的のデータを取り出すのか」というのが今回のテーマです。

DynamoDBには、RDBのようなSQL文がありません。代わりに、専用のAPI操作を使ってデータを取得します。ここで絶対に知っておきたいのが、データの取り出し方による「圧倒的なパフォーマンスの差」です。

Query(クエリ)とScan(スキャン)の明確な違い

DynamoDBでデータを探す際、大きく分けて2つのアプローチがあります。1つが「Query」、もう1つが「Scan」です。

Queryは、パーティションキー(データの振り分け先を決めるID)を指定してデータを取得する操作です。図書館に例えると、「2階の3列目の棚(特定のパーティション)」を直接開いて探すようなイメージです。そのため、テーブル全体に何百万件のデータがあっても、一瞬で目的のデータを見つけ出すことができます。ソートキーを設定していれば、「〇日以降のデータ」といった範囲指定も可能です。

一方でScanは、テーブルに保存されているデータを先頭から最後まで舐めるように全件検索する操作です。図書館の「すべての棚」を1冊ずつチェックしていくようなものです。データが数千件ならまだしも、数十万件、数百万件となると、取得にかかる時間は跳ね上がり、実行コストも高騰します。

Scanの使用は極力避ける

Scan操作はテーブルの全データを読み取るため、データ量が増えるほど応答時間が長くなり、その分しっかりと課金されます。本番環境のユーザー画面を描画するような場面での使用は、極力避けるようにしてください。

基本原則として、「データを取得するときは必ずQueryを基本とする」ことです。Queryを活用することで、常に安定したミリ秒レベルの高速応答を得ることができます。Scanは開発時のデータ確認や、バッチ処理などの特別な事情がある時だけ使うものと覚えておいて間違いありません。

インデックス(GSIとLSI)の活用

「Queryが高速なら、いつもQueryを使えばいいじゃないか」と思うかもしれません。しかし、現実のアプリケーションでは「パーティションキー以外の条件で検索したい」というケースが必ず出てきます。

例えば、パーティションキーに「ユーザーID」を設定していたとします。ログイン機能ではユーザーIDとパスワードで検索できるのでQueryで事足りますが、「メールアドレスを使ってパスワードをリセットしたい」という機能を追加したらどうでしょう?メールアドレスはパーティションキーではないため、Queryで一瞬に探すことができません。

ここで活躍するのが「インデックス」です。DynamoDBには用途に応じて2つのインデックスが用意されています。

1. グローバルセカンダリインデックス(GSI) GSIは、元のテーブルとは全く異なるパーティションキーとソートキーを指定して作成できる「もう1つの目次」です。上記の例であれば、「メールアドレスをパーティションキーにした新しい目次(GSI)」をテーブルに追加しておきます。これにより、ログイン時は本来のテーブルの目次を、パスワードリセット時はGSIの目次を見ることで、どちらのケースでも高速なQueryが使えるようになります。テーブル作成後でも自由に追加・削除が可能で、非常に柔軟なため最もよく使われます。

2. ローカルセカンダリインデックス(LSI) 一方で、パーティションキーは元のテーブルと同じままで、ソートキーだけを別の属性に変えたい場合に使うのがLSIです。例えば、パーティションキーが「ユーザーID」でソートキーが「作成日時」のテーブルがあるとき、「作成日時」ではなく「更新日時」で並び替えて範囲検索をしたい場合などに役立ちます。ただし、LSIはテーブル作成時にしか追加できないという制約があるため、事前のアクセスパターンの洗い出しがより重要になります。

インデションの追加にはコストがかかる

GSIやLSIを追加すると、そのインデックス用のストレージ容量と読み書きのキャパシティが別途消費されます。テーブル設計の段階で「本当にこのインデックスは必要か?」を見極め、むやみに追加しすぎないように注意しましょう。

フィルタリングの正しい使い方と注意点

QueryやScanを使っていると、「指定したパーティションキーのデータの中から、さらに『ステータスが完了のものだけ』を取り出したい」という場面が出てきます。この時に使えるのがFilter(フィルタリング)機能です。

RDBの「WHERE句」に慣れていると、「検索条件を指定しているんだから、余計なデータは読み込まれずにコストも安くなるはず」と思いがちです。しかし、ここでDynamoDBの大きな落とし穴が待っています。

実は、DynamoDBのFilterは「データを取得した後」に適用されます。 先ほどの図書館の例でいうと、Queryで「2階の3列目の棚から本をすべて持ってきた」あとに、出口で「あ、完了ステータスの本以外は棚に戻して」と選別している状態です。つまり、棚から全ての本を一度取り出している時点で、その分の読み込みコスト(キャパシティ)はすでに消費されてしまっているのです。

フィルタリングはコスト削減にならない

フィルタリングは、読み込みキャパシティの消費を抑えるためのものではありません。取得したデータをアプリケーション側で絞り込む手間を省くための機能です。コストを抑えたい場合は、Filterに頼るのではなく、ソートキーやGSIなどの設計でQueryの条件だけで必要なデータが特定できるようにするのが正解です。

「検索条件」なのに「絞り込み(コスト削減)」にならない。この仕組みは最初とても違和感がありますが、DynamoDBが「いかに物理的なデータ配置に近い形で効率よくデータを拾うか」に特化しているからこその仕様です。この特性を理解して上手く使っていくことで、無駄なコストを抑えつつ、高速なデータ取得を実現できるようになります。

パフォーマンスと運用管理を支える仕組み

ここまでデータの保存や取得の方法を見てきましたが、実際にシステムを運用し始めると「どれくらいの処理能力を確保すべきか」「古いデータはどうしよう」といった悩みが出てきます。DynamoDBは、こうしたパフォーマンスや運用管理の面でも、開発者が楽できる気の利いた仕組みを備えています。最後のセクションでは、運用を支える3つの重要な機能を解説します。

キャパシティーモード(オンデマンドとプロビジョンド)の選択

DynamoDBを使うとき、最初にぶつかる壁が「キャパシティの設定」です。DynamoDBでは、データの読み書きの処理能力を「キャパシティユニット」という単位で管理します。このキャパシティの確保方法には、大きく分けて2つのモードがあります。

1つ目は「プロビジョンドモード」です。これは、事前に「1秒間に何回読み取り(RCU)と書き込み(WCU)をするか」を宣言しておく方式です。例えば、毎秒50回の書き込みが見込めるなら、あらかじめ50 WCUを設定しておきます。トラフィックの予測が立てやすい常時稼働のシステムなどでは、このモードを選ぶことで無駄なリソースを削減でき、結果的にコストを最適化できます。

2つ目は「オンデマンドモード」です。こちらはキャパシティの事前設定が不要で、アクセス数に応じてDynamoDBが自動で処理能力を拡張してくれます。新規サービスの立ち上げ時や、バッチ処理などで突発的に大量のアクセスが発生するシステム、トラフィックの予測が困難なケースに非常に向いています。

どちらを選ぶべきかは、システムの特性次第です。ただし、オンデマンドモードはプロビジョンドモードと比べて1回あたりの単価が約2.5倍高い設定になっています。そのため、「安定したトラフィックが見込めるならプロビジョンド」「予測不能なアクセスならオンデマンド」というように、コストと運用の手間のバランスで選ぶのが実践的な判断基準になります。

キャパシティ超過によるスロットリング

プロビジョンドモードを選んだ場合、事前設定したキャパシティを超えてアクセスが集中すると「スロットリング(処理の絞り込み)」が発生し、エラーになります。逆に設定しすぎると、使わないキャパシティにも課金されてしまうため、最初はオンデマンドモードで様子を見るのも一つの手です。

アダプティブキャパシティによるワークロードの均等化

前のセクションで「ホットパーティション(特定のパーティションにアクセスが集中する現象)」の問題に触れましたが、DynamoDBにはこれを自動的に緩和する仕組みがあります。それが「アダプティブキャパシティ」です。

通常、テーブルに割り当てたキャパシティは、内部のパーティションに均等に分割されます。例えばテーブル全体で1秒間に3000回の読み取り(3000 RCU)を確保していて、データが3つのパーティションに分かれていれば、それぞれ1000 RCUずつ割り当てられます。

ここで、何らかの拍子に1つのパーティションにだけ1500 RCU分のアクセスが集中したとします。均等割りなら、この時点で上限オーバーのエラーになりかねません。しかし、アダプティブキャパシティが有効になっていると、DynamoDBは「今はこのパーティションが空いているから、そちらの余裕分を融通しよう」と判断し、一時的にアクセスが集中しているパーティションのキャパシティを増やして対応します。

これにより、一時的なアクセスの偏りによってシステムが停止してしまうリスクを防ぎ、継続的なパフォーマンスを維持できるのです。

ただし、アダプティブキャパシティはあくまで「一時的な偏りを助けるセーフティネット」です。 パーティションキーの設計が甘く、常に特定のデータにアクセスが集中している状態(例:「ステータス」という数パターンしかない値をパーティションキーにしてしまった等)では、他から融通できる枠にも限界があります。根本的な解決には、前回紹介した「書き込みシャーディング」などの適切なキー設計が不可欠です。

データのライフサイクル管理(TTL)と高可用性

システムを運用していると、「ログデータやセッション情報など、一定期間を過ぎたものは不要だから自動で消したい」という場面がよくあります。これを実現してくれるのが「TTL(Time to Live)」機能です。

TTLは、テーブル内の特定の属性(例えば expire_at など)に、データの有効期限をUNIX時間(1970年1月1日0時0分0秒からの経過秒数)で設定しておく機能です。この時刻を過ぎたデータは、DynamoDBのバックグラウンドプロセスによって自動的に削除されます。

例えば、ユーザーのログインセッションデータに「1時間後のUNIX時間」をTTLとしてセットしておけば、1時間経過したデータは勝手に消えていきます。アプリケーション側で削除処理を書く必要がなくなるため、コードがスッキリするだけでなく、不要なストレージ容量を圧迫せずに済むためコスト削減にも直結します。

TTLによる削除のタイミング

TTLによる削除処理は、DynamoDB側で非同期に行われます。そのため、有効期限を過ぎた瞬間に即座に消えるわけではなく、最大で48時間程度の遅延が発生する可能性があります。また、この削除処理自体は読み書きのキャパシティを消費しないため、コストを気にせずに使うことができます。

さらに、DynamoDBを支える大きな強みとして「高可用性」が挙げられます。DynamoDBはデータを保存する際、自動的に複数のアベイラビリティゾーン(AZ:データセンターが物理的に分離された区画のこと)にデータを複製(レプリケーション)します。

もし何らかのトラブルで1つのデータセンターが停止してしまっても、別のデータセンターにある同じデータを使って継続してサービスを提供できます。これもフルマネージドサービスであるDynamoDBならではの恩恵で、私たち開発者は複雑な障害対応の設計を意識せずとも、高い耐障害性を持ったシステムを構築できるのです。

まとめ:DynamoDBのメリット・デメリットと適材適所

ここまで、DynamoDBの基礎的な仕組みからデータモデリング、運用に至るまでのエッセンスを解説してきました。最後に、DynamoDBをシステムに導入する際の判断材料として、メリットとデメリットを改めて整理します。

DynamoDBのメリット

  • 圧倒的なスケーラビリティとパフォーマンス:データ量やトラフィックが急増しても、常にミリ秒単位の応答速度を維持します。サーバーの増強などを意識する必要はありません。
  • 運用の大幅な軽減:OSのパッチ適用やバックアップなどのインフラ管理が不要になるため、開発者はアプリケーションの機能開発にリソースを注げます。
  • 高い耐障害性:標準で複数のデータセンター(AZ)にデータが自動レプリケーションされるため、物理的な障害に強いシステムを簡単に構築できます。

DynamoDBのデメリット

  • 複雑な検索や集計が不得意:RDBのように、後から思いついた条件で柔軟にデータを検索(JOINや複雑な集計)することが苦手です。
  • 設計の難易度が高い:「アクセスパターン先行」の設計やSingle Table Designなど、RDBとは根本的に異なる考え方が求められるため、最初は学習コストがかかります。
  • データの不整合リスク:高速な読み取りを実現するためにデータの非正規化(重複)を行うため、更新時のデータ整合性を保つ実装が複雑になることがあります。

適材適所の考え方

DynamoDBは「何でもできる万能なデータベース」ではありません。したがって、システム全体をDynamoDBだけで構築するのではなく、RDBと組み合わせた「適材適所」のアプローチが現実のプロジェクトでは最も効果的です。

例えば、ユーザーのプロフィール情報や複雑な要件を伴う注文データの保存にはRDB(MySQLやPostgreSQL)を使用し、ログインセッションの管理、リアルタイムなチャット履歴、IoTデバイスからの大量データの受け口としてはDynamoDBを採用する、といった使い分けです。

システムが求めている要件(一貫性なのか、それとも圧倒的なスピードとスケールなのか)を見極め、DynamoDBの特徴を正しく活用することで、強靭で柔軟なモダンなアーキテクチャを構築できるようになります。