以下の内容はhttps://www.m3tech.blog/entry/2025/08/21/103817より取得しました。


仕事を通して、初めてDynamoDBと向き合いました

【Unit4 ブログリレー11日目】

こんにちは。昨年末に入社した、Unit4(m3.com開発チーム)エンジニアの升元です。このブログは、Unit4ブログリレー11日目の記事です。

この記事では、DynamoDBの実践の中で初めて知った仕様を紹介していきます。これからDynamoDBを検討する方の参考になればと思います。

はじめに

AWSのNoSQLサービスで最も有名なものと言えば、DynamoDBではないでしょうか。私も、資格試験の勉強などを通じてDynamoDBについては何度も見聞きしたことがあり、

  • AWSのフルマネージドサービスで〜
  • キーバリュー型のデータベースで〜
  • 非常に高速で〜

といった程度の認識は持っていました。

しかし先日、初めて仕事でDynamoDBと本格的に向き合うことになり、初めて学んだことも多くありました。ここからは実例をもとに、そのうち3つを紹介します。

対象のサービスと、そこにDynamoDBが使われている理由

まずは、私がDynamoDBと向き合うことになったサービスを簡単に紹介します。

私の所属するUnit4には大小30以上のサービスがあり、その中のひとつに、ユーザーへのお知らせを管理・表示するためのAPIだけを集めた小さなサービスがあります。このサービスでは、お知らせの保持にDynamoDBが使われています。ここで言う「お知らせ」とは、たとえば「年末年始は12月m日から1月n日までお休みします」といったものをイメージしてください。

そこに時折、「このお知らせは医師だけに伝えられれば」「薬剤師だけに伝えられれば」といった要望が生まれることがあります。それを叶えること、すなわち、ユーザーの属性に応じてお知らせを出し分ける機能を追加すること、が私に課せられたタスクでした。

ちなみに、この「お知らせサービス」にDynamoDBが使われている理由ですが、コスト面の優位性が大きいです。DynamoDBのオンデマンドモードでは、発生した読み込み/書き込み処理と保存しているデータ量にだけ課金されます。ベースラインのコンピューティンリソースや、スケールダウンしている途中のリソースといったものを考慮する必要がありません。「お知らせサービス」は、テーブル数が非常に少なく、クエリはシンプル、さらにユーザーのアクセス数によるリクエスト数の変動が大きいという事情もあり、DynamoDBが最適なデータベースでした。

対象を管理するテーブルを追加しようとした

さて、「お知らせサービス」にユーザー属性による出し分け機能を追加するにあたり、具体的な要望は、

  • とりあえず属性1つを指定できれば良い(たとえば、あるお知らせは医師だけに見せる)

が、さらに、

  • 複数のユーザーを指定できるとなお良い(たとえば、あるお知らせはユーザー1, 2, 3に見せる)

とのことでした。そこで、より拡張性の高い、複数のユーザーを紐づけられる方法を考えることにしました。

まずは、RDBでよく採用されるように、お知らせのIDと対象をペアで登録するテーブルを作ってみましょう。

お知らせID(パーティションキー) 対象(ソートキー)
1 JOB#A
1 JOB#B
2 USER#1
2 USER#2
2 USER#3

これで、職業がAまたはBの人を対象とするお知らせ1と、ユーザー1, 2, 3を対象とするお知らせ2を表現できました。さらに、対象をパーティションキーとする、グローバルセカンダリーインデックス(GSI)も作成しておきます。このように登録しておけば、たとえばユーザーIDが1で職業がAであるユーザーにお知らせを出す際、対象がUSER#1とJOB#Aで検索することで、高速に取得できます。

知見1: 1トランザクションの上限は100アクション

しかし、この設計には問題がありました。DynamoDBにはトランザクション機能があるものの、1トランザクションの上限は100アクションという仕様があります。管理画面から対象を変更する操作を想像すると、既存の対象の削除と新しい対象の登録を1トランザクションで実行する必要があります。そのため、実際に紐づけられる対象数は半分の50以下ということになります。ユーザーを指定する場合は千件や万件単位のユースケースも想定されていたので、これでは要望を満たせませんでした。

TransactWriteItems は、最大 100 の書き込みアクションを 1 つのオールオアナッシングオペレーションにグループ化する、同期的でべき等な書き込みオペレーションです。

docs.aws.amazon.com

知見2: パーティションキーだけを指定して削除できない

トランザクションのアクション数制限を調べていて、ふと思いました。パーティションキーでお知らせIDを指定して、対象との紐づけを一括で削除できれば、それは1アクションで済むのではなかろうか、と。リレーショナルデータベース(RDB)では、DELETE文を書く際のWHERE句に一致するレコードが複数あれば、複数削除できますよね。キーバリュー型のデータベースであるDynamoDBにそこまでの柔軟性は期待できなくても、パーティションキーと言うからには、何らか論理的に集約されていて、一括操作ができるのでは…と思ったわけです。

残念ながら、それはできません。DynamoDBでレコードを削除する際は、プライマリーキーを完全に指定する必要があります。プライマリーキーがパーティションキーとソートキーから構成されていれば、パーティションキーだけでなく、ソートキーもレコードごとに正しく指定して削除リクエストに渡す必要があります。トランザクションの中では、1レコードの削除には必ず1アクションが必要ということになります。

For the primary key, you must provide all of the key attributes.

docs.aws.amazon.com

ちなみに、BatchWriteItemを使うことで最大25個のレコードをまとめて削除できますが、この場合もあくまでリクエストが1回で済むというだけで、やはり個々のレコードに対応する完全なプライマリーキーを指定する必要があります。また、トランザクションに対応していません。

A map of primary key attribute values that uniquely identify the item. Each entry in this map consists of an attribute name and an attribute value. For each primary key, you must provide all of the key attributes.

docs.aws.amazon.com

1行ですべての対象情報を持とうとした

対象ごとに別々のレコードを作るのがNGなら、1行で対象をすべて持ってしまうのはどうでしょうか。これが可能なら、既存のお知らせテーブルの拡張で済みそうです。

お知らせID(パーティションキー) 対象(セット型)
1 JOB#A, JOB#B
2 USER#1, USER#2, USER#3

対象を1行にまとめて、先ほどと同じデータを表現してみました。DynamoDBには、このような構造を実現できる、セット型というデータ型が存在します。ちなみに、さらに複雑なJSONのような構造を表現する、ドキュメント型というのも存在します。

知見3: セット型で複数の値を持てる。ただ、そこにインデックスを張れない

キーバリュー型のデータベースということで、難しいかもしれないという予感はありましたが、調べてみるとやはり、DynamoDBではセット型にインデックスを張れないことが分かりました。DynamoDBの検索性能を引き出すには、インデックスが不可欠ですので、この案も採用できないことになりました。

テーブルまたはセカンダリインデックスを作成するときは、各プライマリキー属性 (パーティションキーとソートキー) の名前とデータ型を指定する必要があります。さらに、各プライマリキー属性は、文字列、数値、またはバイナリとして定義する必要があります。

docs.aws.amazon.com

ミニマムでの実現と、他データベースの検討

ここまで、DynamoDBでお知らせに複数の対象を紐づけようという試みを紹介しました。実は、最後に紹介した「セット型にインデックスを張る」という案は、他のデータベースでは可能な場合があります。

たとえば、ドキュメント型データベースであるMongoDBは、配列フィールドにインデックスを張ることができます。

 配列値を含むフィールドにインデックスを作成すると、MongoDB はそのインデックスをマルチキー インデックスとして保存します。

www.mongodb.com

ほかの例として、RDBのPostgreSQLでは、GINインデックスを使うことで、配列の各要素にインデックスを張ることができます。

GINは「転置インデックス」であり、配列などのように複数の要素を持つデータ値に適しています。 転置インデックスは各要素値に対して別々のエントリを持っており、特定の要素値の存在について検査する問い合わせを効率的に処理できます。

www.postgresql.jp

ありがたいことに、どちらのデータベースもAWSで利用可能です(MongoDBはDocumentDB、PostgreSQLはRDSまたは互換Aurora)。ただ、DynamoDBほどのコストパフォーマンスは期待できません。もともとDynamoDBを含めたサーバーレス構成により低コストで実現していたサービスなので、データベースの追加または移行により追加のコストが発生するならば、それに見合う価値を生めるのかを議論する必要があります(このように、エンジニアであってもROIへの意識を求められるのがエムスリーの文化です)。

ということで、このタスクでは、既存のテーブルに1つの対象のみを表す列を追加することで、「とりあえず属性1つを指定できれば良い(たとえば、あるお知らせは医師だけに見せる)」という最低限の要望までを実現しました。これならば、1件のお知らせの対象変更は1レコードの更新で済みますし、対象にインデックスを張ることもできます。

お知らせID(パーティションキー) 対象(文字列型)
1 JOB#A
2 JOB#B

そして、それとは別にDocumentDB等を導入した場合のコスト増を見積もってビジネスサイドに伝え、それに見合うお知らせの活用を引き続き検討してもらうこととし、リリースとなりました。

おわりに

机上で勉強したあとで実際に手を動かしてみると意外なところで引っ掛かることは、あるあるだと思います。今回もまさにそうでした。Unit4はサービス数が多い上に、その技術スタックも多種多様なので、いろいろな経験を得られるのが良いところです。詳しくはUnit4ブログリレー1日目の記事で紹介しています。

www.m3tech.blog

We are Hiring!

せっかく勉強したあの技術を仕事で触りたいというエンジニアの方、エムスリーなら叶うかもしれません。ご興味を持たれた方はぜひご応募ください。

エンジニア採用ページはこちら

jobs.m3.com

エンジニア新卒採用サイト

fresh.m3recruit.com

カジュアル面談はこちら

jobs.m3.com




以上の内容はhttps://www.m3tech.blog/entry/2025/08/21/103817より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14