hey Product Blog

こだわりを持ったお商売を支えるプラットフォーム「STORES」の開発チームによる技術ブログです。

MongoDBでnull以外を条件にデータを取得する時にCovered Queryにする方法

この記事はheyアドベントカレンダー2020の5日目の記事です。

STORESでECサービスのSREをしている@wanijiです。

STORESではメインとなるDBにMongoDBを採用しており、RDBと同様にインデックスを使ったパフォーマンス改善をします。その際に、インデックスを使用したクエリの中でも効率の良いCovered Queryにすることを検討するのですが、 null が条件に入ってはならないという制約があるため、使用出来ない事がよくあります。

今回は null 以外のデータを取得したい時に、どうやってCovered Queryにするかをご紹介します。

Covered Queryとは

Covered Queryは、クエリ条件と返却されるフィールドがすべて同一のインデックスに含まれているため、ドキュメントの取得がなく非常に高速なクエリです。Covered Queryではない場合、インデックスに含まれていないフィールドが必要なため、ドキュメントを取得する必要があります。

クエリをCovered Queryにする条件は以下のとおりです。

  • クエリで使用するフィールドと返却するフィールドが同一のインデックスに含まれている
  • null がクエリ条件に含まれない

しかし現実には、null 以外のデータを取得するために foo: { "$ne": nil } のような条件を使いたいケースが多々あります。これを null を条件に入れないで実現する方法を説明します。

どのように実現するか

フィールドに含まれるデータ型や値によって変わるため、それぞれ説明します。

フィールドに数値とnullが含まれる場合

数値の場合は $gt$lt を使います。例えば fooフィールドには0以上のデータと nullしか入らない場合、 foo: { "$gte": 0 } とすることで null を排除しつつCovered Queryに出来ます。実装当時は0以上しか入らなかったが、仕様変更で0未満も入るようになったということがあり得るため、その点は注意が必要です。

フィールドに文字列とnullが含まれる場合

文字列の場合は { "$gte": " " } とすることでCovered Queryに出来ます。MongoDBで文字列に対し $gt を使うと、先頭から一文字ずつ比較します。MongoDBは文字列をUTF-8で扱っており、その中で半角スペースは制御文字列を除くと最小の値(0x20)であるため、すべての文字列を取得することが出来ます。また、MongoDB 2.6以降から $gt$lt を使うと null が取得されないようになっているため、 null が排除されます。

まとめ

Covered Queryにしたことで効率的なクエリになりました。Sparse IndexPartial Index を併用することで、インデックス自体のサイズを削減することも出来るので、そちらもおすすめです。

明日は@daitasuによる "フルリモート環境で行う大喜利式チームランチ" です。