要件に応じて柔軟性、拡張性、パフォーマンス、およびスピードの優先付けが求められるソフトウェア開発において、データベースの選択は一筋縄ではいきません。高い柔軟性と拡張性を備え、顧客分析用にデータを集約するデータベースをお探しなら、MongoDBが答えかもしれません。

この記事では、MongoDBデータベースの構造とデータベースの作成、監視、管理方法についてご説明します。

MongoDBデータベースの構造

MongoDBはスキーマレスのNoSQLデータベースです。つまり、SQLデータベースのようにテーブル/データベースの構造を指定することはありません。

NoSQLデータベースは、実はリレーショナルデータベースよりも高速であることをご存知でしょうか?これは、インデックス、シャーディング、アグリゲーションパイプライン(Aggregation Pipeline=集計)といった特性によるものです。また、MongoDBはクエリの実行が高速であることでも知られています。このような理由から、Google、トヨタ自動車、Forbesなどの企業で採用されています。

以下では、MongoDBの主な特徴をいくつかご紹介します。

ドキュメント

MongoDBは、データをJSONドキュメントとして保存するドキュメントデータモデルを採用しています。ドキュメントはアプリケーションコード内のオブジェクトに自然にマッピングされるため、操作が容易です。

リレーショナルデータベースのテーブルにフィールドを追加するには、カラムを追加しなければなりません。これに対して、JSONドキュメントのフィールドは、ドキュメントごとに異なる設計が可能で、データベース内のすべてのレコードにフィールドを追加する必要はありません。

ドキュメントには配列のような構造を格納し、ネストして階層的な関係を表現することができます。さらに、MongoDBのドキュメントは、バイナリJSON(BSON)型に変換されます。これによりアクセスが高速化され、文字列、整数、ブール値など、様々なデータ型をサポートできます。

レプリカセット

MongoDBで新しいデータベースを作成すると、システムは自動的に少なくとも2つのデータのコピーを作成します。このコピーは「レプリカセット」と呼ばれ、レプリカセット間で継続的にデータを複製し、データの可用性を高める仕組みです。また、システム障害や計画的なメンテナンスの際にダウンが発生しないように保護することも可能です。

コレクション

コレクションは、1つのデータベースに関連する文書のグループです。リレーショナルデータベースのテーブルに匹敵する概念です。

しかし、コレクションはより柔軟です。スキーマに依存しない設計(スキーマレス)であり、ドキュメントが同じデータ型である必要もありません。

データベースに属するコレクションのリストを表示するには、listCollectionsコマンドを使用することができます。

アグリゲーションパイプライン

このフレームワークを使用すると、複数の演算子や式をグループ化することができます。柔軟性に長け、どのような構造のデータでも処理、変換、分析が可能です。

MongoDBでは150の演算子や式にわたって高速なデータフローや機能を実現できます。また、複数のコレクションからの結果を柔軟にまとめるユニオン(union)ステージなど、いくつかのステージがあるのも特徴です。

インデックス

MongoDBドキュメントの任意のフィールドにインデックスを付けて、効率を上げ、クエリの速度を向上させることができます。インデックスをスキャンして検査するドキュメントを限定することで、時間を節約可能です。コレクション内のすべてのドキュメントを読むより遥かに効率的であることは、言うまでもありません。

複数のフィールドに対する複合インデックスなど、さまざまなインデックス戦略を使用することができます。例えば、従業員の姓と名を別々のフィールドで格納する文書が複数あるとします。姓と名の両方を返したい場合は、「姓」と「名」の両方を含むインデックスを作成できます。これは、「姓」と「名」の2つのインデックスを利用するよりもはるかに効果的です。

Performance Advisorのようなツールを活用すれば、どのクエリにインデックスが有用であるかをさらに理解することができます。

シャーディング

シャーディングは、1つのデータセットを複数のデータベースに分散させるものです。データセットを複数のマシンに保存することで、システムの総ストレージ容量を増やすことができます。大きなデータセットを小さな塊に分割して、さまざまなデータノードに保存することになります。

MongoDBは、データをコレクションレベルでシャード化し、コレクション内のドキュメントをクラスタ内のシャードに分散させます。これにより、最大規模のアプリケーションを処理できるアーキテクチャとなり、スケーラビリティが確保されます。

MongoDBデータベースを作成する方法

まず、お使いのOSに適したMongoDBパッケージをインストールする必要があります。MongoDB Community Serverのダウンロードページにアクセスします。その中から、最新の「Version」、「Platform」(OS)、「Package」形式(zipファイル)を選択し、下図のように「Download」をクリックします。

MongoDB Community Serverダウンロードの流れ
MongoDB Community Serverダウンロードの流れ(画像出典:MongoDB Community Server

手順は非常にシンプルで、すぐにインストールできます。

インストールが完了したら、コマンドプロンプトを開き、mongod -versionと入力して確認してください。もし、以下のような出力が得られず、代わりにエラーの文字列が表示された場合は、再インストールが必要になる可能性があります。

MongoDBのバージョンを確認する(画像参照元
MongoDBのバージョンを確認する(画像参照元:configserverfirewall

MongoDB Shellの使用

まずは以下の要件を確認してください。

  • クライアントにTransport Layer Securityがあり、IP許可リストに登録されていること
  • 目的のMongoDBクラスタのユーザーアカウントとパスワードを持っていること
  • デバイスにMongoDBがインストールされていること

ステップ1. MongoDB Shellにアクセスする

各OSの説明に従ってMongoDBサーバーを起動します。Windowsの場合、以下のコマンドを入力します。その他のOSの場合は、MongoDBのドキュメントを参照してください。

net start MongoDB

すると、以下のように出力されるはずです。

MongoDBサーバーの実行
MongoDBサーバーの実行(画像参照元:c-sharpcorner

先ほどのコマンドでMongoDBサーバーが初期化されました。これを実行するには、コマンドプロンプトでmongoと入力します。

MongoDB Shellを実行する
MongoDB Shellを実行する(画像参照元:bmc

このMongoDB Shellで、データベースの作成、データの挿入、データの編集、管理コマンドの発行、データの削除などを実行することができます。

ステップ2. データベースの作成

一般的なリレーショナルデータベースとは異なり、MongoDBにはデータベース作成コマンドはありません。その代わりに、useというキーワードがあり、指定したデータベースに切り替わります。データベースが存在しない場合は、新しいデータベースを作成し、そうでない場合は、既存のデータベースにリンクする仕様です。

例えば、「company」という名前のデータベースを作成するには、次のように入力します。

use Company
MongoDBでデータベースを作成する
MongoDBでデータベースを作成する

dbと入力して、今作成したデータベースをシステムで確認することができます。作成した新しいデータベースがポップアップ表示されれば、接続に成功したことになります。

既存のデータベースを確認したい場合は、show dbsと入力すると、システム内にあるすべてのデータベースが返されます。

MongoDBのデータベースを表示する
MongoDBのデータベースを表示する

デフォルトでは、MongoDBをインストールすると、admin、config、localの各データベースが作成されます。

なお、この時点では、先ほど作成したデータベースは表示されません。まだデータベースに値を保存していないためです。これについては、次の「MongoDBデータベースの管理」セクションで詳しく見ていきます。

Atlas UIの利用

データベースサービス「MongoDB Atlas」を使用する手もあります。Atlasの一部の機能を利用するには有料になりますが、ほとんどのデータベース機能は無料で利用できます。無料版の機能でもMongoDBデータベースを作成するには十分です。

前提条件として、以下のことを確認してください。

  1. IPが許可リストに登録されていること
  2. 使用するMongoDBクラスタにユーザーアカウントとパスワードがあること

Atlas UIでMongoDBデータベースを作成するには、ブラウザを開き、https://cloud.mongodb.comにログインします。クラスタページから、「Browse Collections(コレクションの表示)」をクリックします。クラスタにデータベースがない場合は、「Add My Own Data(データの追加)」ボタンをクリックしてデータベースを作成します。

プロンプトが表示されるので、データベース名とコレクション名を入力します。名前を付けたら、Create」(作成)をクリックし、完了です。これで、新しいドキュメントを入力したり、ドライバーを使ってデータベースに接続したりすることができます。

MongoDBデータベースの管理

このセクションでは、MongoDB データベースを効率的に管理するための便利な方法をいくつかご紹介します。MongoDB Compass、または、コレクションが利用できます。

コレクションを使う

リレーショナルデータベースには、データ型やカラムを指定した定義済みのテーブルがありますが、NoSQLではテーブルの代わりにコレクションがあります。このコレクションには構造がなく、ドキュメントもさまざまなものがあります。同じコレクション内で他のドキュメントの形式と一致しなくても、さまざまなデータ型やフィールドを持つことができます。

例として「Employee(社員)」というコレクションを作成し、そこにドキュメントを追加してみましょう。

db.Employee.insert(
  {
   	"Employeename" : "Chris",
   	"EmployeeDepartment" : "Sales"
  }
)

挿入が成功すると、WriteResult({ "nInserted" : 1 })と返されます。

MongoDBでのデータの挿入に成功
MongoDBでのデータの挿入に成功

「db」は現在接続しているデータベースを指します。「Employee」は、会社のデータベースで新しく作成されたコレクションです。

ここでは主キーを設定していませんが、これはMongoDBが自動的に「_id」という主キーフィールドを作成し、デフォルト値を設定しているためです。

以下のコマンドを実行すると、JSON形式でコレクションを出力できます。

db.Employee.find().forEach(printjson)

出力は以下のようになります。

{
  "_id" : ObjectId("63151427a4dd187757d135b8"),
  "Employeename" : "Chris",
  "EmployeeDepartment" : "Sales"
}

「_id」の値は自動的に割り当てられますが、変更可能です。「Employee」データベースに「_id」値を「1」とする別のドキュメントを挿入します。

db.Employee.insert(
  {  
   	"_id" : 1,
   	"EmployeeName" : "Ava",
   	"EmployeeDepartment" : "Public Relations"
  }
)

コマンドdb.Employee.find().forEach(printjson)を実行すると、次のような出力が得られます。

主キーを持つコレクション内のドキュメント
主キーを持つコレクション内のドキュメント

上記の出力では、「Ava」の「_id」の値は自動的に割り当てられるのではなく「1」に設定されています。

データベースに値を追加することに成功したので、次のコマンドを使用して、システム内の既存のデータベースで表示されるかどうか確認しましょう。

show dbs
データベースの一覧を表示する
データベースの一覧を表示する

これで完成です。システム内にデータベースを作成することができました。

MongoDB Compassを使う

MongoDBサーバーをMongo Shellから操作することはできますが、これは時に面倒になることがあります。特に本番環境などでは、この方法が不便に思えるかもしれません。

そこで便利なのが、MongoDB社開発のGUIツール「Compass」です。これを使うと作業が非常に楽になります。データの可視化、パフォーマンスプロファイリング、データ、データベース、コレクションへのCRUD(作成、読み取り、更新、削除)などの機能が利用できます。

Compass IDEは、お使いのOSに合わせてダウンロードし、簡単な手順でインストールできます。

インストールできたら、アプリケーションを開き、接続用の文字列を貼り付け、サーバーとの接続を確立します。接続文字列が不明な場合は、「Fill in connection fields individually(個別に接続フィールドに入力する)」をクリックします。MongoDBのインストール時にポート番号を変更していなければ、「Connect」ボタンをクリックして完了です。変更している場合は、設定した値を入力して「Connect」をクリックします。

MongoDBの新規接続ウィンドウ(画像参照元
MongoDBの新規接続ウィンドウ(画像参照元:Mongodb

次に、「New Connection」ウィンドウに「Hostname」、「Port」、「Authentication」を指定します。

MongoDB Compassでは、データベースの作成とその最初のコレクションの追加を同時に行うことができます。その方法は以下の通りです。

  1. Create Database」をクリックして、プロンプトを開く
  2. データベースの名前とその最初のコレクションを入力する
  3. Create Database」をクリックする

データベース、コレクションの名前をクリックして「Documents」タブを表示すると、データベースにさらにドキュメントを挿入することができます。次に、「Add data」ボタンをクリックして、1つまたは複数のドキュメントをコレクションに挿入可能です。

ドキュメントを追加する際、1つずつ入力することもできますし、複数のドキュメントを配列で入力することもできます。複数のドキュメントを追加するに合は、コンマで区切られたドキュメントを角括弧で囲んでください。例えば、次のようになります。

{ _id: 1, item: { name: "apple", code: "123" }, qty: 15, tags: [ "A", "B", "C" ] },
{ _id: 2, item: { name: "banana", code: "123" }, qty: 20, tags: [ "B" ] },
{ _id: 3, item: { name: "spinach", code: "456" }, qty: 25, tags: [ "A", "B" ] },
{ _id: 4, item: { name: "lentils", code: "456" }, qty: 30, tags: [ "B", "A" ] },
{ _id: 5, item: { name: "pears", code: "000" }, qty: 20, tags: [ [ "A", "B" ], "C" ] },
{ _id: 6, item: { name: "strawberry", code: "123" }, tags: [ "B" ] }

最後に、「Insert」をクリックして、ドキュメントをコレクションに追加します。ドキュメントの本文は、次のようになります。

{
  "StudentID" : 1
  "StudentName" : "JohnDoe"
}

フィールド名は「StudentID」と「StudentName」です。フィールドの値はそれぞれ「1」と「JohnDoe」となっています。

便利なコマンド

コレクションは、権限管理コマンドとユーザー管理コマンドで操作することができます。

ユーザー管理コマンド

MongoDBにはユーザー管理に関するコマンドがあります。これを使って、ユーザーの作成、更新、削除を行うことができます。

dropUser

このコマンドは、指定したデータベースから1人のユーザーを削除します。以下がその中身です。

db.dropUser(username, writeConcern)

usernameは必須フィールドで、データベースから削除するユーザーの名前を指定します。オプションのフィールドwriteConcernには、削除操作に対する書き込み保証のレベルが付帯します。書き込み保証レベルをこのフィールドwriteConcernで決定することができます。

userAdminAnyDatabaseの役割を持つユーザーを削除する前に、ユーザー管理権限を持つ他のユーザーが少なくとも1人いることを確認してください。

この例では、testデータベースのユーザー「user26」を削除することにします。

use test
db.dropUser("user26", {w: "majority", wtimeout: 4000})

出力は以下の通りです。

> db.dropUser("user26", {w: "majority", wtimeout: 4000});
true
createUser

このコマンドは、指定されたデータベースに対して、次のように新しいユーザーを作成します。

db.createUser(user, writeConcern)

userは必須フィールドです。作成するユーザーの認証情報およびアクセス情報を記述したドキュメントを指定してください。オプションのフィールドwriteConcernで、作成操作に対する書き込み保証のレベルを扱います。書き込み保証のレベルは、writeConcernによって決定することができます。

createUserを実行し、そのユーザーがすでにデータベース上に存在する場合には、重複ユーザーエラーが返されます。

テストデータベースで新しいユーザーを作成するには、次のようにします。

use test
db.createUser(
  {
    user: "user26",
    pwd: "myuser123",
    roles: [ "readWrite" ]  
  }
);

出力は次のようになります。

Successfully added user: { "user" : "user26", "roles" : [ "readWrite", "dbAdmin" ] }
grantRolesToUser

このコマンドを活用すると、ユーザーに追加の役割を付与することができます。次のように使用します。

db.runCommand(
  {
    grantRolesToUser: "<user>",
    roles: [ <roles> ],
    writeConcern: { <write concern> },
    comment: <any> 
  }
)

上記の役割には、定義したものと組み込みのものの両方を指定することができます。grantRolesToUserが実行されている同じデータベースに存在する役割を指定するには、以下のようにドキュメントで役割を指定することも可能です。

{ role: "<role>", db: "<database>" }

または、単に役割名で役割を指定することもできます。例えば、以下のような具合です。

"readWrite"

別のデータベースに存在する役割を指定するには、別のドキュメントで役割を指定する必要があります。

データベース上で役割を付与するには、指定したデータベース上でgrantRole操作が必要になります。

わかりやすいように例を挙げてみます。例えば、商品データベース内のユーザー「productUser00」が、以下のような役割を持つ場合を考えてみましょう。

"roles" : [
  {
    "role" : "assetsWriter",
    "db" : "assets"
  }
]

grantRolesToUserの操作により、「productUser00」 は、在庫データベースのreadWriteの役割と、商品データベースの「read」の役割を与えられています。

use products
db.runCommand({
  grantRolesToUser: "productUser00",
  roles: [
    { role: "readWrite", db: "stock"},
    "read"
  ],
  writeConcern: { w: "majority" , wtimeout: 2000 }
})

商品データベースのユーザー「productUser00」は、以下の役割を持つようになりました。

"roles" : [
  {
    "role" : "assetsWriter",
    "db" : "assets"
  },
  {
    "role" : "readWrite",
    "db" : "stock"
  },
  {
    "role" : "read",
    "db" : "products"
  }
]
usersInfo

usersInfoコマンドを使用すると、1つまたは複数のユーザーに関する情報を返すことができます。以下はその構文です。

db.runCommand(
  {
    usersInfo: <various>,
    showCredentials: <Boolean>,
    showCustomData: <Boolean>,
    showPrivileges: <Boolean>,
    showAuthenticationRestrictions: <Boolean>,
    filter: <document>,
    comment: <any> 
  }
)
{ usersInfo: <various> }

アクセスに関しては、ユーザーは常に自分自身の情報を見ることができます。他のユーザーの情報を見るには、コマンドを実行するユーザーが、他のユーザーのデータベースに対するviewUserアクションを含む権限を所有する必要があります。

userInfoコマンドを実行すると、指定されたオプションに応じて、以下の情報を取得することができます。

{
  "users" : [
    {
      "_id" : "<db>.<username>",
      "userId" : <UUID>, // Starting in MongoDB 4.0.9
      "user" : "<username>",
      "db" : "<db>",
      "mechanisms" : [ ... ],  // Starting in MongoDB 4.0
      "customData" : <document>,
      "roles" : [ ... ],
      "credentials": { ... }, // only if showCredentials: true
      "inheritedRoles" : [ ... ],  // only if showPrivileges: true or showAuthenticationRestrictions: true
      "inheritedPrivileges" : [ ... ], // only if showPrivileges: true or showAuthenticationRestrictions: true
      "inheritedAuthenticationRestrictions" : [ ] // only if showPrivileges: true or showAuthenticationRestrictions: true
      "authenticationRestrictions" : [ ... ] // only if showAuthenticationRestrictions: true
    },
  ],
  "ok" : 1
} 

usersInfoコマンドで何ができるのか、大まかなことはお分かりいただけたと思います。次に浮かんでくる疑問として、特定のユーザーや複数のユーザーを調べるには、どんなコマンドが便利なのでしょうか。

これについて、同じことを説明するために2つの便利な例を紹介します。
「office」データベースで定義されたユーザー「Anthony」に対して、認証情報ではなく、特定のユーザーの権限と情報を見るには、次のコマンドを実行します。

db.runCommand(
  {
    usersInfo:  { user: "Anthony", db: "office" },
    showPrivileges: true
  }
)

現在のデータベース内のユーザーを調べるには、そのユーザーの名前を指定します。たとえば、「home」データベースに「Timothy」という名前のユーザーが存在する場合、以下のコマンドを実行します。

db.getSiblingDB("home").runCommand(
  {
    usersInfo:  "Timothy",
    showPrivileges: true
  }
)

次に、さまざまなユーザーの情報を見るには、配列を使用することができます。オプションのフィールドとしてshowCredentialsshowPrivilegesを含めることもできますし、省略することもできます。実際にはこのようなコマンドになります。

db.runCommand({
usersInfo: [ { user: "Anthony", db: "office" }, { user: "Timothy", db: "home" } ],
  showPrivileges: true
})
revokeRolesFromUser

revokeRolesFromUserコマンドを使用して、役割が存在するデータベース上のユーザーから1つまたは複数の役割を削除することができます。revokeRolesFromUserコマンドの構文は次のとおりです。

db.runCommand(
  {
    revokeRolesFromUser: "<user>",
    roles: [
      { role: "<role>", db: "<database>" } | "<role>",
    ],
    writeConcern: { <write concern> },
    comment: <any> 
  }
)

上記の構文では、rolesフィールドにユーザーの定義した役割と内蔵の役割の両方を指定できます。grantRolesToUserコマンドと同様に、取り消したい役割をドキュメントで指定するか、その名前を使用することができます。

revokeRolesFromUserコマンドを正常に実行するには、指定したデータベースでrevokeRoleアクションを実行する必要があります。

ポイントを押さえるために例を挙げます。商品データベースのproductUser00エンティティには、以下の役割があります。

"roles" : [
  {
    "role" : "assetsWriter",
    "db" : "assets"
  },
  {
    "role" : "readWrite",
    "db" : "stock"
  },
  {
    "role" : "read",
    "db" : "products"
  }
]

次のrevokeRolesFromUserコマンドは、ユーザーの役割のうち、products の「読み取り」役割と、「資産」データベースのassetsWriterの2つを削除します。

use products
db.runCommand( { revokeRolesFromUser: "productUser00",
  roles: [
    { role: "AssetsWriter", db: "assets" },
    "read"
  ],
  writeConcern: { w: "majority" }
} )

商品データベースのユーザー「productUser00」が持つ役割は残り1つになります。

"roles" : [
  {
    "role" : "readWrite",
    "db" : "stock"
  }
]

権限管理コマンド

役割はユーザーにリソースへのアクセス権を与えるものです。管理者は、組み込みの役割を使って、MongoDBシステムへのアクセスを制御することができます。役割が必要な権限をカバーしていない場合は、特定のデータベースで新しい役割を作成することも可能です。

dropRole

dropRoleコマンドを使うと、コマンドを実行したデータベースからユーザーが定義した役割を削除することができます。このコマンドを実行するには、次の構文を使用します。

db.runCommand(
  {
    dropRole: "<role>",
    writeConcern: { <write concern> },
    comment: <any> 
  }
)

指定したデータベースでdropRoleアクションを実行するようにしてください。次の操作は、「products」データベースからwriteTagsの役割を削除するものです。

use products
db.runCommand(
  {
    dropRole: "writeTags",
    writeConcern: { w: "majority" }
  }
)
createRole

createRoleコマンドを利用して、役割を作成し、その権限を指定することができます。この役割は、コマンドを実行するデータベースに対して適用されます。役割がすでにデータベースに存在する場合、createRoleコマンドから役割の重複エラーが返されます。

このコマンドは以下のような構文になります。

db.adminCommand(
  {
    createRole: "<new role>",
    privileges: [
      { resource: { <resource> }, actions: [ "<action>", ... ] },
    ],
    roles: [
      { role: "<role>", db: "<database>" } | "<role>",
    ],
    authenticationRestrictions: [
      {
        clientSource: ["<IP>" | "<CIDR range>", ...],
        serverAddress: ["<IP>" | "<CIDR range>", ...]
      },
    ],
    writeConcern: <write concern document>,
    comment: <any> 
  }
)

役割の特権は、その役割が作成されたデータベースに適用されます。役割において、そのデータベース内の他の役割から特権を継承することが可能です。たとえば、「admin」データベースで作成した役割が、クラスタまたはすべてのデータベースに適用される特権を含むことができます。また、他のデータベースに存在する役割から特権を継承することもできます。

データベースで役割を作成するには、次の2つが必要です。

  1. そのデータベース上のgrantRole:新しい役割の特権と継承する役割を指定する
  2. createRoleアクション(対象のデータベースリソースに適用)

次のcreateRoleコマンドは、ユーザーデータベース上にclusterAdminの役割を作成するものです。

db.adminCommand({ createRole: "clusterAdmin",
  privileges: [
    { resource: { cluster: true }, actions: [ "addShard" ] },
    { resource: { db: "config", collection: "" }, actions: [ "find", "remove" ] },
    { resource: { db: "users", collection: "usersCollection" }, actions: [ "update", "insert" ] },
    { resource: { db: "", collection: "" }, actions: [ "find" ] }
  ],
  roles: [
    { role: "read", db: "user" }
  ],
  writeConcern: { w: "majority" , wtimeout: 5000 }
})
grantRolesToRole

grantRolesToRoleコマンドを使用すると、ユーザーの定義した役割に別の役割を付与することができます。grantRolesToRoleコマンドは、そのコマンドが実行されたデータベース上の役割に影響を与えることになります。

このgrantRolesToRoleコマンドは、以下の構文を取ります。

db.runCommand(
  {
    grantRolesToRole: "<role>",
    roles: [
     { role: "<role>", db: "<database>" },
    ],
    writeConcern: { <write concern> },
    comment: <any> 
  }
)

アクセス権限はgrantRolesToUserコマンドと同様です。コマンドを適切に実行するには、データベース上でgrantRoleアクションを実行する必要があります。

次の例では、grantRolesToRoleコマンドを使用して、「products」データベースのproductsReader役割を更新し、productsWriter役割の権限を継承させています。

use products
db.runCommand(
  { 
    grantRolesToRole: "productsReader",
    roles: [
      "productsWriter"
    ],
    writeConcern: { w: "majority" , wtimeout: 5000 }
  }
)
revokePrivilegesFromRole

revokePrivilegesFromRoleを使用すると、コマンドを実行したデータベース上のユーザー定義役割から指定された権限を削除することができます。これは以下のような構文になります。

db.runCommand(
  {
    revokePrivilegesFromRole: "<role>",
    privileges: [
      { resource: { <resource> }, actions: [ "<action>", ... ] },
    ],
    writeConcern: <write concern document>,
    comment: <any> 
  }
)

権限を取り消すには、「resource document」パターンがその権限の「resource」フィールドと一致する必要があります。「actions」フィールドは、完全一致でもサブセットでもかまいません。

例えば、「products」データベースの役割manageRoleについて考えてみましょう。リソースとして「managers」データベースを指定する以下のような権限を持つケースです。

{
  "resource" : {
    "db" : "managers",
    "collection" : ""
  },
  "actions" : [
    "insert",
    "remove"
  ]
}

「managers」データベースの1つのコレクションから、「insert」または「remove」アクションを取り消すことはできません。以下の操作を行っても、役割に変更は生じません。

use managers
db.runCommand(
  {
    revokePrivilegesFromRole: "manageRole",
    privileges: [
      {
        resource : {
          db : "managers",
          collection : "kiosks"
        },
        actions : [
          "insert",
          "remove"
        ]
      }
    ]
  }
)
db.runCommand(
  {
    revokePrivilegesFromRole: "manageRole",
    privileges:
      [
        {
          resource : {
          db : "managers",
          collection : "kiosks"
        },
        actions : [
          "insert"
        ]
      }
    ]
  }
)

役割の「insert」や「remove」アクションを取り消すには、manageRole 、リソースドキュメントとの正確な一致が必要になります。例えば、次の操作は、既存の特権から「remove」アクションだけを取り消すものです。

use managers
db.runCommand(
  {
    revokePrivilegesFromRole: "manageRole",
    privileges:
      [
        {
          resource : {
            db : "managers",
            collection : ""
        },
        actions : [ "remove" ]
      }
    ]
  }
)

次の操作は、「managers」データベースの「executive」という役割から複数の権限を削除するものです。

use managers
db.runCommand(
  {
    revokePrivilegesFromRole: "executive",
    privileges: [
      {
        resource: { db: "managers", collection: "" },
        actions: [ "insert", "remove", "find" ]
      },
      {
        resource: { db: "managers", collection: "partners" },
        actions: [ "update" ]
      }
    ],
    writeConcern: { w: "majority" }
    }
)
rolesInfo

rolesInfoコマンドは、組み込みとユーザー定義の役割の両方を対象に、指定された役割の権限と継承情報を返します。また、rolesInfoコマンドを活用して、データベースにスコープされたすべての役割を取得することもできます。

例えば、次の構文で実行可能です。

db.runCommand(
  {
    rolesInfo: { role: <name>, db: <db> },
    showPrivileges: <Boolean>,
    showBuiltinRoles: <Boolean>,
    comment: <any> 
  }
)

データベースから役割の情報を返すには、次のようにその名前を指定します。

{ rolesInfo: "<rolename>" }

別のデータベースから役割の情報を取得するには、役割とデータベースについて言及したドキュメントで役割を指定します。

{ rolesInfo: { role: "<rolename>", db: "<database>" } }

たとえば、次のコマンドは、「managers」データベースで定義された役割「executive」の役割継承情報を返します。

db.runCommand(
   {
      rolesInfo: { role: "executive", db: "managers" }
   }
)

この次のコマンドは、コマンドを実行したデータベースにおけるaccountManagerについての役割継承情報を返します。

db.runCommand(
   {
      rolesInfo: "accountManager"
   }
)

次のコマンドは、「managers」データベースで定義された役割「executive」の権限と役割継承の両方を返します。

db.runCommand(
   {
     rolesInfo: { role: "executive", db: "managers" },
     showPrivileges: true
   }
)

複数の役割を扱うには、配列を使用します。また、配列内の各役割を文字列またはドキュメントとして記述することができます。

文字列を使用するのは、コマンドが実行されるデータベース上に役割が存在する場合のみです。

{
  rolesInfo: [
    "<rolename>",
    { role: "<rolename>", db: "<database>" },
  ]
}

例えば、次のコマンドは3つのデータベース上の3つの役割の情報を返します。

db.runCommand(
   {
    rolesInfo: [
      { role: "executive", db: "managers" },
      { role: "accounts", db: "departments" },
      { role: "administrator", db: "products" }
    ]
  }
)

以下のように、権限と役割継承の両方を取得することができます。

db.runCommand(
  {
    rolesInfo: [
      { role: "executive", db: "managers" },
      { role: "accounts", db: "departments" },
      { role: "administrator", db: "products" }
    ],
    showPrivileges: true
  }
)

MongoDBドキュメントを埋め込んでパフォーマンスを引き上げる

MongoDBのようなドキュメントデータベースでは、状況に応じてスキーマを定義可能です。MongoDBでスキーマを作成する上で、ドキュメントをネストすることができます。つまり、アプリケーションをデータモデルに合わせるのではなく、用途に合わせたデータモデルを構築することができるということです。

ドキュメントを埋め込むことで、一緒にアクセスする関連データを保存することができます。MongoDBのスキーマを設計する際には、デフォルトでドキュメントを埋め込むことが推奨されます。データベースサイドやアプリケーションサイドの結合や参照は、必要なときだけ使うようにしましょう。

ワークロードが必要な回数だけドキュメントを取得できるようにしましょう。同時に、ドキュメントには必要なデータがすべて存在する必要があります。これは、アプリケーションのパフォーマンス向上を実現する上で極めて重要です。

以下では、ドキュメントを埋め込む複数のパターンをご紹介します。

埋め込み型ドキュメントパターン

この方法で、複雑なサブストラクチャもドキュメントに埋め込んで使うことができます。連結データを1つのドキュメントに埋め込むことで、データを取得するのに必要な読み取り操作の回数を減らすことができます。一般に、スキーマの構造では、アプリケーションが1回の読み取り操作で必要な情報をすべて受け取れるようにする必要があります。従って、ここで留意すべきは、一緒に使われるものは一緒に保存するというルールです。

埋め込み型サブセットパターン

埋め込み型サブセットパターンは、「ハイブリッド」な手法だと言えます。たくさんの項目を格納したコレクションに対して使用することで、その項目のいくつかを手元に置いてすぐに表示できるようになります。

映画のレビューを表示する例を以下に示します。

> db.movie.findOne()
{   
  _id: 321475,   
  title: "The Dark Knight"
}  
> db.review.find({movie_id: 321475})
{   
  _id: 264579,   
  movie_id: 321475,   
  stars: 4   
  text: "Amazing"   
}
{   
  _id: 375684,   
  movie_id: 321475,   
  stars:5,   
  text: "Mindblowing"
}

同じようなレビューが1,000件あるとして、その映画について最新の2件だけを表示したいとします。そんな時には、そのサブセットをドキュメントの中に整理して保存するのが効果的です。

> db.movie.findOne({_id: 321475})   
{   
  _id: 321475,   
  title: "The Dark Knight",   
  recent_reviews: [   
    {_id: 264579, stars: 4, text: "Amazing"},   
    {_id: 375684, stars: 5, text: "Mindblowing"}   
  ]   
}

一般的に言って、関連するいくつかの項目を日常的に取り出す仕様であれば、それをまとめて埋め込むようにしましょう。

コレクションの分離

サブドキュメントを、親コレクションから分離するために、そのコレクションに格納したいケースがあります。

例えば、ある会社の製品ラインナップを考えてみましょう。その会社が小さな製品群を販売している場合、それを会社のドキュメント内に格納しようと考えるかもしれません。しかし、会社間で再利用したり、在庫管理単位(SKU)で直接アクセスしたりできるように、コレクションに格納することも必要になります。

エンティティを単独で操作、アクセスするには、ベストプラクティスとしてコレクションを作成して個別に保存するようにしましょう。

配列肥大化の防止

関連する情報を配列としてドキュメントに格納することができますが、これには欠点があります。管理の難しさやサイズの肥大化といった観点から、1つのドキュメントで配列を際限なく拡張するのは避けたいところです。

これを避けるべき明確な理由が2つ存在します。1つ目として、MongoDBのドキュメント毎にサイズ制限があります。2つ目に、ドキュメントにアクセスする頻度が多すぎると、メモリの使用量が制御できなくなり、様々な問題が引き起こされる可能性があります。

簡単に言えば、配列が際限なく肥大化していると感じたら(むしろそうなる前に)、コレクションを作って別に保存するようにしましょう。

拡張参照パターン

拡張リファレンスパターンは、サブセットパターンと同じような概念ですが、これには定期的にアクセスする情報を最適化するという特徴があります。

この手法は配列の代わりに、ドキュメントが同じコレクションに存在する別のドキュメントを参照するときに利用されます。すぐにアクセスできるように、他の対象のドキュメントからフィールドを保存します。

例えば、以下のようになります。

> db.movie.findOne({_id: 245434})
{   
  _id: 245434,   
  title: "Mission Impossible 4 - Ghost Protocol",   
  studio_id: 924935,   
  studio_name: "Paramount Pictures"   
}

このように、「studio_id」が保存されているので、映画を制作したスタジオの詳細情報を調べることができます。スタジオの名前もこのドキュメントにコピーされ、簡単に扱えるようになっています。

別のドキュメントから定期的に情報を埋め込むとして、情報の変更が発生した時には、当然ながら対応する更新作業を行う必要があります。参照先のドキュメントの特定のフィールドに日常的にアクセスする場合には、それを埋め込むことができます。

MongoDBを監視する方法

Kinsta APMのような監視ツールを使って、時間のかかっているAPIコール、データベースクエリ、外部URLリクエストなどをデバッグすることができます。コマンドを活用し、データベースのパフォーマンスを改善することも可能です。また、データベースインスタンスの健全性を検査するのにも有用です。

MongoDBデータベースを監視する必要性

クラスタのパフォーマンスと健全性を監視することは、データベース管理計画の重要な側面の一つです。MongoDB Atlasを使って(そのフォールトトレランス/スケーリング機能により)管理作業の大部分を処理することができます。

とはいえ、クラスタを追跡する方法を知っておく必要があります。また、危機に直面する前に、必要なものをスケーリングしたり、調整したりする方法を知っておきたいところです。

MongoDBデータベースを監視することで、以下のことが可能になります。

  • リソースの使用状況を観察する
  • データベースの現在のキャパシティを把握する
  • アプリケーションスタックを強化するために、リアルタイムで問題に反応し、検出する
  • パフォーマンスの問題や異常な動作の有無を確認する
  • ガバナンス、データ保護、サービスレベル合意(SLA)要件に合致するように調整する

監視すべき主な指標

MongoDBを監視する際には、4つの重要な側面を念頭に置く必要があります。

MongoDBのハードウェアに関する指標

ハードウェア監視の主な指標を以下に示します。

正規化プロセスCPU

MongoDBプロセスを維持するアプリケーションソフトウェアに費やされるCPUの時間の割合と定義されます。

CPUコア数で割ることで、0~100%の範囲にスケールできます。カーネルやユーザーなどのモジュールで活用されるCPUもこれに含まれます。

カーネルのCPUが高いと、OSの操作でCPUを使い果たしたことを示すかもしれません。しかし、MongoDBの操作に連動するユーザーがCPUを疲弊させる根本的な原因かもしれません。

正規化システムCPU

CPUがこのMongoDBプロセスのシステムコール処理に費やした時間の割合です。CPUコア数で割ることで、0~100%の範囲で特定することができます。iowait、user、kernel、stealなどのモジュールが使用するCPUも対象となります。

CPUやカーネルの負担が高い場合は、MongoDBの操作(ソフトウェア)によるCPUの消耗が懸念されます。iowaitが高い場合は、ストレージの枯渇がCPUの消耗を引き起こしている可能性があります。

ディスクIOPS

ディスクIOPSは、MongoDBのディスクパーティションで1秒間に消費されるIO操作の平均値です。

ディスクレイテンシ

MongoDBのディスクパーティションの読み込みと書き込みのディスクレイテンシ(ミリ秒)です。この値が高い(>500ms)と、ストレージ層がMongoDBのパフォーマンスに影響を与えている可能性があります。

システムメモリ

システムメモリを使用して、使用されている物理的なメモリの量と使用可能な空き領域を把握することができます。

利用可能なシステムメモリが概算で表示されます。これを利用して、スワップなしで新しいアプリケーションを実行することができます。

ディスクの空き容量

MongoDBのディスクパーティションにあるディスクの空き容量です。MongoDB Atlasは、この指標に基づいて自動スケーリング機能を実行します。

スワップ使用量

スワップ使用量グラフを活用して、スワップデバイスに置かれているメモリの量を確認することができます。この使用率が高いと、スワップが利用されていることを意味し、既存のワークロードに対してメモリが十分にプロビジョニングされていないことになります。

MongoDBクラスタの接続と操作

クラスタの接続と操作に関わる主要なメトリクスをご紹介します。

操作の実行時間

選択したサンプル期間中に実行された平均操作時間(書き込み操作と読み取り操作)です。

opcounters

選択したサンプル期間において、1秒間に実行された操作の平均レートです。opcountersグラフ/メトリクスからは、インスタンスのオペレーションタイプとベロシティといった内訳が分かります。

接続─connections

この指標は、インスタンスへのオープンな(特定の期間に実行中の)接続数を意味します。数値の急上昇などは、サーバーが応答していない、またはクライアント側からの接続が最適でない可能性を示唆します。

クエリターゲティングとクエリエクゼキュータ

スキャンしたドキュメントにおける、一定期間中の1秒あたりの平均レートです。クエリエクゼキュータにおいては、クエリプランの評価時点が対象となります。クエリターゲティングは、スキャンしたドキュメント数と返されたドキュメント数の比率を示します。

その結果、例えば少ない情報を返すために、多くのドキュメントがスキャンされていることが判明した場合には、クエリの効率が悪いことがわかります。

スキャンと並び替え─scan and order

選択したサンプル期間のクエリにおける1秒あたりの平均レートを意味します。インデックスを使用した操作を実行できない、ソートされた結果が返されます。

キュー─queues

キューは、書き込みまたは読み込みのロックを待っている操作の数を示します。キューが多いと、スキーマの設計が最適でない可能性があります。また、書き込み経路が競合し、データベースリソースをめぐる競争が激しくなっている状態であることもあります。

MongoDBのレプリケーションに関する指標

レプリケーション監視のための主要な指標は以下の通りです。

レプリケーションoplogウィンドウ

この指標は、プライマリのレプリケーションoplogで利用可能なおおよその時間を示します。セカンダリ側がこの時間より遅れている場合、追いつくことができず、完全な再同期が必要になります。

レプリケーションラグ

レプリケーションラグは、セカンダリノードがプライマリノードより書き込み操作で遅れている具合を、おおよその秒数で示します。レプリケーションラグが大きいと、セカンダリのレプリケーションが困難であることを意味します。接続の読み書きに関する懸念が生じ、運用のレイテンシに影響が出る可能性があります。

レプリケーションヘッドルーム

この指標は、プライマリレプリケーションのoplogウィンドウとセカンダリのレプリケーションラグの差分を指します。この値がゼロになると、セカンダリがRECOVERINGモードになる可能性があります。

opcounters – repl

Opcounters – replは、選択したサンプル期間において、1秒間に実行されたレプリケーション操作の平均レートです。opcounters – graph/metricを使用すると、指定したインスタンスの操作速度と操作タイプの内訳を見ることができます。

oplog GB/時間

プライマリが1時間あたりに生成するoplogの平均レート(GB)です。予期せぬ大量のoplogが発生した場合、書き込み作業能力が大きく不足しているか、スキーマ設計に問題がある可能性があります。

MongoDBパフォーマンス監視コマンド

MongoDBには、Cloud Manager、Atlas、Ops Managerに標準搭載するかたちで、パフォーマンス追跡ツールが用意されています。また、より生に近いデータを見るためのコマンドやツールもいくつかあります。以下では、アクセス権や権限を持つホストから実行できる、環境をチェックするのに便利なコマンドをご紹介します。

mongotop

このコマンドを使うと、MongoDBインスタンスがコレクションごとにデータの書き込みと読み取りに費やす時間を追跡することができます。次のように使用します。

mongotop <options> <connection-string> <polling-interval in seconds>

rs.status()

このコマンドは、レプリカセットのステータスを返します。メソッドが実行されるメンバーの視点から実行されます。

mongostat

mongostatコマンドを使うと、MongoDBサーバーインスタンスのステータスを簡単に把握することができます。リアルタイムで表示されるので、特定のイベントに対して単一のインスタンスを監視するのに使用するのが効果的です。

このコマンドを活用して、ロックキュー、オペレーションブレイクダウン、MongoDBメモリ、接続/ネットワークなどの基本的なサーバー統計情報を監視できます。

mongostat <options> <connection-string> <polling interval in seconds>

dbStats

このコマンドは、インデックスの数とそのサイズ、コレクションデータの合計とストレージのサイズ、コレクション関連の統計(コレクションとドキュメントの数)など、特定のデータベースのストレージ情報を返します。

db.serverStatus()

db.serverStatus()コマンドでは、データベースの状態の概要を把握することができます。対象のインスタンスについての各種指標がドキュメント形式で表示されます。このコマンドを定期的に実行することで、インスタンスに関するデータを照合可能です。

collStats

collStatsコマンドでは、dbStatsと同様の情報を(データベース全体ではなく)コレクションレベルで表示できます。コレクション内のオブジェクト数、コレクションによって消費されているディスク容量、コレクションのサイズ、および特定のコレクションに対するインデックスといった情報が確認できます。

このコマンドを使用すると、データベースサーバーのリアルタイムレポートの確認や監視が可能です。データベースのパフォーマンスとエラーをモニタリングし、その情報に基づいてデータベースを改良するための意思決定を行うことができます。

MongoDBデータベースを削除する方法

MongoDBで作成したデータベースを削除するには、useキーワードでデータベースに接続します。

例えば、「Engineers」という名前のデータベースを作成したとします。このデータベースに接続するには、次のコマンドを使用します。

use Engineers

次に、このデータベースを削除するには、db.dropDatabase()と入力します。実行後、以下のような結果が返されるはずです。

{ "dropped"  :  "Engineers", "ok" : 1 }

showdbsコマンドを実行して、データベースがまだ存在するかどうかを確認することもできます。

まとめ

MongoDBの価値を引き出すには、その性能をしっかりと理解する必要があります。だからこそ、MongoDBデータベースの基礎を学び、復習することが極めて重要です。その一歩が、データベースの作成だと言えるでしょう。

この記事では、MongoDBでデータベースを作成するさまざまな方法、そしてデータベースの管理に便利なMongoDBコマンドについて詳しく説明しました。また、ワークフローの効率を引き上げるためにおすすめのMongoDB組み込みドキュメントやパフォーマンス監視コマンドにも触れました。

今回ご紹介したMongoDBコマンド等について、特別な思い入れはありますか?また、この記事で紹介してほしい内容やポイントはありますか?コメント欄でお聞かせください。

Salman Ravoof

Salman Ravoof is a self-taught web developer, writer, creator, and a huge admirer of Free and Open Source Software (FOSS). Besides tech, he's excited by science, philosophy, photography, arts, cats, and food. Learn more about him on his website, and connect with Salman on Twitter.