🔥

DynamoDB設計の失敗と学び ― 技術ではなく設計でつまずいた話

に公開
2026/04/20

1人アドベントカレンダー2025 15日目。

DynamoDB設計でつまずいた経験を振り返ります。

はじめに

DynamoDB は「スケールする」「速い」「運用が楽」と言われがちですが、
設計思想(アクセスパターン)を実装へ落とし切れないと、後半で大きなやり直しが発生するタイプのDBです。

この記事は、BFF × Java 構成の実案件で

  • 技術選定として Java + DynamoDB は間違っていなかった一方で
  • 実装が Scan 寄りになり、ST 以降でボトルネックが顕在化
  • 設計意図と実装がズレていたことに後から気づき、手戻りが発生しました。

という経験を、できるだけフラットに振り返り、
「次に同じ状況が来たとき、どう判断・どう防ぐべきか」を整理します。

※ここでの反省は「誰が悪い」ではなく、
成果物(設計書)・レビュー体制・開発フェーズの罠に焦点を当てます。

まず結論:Java + DynamoDB はアンチパターンではない

「Java で DynamoDB は相性が悪いのでは?」という不安は理解できますが、結論としてはこうです。

  • SDK(AWS SDK for Java)は成熟している
  • DynamoDB の性能・可用性・運用負荷の低さは実戦投入に十分
  • Java だから DynamoDB が遅い/使えない、という話ではない

一方で現場の"事故率"は上がりやすいです。私たちもそうでしたが、理由は技術ではなく、RDBに慣れた思考モデルとの違いにありました。

  • RDB:最後は JOIN や追加クエリで何とかできる、最悪全件取って絞れる
  • DynamoDB:Query 前提。Scan は「動くけど将来死ぬ」最短手段になりがち

この文化差が、設計書やレビューの粒度が足りないと そのまま実装に反映される のが今回の本質でした。

何が起きたか(タイムラインで整理)

1) 開発初期:データが少なく、Scan実装でも“問題なく(速く)見えた”

開発初期はユーザー数・データ量が少なく、Scan を使った実装でも体感速度は十分でした。
この段階では、設計意図と実装のズレが露見しません。

2) ST(System Test)でテストデータを充足した(=データ量を増やした)瞬間に、レイテンシが悪化

テストデータを拡充したり、ST段階でユーザー数を増やしたところ、

  • Scan の読み取り量が増える
  • RCU が跳ねる/レイテンシが伸びる
  • 「同じAPIなのに急に遅くなる」

が発生しました。

この時点になるまで、私は、Scanで実装されていることに気づいていませんでした。

3) “やっと”設計意図と実装の齟齬に気づく

ここで初めて、実装が「Query で取得する」ではなく、
Scan で取ってFilterで絞ってDBの情報を処理してレスポンスを返す処理流れになっている箇所が見つかりました。

DynamoDBでレイテンシーに影響する処理(APIのレスポンスに関わる処理)でScanを使うのは、多くのケースでアンチパターンになりやすいのは事実です。

が、そのパターンを使っていたということにこの段階でしか気づけなかった、レビュー体制やナレッジを共有できていなかった事実に「しまった」と感じました。

つまり、問題は「Scanを使ったのが悪」ではなく、

設計書にユースケースごとの"Query例"を記載しなかったため、
実装者が速度優先で"Scan"の利用を選択できてしまった
、設計と実装の齟齬が起きた

ことがSTで発覚してしまいました。

なぜそうなったか(原因を分解)

原因A:設計書が「テーブル構造」中心で、「Query例」がなかった

設計書に PK/SK やLSI/GSIなどの構造は書いていたとしても、実装者から見ると

  • このユースケースは Query で取るのか?
  • どのキーを渡して、どの範囲を読むのか?
  • 想定件数が最大いくつか?

が不明で、ユースケースごとのアクセスパターンがはっきり伝わらない設計書になっていました。

このため、実装者が最短で動かすならScanを使ってしまうのは仕方なかったかもしれません。
(悪意ではなく、開発スピードの合理性でそうなる)

原因B:RDB慣れの実装文化では「まず動く」が勝つ

RDBの世界では、開発初期に

  • まず全件取って絞る
  • 後からインデックスやSQLを調整する

が成立しやすいです。
DynamoDBではこの最適化ルートが通らず、後半でパフォーマンス問題が顕在化します。

原因C:レビューで「Scan禁止」「Query形の確認」が制度化されていなかった

設計思想は共有していても、レビュー観点として

  • その実装はQueryを使っているか?
  • Scanを使っていないか?
  • 想定件数と消費キャパシティはどうか?

がチェックできる体制になっていないと、ズレは入り込みます。

設計の段階で、ユースケースを考え、PK/SKとAttributeを考えていたのに、設計書に明確に書いていなかったことが敗因だったと感じています。

原因D:外部API仕様のFixが後半で、アクセスパターンが確定しにくかった

今回、外部APIの仕様が開発後半までFixしなかった点も重要です。
この状況では、RDBでも「正しいスキーマ設計」を初期に固めるのは難しいと感じました。

このため、「テーブル変更コストより柔軟性」を期待して、DynamoDBを選ぶ判断をしました。

DynamoDB の“柔軟性”は スキーマ柔軟ではあっても、
アクセスパターン柔軟ではありません。

この事実にその当時は気づかず、
「このPK/SK構成で情報が取得できるから、データが増えてもAttributeを追加すればいい」
「柔軟性があるから後で調整できる」という前提で、構造中心のDB設計書だけで進めてしまった点が、結果的に判断を誤らせました。

DynamoDBは「項目追加は楽」だが、
「後から Query しやすい形に変える」のは難しい(キー設計が固定)

このギャップが、後半の手戻りコストとして効いてきました。

失敗①:将来拡張を意識しすぎたPK設計(抽象化が強すぎた)

PK = USER#{userId}
SK = ENTITY#{entityType}#{entityId}

「将来エンティティが増える」を見越して抽象化した結果、

  • USER配下の件数が膨張
  • begins_with で絞り切れない
  • GSI追加 → 経路が増え、理解が難しくなる

につながりました。

ポイントは「未来を見るな」ではなく、未来の受け皿を“増築ルール”として分離することです。

  • 今必要な Query を成立させるキー設計
  • 将来増えるユースケースは、SKプレフィックス追加/専用テーブル追加で増築する

という方が、チームで運用しやすいです。

失敗②:ホットパーティション(偏り)を過小評価した

PK = REQUEST#{requestId}

一意キーでも、特定のキーへの読み書きが集中すればホットパーティションになります。
例:人気商品の requestId への集中、リトライによる同一キーへの連続アクセスなど。

これは DynamoDB というより「分散ストア一般」の落とし穴で、
偏りやすいキーを使うなら分散戦略(シャーディング等)を前提にするのが安全ですね。

※DynamoDB の Adaptive Capacity である程度は吸収されますが、設計段階で偏りを避けるのが基本です。

失敗③:prefix が広すぎる begins_with 設計

SK = ORDER#{orderId}
Query: begins_with("ORDER#")

begins_with は便利ですが、prefix が広いとレンジが広くなり、結果として読む量が増えます。
「ORDER が全体の大半」なら、Query で読み込むアイテム数が多くなり、RCU 消費が増えます。

改善の方向性は

  • prefix を用途別に細分化(SUMMARY / ITEM / EVENT など)
  • PK配下の最大件数を抑える(読みたい集合でPKを切る)

です。

「RDBにしておけば良かったのか?」をフラットに検討する

設計の意図を伝えられていなかったことを反省点にしつつ、「RDBにしておけば良かったのか?」について、フラットに評価軸を整理します。

RDBが有利だった可能性が高い条件

  • 仕様が固まるまで検索要件が流動的(後でクエリが増える)
  • 絞り込み・集計・複雑検索が増えそう
  • 実装チームがRDB中心で、DynamoDBのレビュー文化がない
  • 「まず動かして後で最適化」が必要(プロトタイピング重視)

DynamoDBが有利だった(今回の選定理由として妥当)な条件

  • 外部API中心で、ローカルデータは少ない
  • 高可用性・運用負荷の低さを重視
  • スキーマ変更(項目追加)が多そう
  • 「1発で取れる Query」が明確に定義できるユースケースが中心

今回のケースは後者寄りでしたので、DynamoDB選定自体は、そこまで悪くない判断だった、と再認識しました。
ただし、**DynamoDBを選んだ時点で必要になる“設計成果物”と“レビュー体制”**を
RDBと同じ粒度で進めてしまったことが、手戻りを増やす結果を招いてしまいました。

次回に活かすための「再発防止パッケージ」

1)設計書に「ユースケース表」と「Query例」を必ず書く

最低限これがあると、実装者が迷いません。

  • 入力(キー)
  • 期待する出力
  • 1回の Query で取る範囲
  • 想定件数(平均/最大)
  • 代替案(GSI/専用テーブル)

2) “Scanが入り込む”のをプロセスで潰す

  • 原則 Scan 禁止(例外は明文化)
  • PRレビュー観点に「Scan/Query」「消費キャパ」「想定件数」を追加
  • ST前に「データ増やしたテスト」を必ずやる(時間差で発覚するので早める)

3) 拡張は「抽象化」ではなく「増築ルール」で扱う

  • 新ユースケース → SKプレフィックス追加
  • 別検索軸 → GSI or 専用テーブル(判断基準を決める)
  • 集計や複雑検索 → 別ストア(DynamoDBで無理にやらない)

まとめ:選定ミスではなく、DynamoDBに必要な“設計の書き方・伝え方”が足りなかった

振り返って一番の学びはこれです。

  • Java + DynamoDB が悪いわけではない
  • DynamoDBは「スキーマが柔軟」だが「アクセスパターンは柔軟ではない」
  • 設計書に Query の形を書かないと、実装者は最短で動く(=Scan)に寄る
  • データ量が少ない間は問題が見えず、ST以降にズレが顕在化する

次は、設計思想を「口頭共有」ではなく **設計成果物(Query例・想定件数・禁止事項)**に落として、
チームの実装を同じ方向に揃えるところから始めたいです。


翌日の記事:DynamoDB暗号化は後から変えられない──KMSキャッシュ化とデータ移行の話

📚 1人アドベントカレンダー2025 全記事一覧


megusunu

🐦 X (@megusunu)
🧶 megusunuLab(ハンドメイド)
🏢 Wells合同会社

GitHubで編集を提案

Discussion