コンセプトから学ぶAmazon DynamoDB【インデックス俯瞰篇】

DynamoDB

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

よく訓練されたアップル信者、都元です。すこーし間があいてしまいましたが。今回はDynamoDBのインデックスについて俯瞰で見て行きたいと思います。

データベースにおける検索の手法

私も詳しくは無いのですが、データベース上の検索には大きく「フィルタによる検索」と「アクセスによる検索」というものがあるようです。

前者は「データをとりあえず読み込んだ後、それが条件にマッチするかどうかを判断し、マッチしていれば採用する」という検索方法です。全てのデータを順次読み込み(Scan)、マッチするものを探していくことも可能です。従って、この方式の実施にあたって特別な条件は必要なく、いかなる時でも実行できる手軽さが特徴です。ただし、データの読み出し(ディスクI/O)というのはパフォーマンスに大きなインパクトを与えます。

後者は「この条件のデータはこの部分に格納されているはずなので、そこを読みに行く(Query)。他のところに目的のデータは無いはずなので読み込まない。」という検索方法です。この方法であれば、全てのデータを読み出す必要はなくなるのでパフォーマンスは良くなります。が、データを事前に整理しておく必要があるため、利用できる場面に制限があります。このようなパフォーマンス向上のために「アクセスによる検索」を提供するための仕組みが「インデックス」です。

ちなみに、これらの検索方法は排他的なものではなく、まず「アクセスによる検索」で最初の絞り込みをした後、さらに「フィルタによる検索」で不要な枝葉を落とす、ということが行われることも多々あります。

キーとインデックス

データベースの話題ではよく、インデックスという言葉と共にキーという言葉が出てきます。これらは相互に深い繋がりがあるため、一部で混同されているのを見かけます。今一度ここで整理しておきましょう。

キーというのは、無数にあるアイテムの中から、特定のアイテムを唯一に特定できる検索条件(RDBではcolumnの組、DynamoDBではattributeの組)のことを言います。

1つのテーブルの中にキーは複数ある可能性があります。例えばログインユーザを格納するテーブルでは一般的にユーザ名がキーとなると思います。一方で、各ユーザのメールアドレスを一意(ユニーク)に管理したいという業務要件がある場合、メールアドレスもキーの1つとなり得ます。このように、複数のキーがある中で、最も代表的 *1なものを「主キー」、それ以外のものを「候補キー」と呼ぶことがあります。

一方インデックスというのは、無数にあるアイテムの中から、特定の検索条件での「アクセスによる検索」を提供し、検索を高速化するためのデータ構造のことを言います。

で、大抵の場合キーによる検索は多用されます。つまり、キーによるアイテム特定のための検索は無条件に高速化しておく、つまりインデックスを作成しておくと効率的です。例えばMySQLにおいては、主キーを作成すると、これに対するインデックスを自動的に作成します。これが、キーとインデックスの深い繋がりです。

この繋がりさえ度外視してしまえば、キーとインデックスはだいぶ別物です。各アイテムにはキーとなるattributeの値が必須ですが、インデックスattributeの値は無くても(つまりnullでも)構いません。キーの値にはアイテム間で重複してはダメですが、インデックスの値は重複を許容します。

ちなみに、敢えて値の重複を許さないインデックスも作成可能な場合があります。MySQLではUNIQUE INDEXと呼んだりします。先ほど主キーに対しては自動作成のインデックスがあると話しましたが、候補キーに対しては、このUNIQUE INDEXを設定しておくことにより、重複したメールアドレスの格納をデータベースレベルで弾くと共に、メールアドレスによるユーザの検索を高速化できますね。

さて、キーとインデックスの違い、整理できましたでしょうか。ここまでをまとめると、キーで検索すると、その結果は0〜1件のアイテムが返ってきます。一方、インデックスを使って検索すると、その結果は0〜複数件のアイテムが返ってくる可能性があります。

ただし、上記の整理(キーとインデックスは別物)はあくまでも本稿における考え方です。世の中には「キーというのはインデックスを特化したもの」、つまり key is-a index という前提で語っている文献もあるかもしれません。そして、DynamoDBの用語は、もしかしたらその立場なのかもしれません。ここで整理した「キーとインデックスは別物」という考え方は広く一般的に考え方の基礎として通用するとは限らないことに、注意が必要です。が、とにかく本稿では、頭の整理のために別物という立場を取ります。

DynamoDBにおけるキー

これは下記あたりで説明しましたね。

ざっとおさらいすると、DynamoDBのテーブルには「ハッシュキーテーブル」と「複合キーテーブル」の2種類があり、テーブル作成時にそれを決定します。

ハッシュキーテーブルでは、1つのattributeがキーとなる、つまりアイテム間で重複が許されないattributeが1つだけあります。所謂KVS (Key-Value store)という名前でイメージするアレです。

複合キーテーブルでは、2つのattributeがキーとなります。そのうち一方をhash-key、他方をrange-keyと呼びます。今思ったんですが、この名前の付け方があんまりよくない気が…。複合キーテーブルにおいては、2つのキーがあるわけではないです。hash attributeとrange attributeという2つのattributeをもって1つのキー(複合キー)を構成しているだけです。

さて、DynamoDBではテーブルのタイプ毎に、上記のようなattributeが主キーとなります。そして、候補キーについては、データベースシステム上での制約(要するに一意制約)を導入することは、残念ながらできません。

DynamoDBにおけるインデックス

さて。DynamoDBにおいても、主キーに対しては然るべきインデックスが作成されているはずです。まぁ、明示的に公式な解説は無かった気がしますが、主キーによって検索を行うことが主たる操作であるDynamoDBにおいて、主キーattributeに対するインデックスを構築しないなんてことは想像つかないのでw まぁ、暗黙的なインデックスがあると思って問題ないでしょう。

一方でDynamoDBには「明示的に定義するインデックス」が2種類あります。それぞれLSI(ローカル・セカンダリ・インデックス)とGSI(グローバル・セカンダリ・インデックス)と呼びます。

ここにも理解のハードルを感じます。「セカンダリと言うからにはプライマリがあるんでしょう? あ、それって要するにキーのことか。インデックスってのはキーのことやったんや!(←間違い) ってことは、要するにセカンダリ・キーだよね! 候補キーを使えるってことだよね!」というハマりルートがあります。いいですか、インデックスとキーは関連が深いですが、別物です。

DynamoDBの「セカンダリ・インデックス」に対する「プライマリ・インデックス」は、前述の「暗黙的なインデックス」のことを言っています。

そして、DynamoDBは「セカンダリ・インデックス」を扱う機能を提供しているだけで、「セカンダリ・キー(=候補キー)」を扱う機能は提供していません。繰り返しになりますが、DynamoDBでは主キー以外に一意制約を導入できません。(将来的にできるようになるかもしれませんが、本稿執筆時点では少なくとも無理です。)

「主キー以外でアイテムを検索したい」問題

さて、ここで話の流れを新たに。これは「NoSQL導入あるある」だと思います。RDBの世界では、後付けであってもインデックスを作成してしまえば(場合によってはインデックスを作成しなくても!)、どのような条件でもアイテムを探すことができました。しかし、NoSQLには(製品ごとに特徴は異なりますが)検索条件に一定の制限があります。

DynamoDBにおいては、セカンダリ・インデックスを定義構築しておくことにより、主キー以外での検索が可能です。定義するインデックスのタイプは、「hash index」と「hash + range index」の何れかです。キーの構成と同じですね。

これも混乱の原因なんですが、これらのインデックス構成要素も、ドキュメント上では「hash key」「range key」という形で「key」という単語が利用されています。でもこれはインデックスを構成するattributeのことであってキーではない! という立場で、改めて心に刻んでください。

さて。ハッシュキーテーブルには、hash keyとして指定したattributeが1つあります。このテーブルに設定できるセカンダリ・インデックスは、GSIと呼ばれるもののみで、「hash index」タイプと「hash+range index」タイプどちらでも設定可能です。

ハッシュキーテーブル LSI GSI
hash index ×
hash+range index ×

複合キーテーブルには、hash-keyとして指定したattributeと、range-keyとして指定したattributeが各1つあります。このテーブルには、LSIとGSIどちらでも設定可能です。ただし、LSIは「hash+range index」タイプのみとなります。

複合キーテーブル LSI GSI
hash index ×
hash+range index

この辺りはまた複雑な話になるので、次の機会に掘り下げていくことにします。

何度も何度も何度も何度も繰り返しになりますが、DynamoDBにおいては、1つのテーブルに対して作成できる「キー」は1つだけです。上の議論は「インデックス」の話で、DynamoDBでは1テーブルについて複数の「インデックス」を作れます。

そしてインデックスは一意性の保証をしません。つまり、セカンダリインデックスを作ったとしても、そのインデックスを使った検索の結果数は、0〜1件ではありません。0〜複数件です。だって、キーによる検索ではないのですから。

まとめ

DynamoDBは「主キー以外でアイテムを検索したい(=結果は0〜複数件で良い)」という望みを叶えてくれます。が、「主キー以外でアイテムを一意に特定したい(=結果は0〜1件)」という望みは叶いません。

大事なことだからもう一度言います。

  • キーとインデックスは別物です。混同しないように。
    • キーは検索結果の一意性の提供
    • インデックスは「アクセスによる検索」の提供
  • DynamoDBが提供するのは「主キー」「主キーに対する暗黙的プライマリ・インデックス」「セカンダリ・インデックス」であり、「セカンダリ・キー(候補キー)」は扱えない。

ちょっと話がとっちらかってしまった感がありますが、また次の機会に整理していこうと思います。

脚注

  1. 「代表的」というのは設計者の主観的なんですが…。