記事一覧はこちら
Table of Contents
背景・モチベーション
8月1日から中途入社して働いている会社ではRDBだけでなく、いわゆるNoSQL(Not Only SQL)であるMongoDBを利用しています。そのうえshardingも利用しているということで、データ指向アプリケーションデザインを7月に読んでいたこともあり、非常に興味を持ちました。
同じチームの方から、MongoDB Universityが良いよというアドバイスを頂き、早速受講してみたのでまとめたメモをブログに残しておこうと思います。
自分が受講したコースは、レクチャー動画を観て、Quizを解き、更にインタラクティブなターミナルをブラウザ上で動かして実際に構築しているという流れで進んでいきます。動画を観るだけではないので、講義内容が右から左に抜けていきづらくなっていると思います。動画は英語ですが、Transcriptがダウンロード出来るようになっているので、最悪DeepLにお願いすることでなんとかなります。
講師の方の中にスターウォーズが好きな人が居て、時々画面の前の僕らに向かってYoung Padawan
と語りかけてくるのが面白いです。
M103
Chapter 1: The Mongod
What is mongod
mongod
はmongodbのメインのdaemon processです。
It is the core server of the database, handling connections, requests, and most importantly, persisting your data.
mongodはReplicaSetやSharded Clusterなど複数サーバでの構成において、各サーバで動作する
Default Configuration
設定ファイルや起動引数で設定を渡すことになるが、渡さない場合にはデフォルトの設定で動く
- The port mongod listens on will default to 27017.
- The default dbpath is /data/db.
- the data files representing your databases, collections, and indexes are stored so that your data persists after mongod stops running
- The dbpath also stores journaling information (crash logs…)
- mongod binds to localhost
- mongodに接続できるクライアントはlocalhostに存在するものだけ
- リモートクライアントを受け入れるには設定を変更する必要がある
- Authentication is turned off
当たり前っちゃ当たり前だけど、リモートからのアクセスを許可するときには、auth
をtrue
にしましょうね
Configuration file
コマンドラインでオプション渡す事もできるが、設定ファイルを利用することも可能
設定ファイルの形式はYAMLとなる
階層化されてるので、コマンドオプションより格段readabilityが増して分かりやすいぞ
mongod -f "/etc/mongod.conf"
Configuration File Options — MongoDB Manual
File Structure
MongoDB standalone server のときのファイル構造
root@2a9d0458fdcc:/data/db# tree -L 2 .
.
|-- WiredTiger
|-- WiredTiger.lock
|-- WiredTiger.turtle
|-- WiredTiger.wt
|-- WiredTigerHS.wt
|-- _mdb_catalog.wt
|-- collection-0-7411902203291987629.wt
|-- collection-2-7411902203291987629.wt
|-- collection-4-7411902203291987629.wt
|-- diagnostic.data
| |-- metrics.2021-08-22T08-52-18Z-00000
| `-- metrics.interim
|-- index-1-7411902203291987629.wt
|-- index-3-7411902203291987629.wt
|-- index-5-7411902203291987629.wt
|-- index-6-7411902203291987629.wt
|-- journal
| |-- WiredTigerLog.0000000001
| |-- WiredTigerPreplog.0000000001
| `-- WiredTigerPreplog.0000000002
|-- mongod.lock
|-- sizeStorer.wt
`-- storage.bson
WiredTiger (storage engine) がクラスタのメタデータやWiredTiger固有の設定などの情報をtrackする用途で使われているファイル群が上記
The WiredTiger.lock
file acts as a safety.
2つ目のMongoDBプロセスを同時に実行し、このフォルダを指定した場合、ロックファイルはその2つ目のMongoDBプロセスが起動するのを防いでくれます。 mongod.lock
も同じような役割を果たす。
ホストマシンが落ちたりcrashしたときには、このlockファイルのせいでmongodが起動できないことがある。
.wt
の拡張子を持つファイルは、collectionとindexのデータ。WiredTigerでは、それぞれ別の構造として保存される。
diagnostic.data
ディレクトリはMondoDBサポートエンジニアが診断する目的のみのために利用されるもの。
この診断データは、Full Time Data Capture (FTDC) モジュールによって取得される。
journal
ディレクトリ内のファイルはそれぞれWiredTigerのjournaling systemの一部。
WiredTigerでは、書き込み操作はメモリにバッファリングされる。60sごとにflashされてデータのチェックポイントが作成される。
また、WiredTigerでは、ディスク上のjournal fileへの書き込みにWAL(Write Ahead Logging)を採用している。journal entryははまずメモリ上にバッファリングされ、その後WiredTigerは50ミリ秒ごとにjournalをディスクに同期します。Each journal file is limited to 100 megabytes of size.
WiredTigerは、データのディスクへの同期にファイルローテーション方式を採用しています。障害発生時には、WiredTigerはjournalを使用してチェックポイント間に発生したデータを回復することができます。
※ 基本的にMongoDBのdata direcrotyは直接編集を行わないこと!
root@2a9d0458fdcc:/tmp# tree .
.
`-- mongodb-27017.sock
/tmp
ディレクトリには、socketファイルがありプロセス間通信に利用される。
このファイルは起動時に作成され、MongoDBサーバーにこのポートを所有させる。
Basic Commands
Shell Helperを利用することでメソッドLikeに操作することができる
db
- database
db.<collection>.createIndex
等…
db.runCommand()
をwrapしてusabilityを高めたもの
rs
- replicaset
sh
- sharded
db.commnadHelp(<Command>)
でヘルプが見れる
Logging
db.getLogComponents()
でlogLevelを確認できる
commandやindexなどのcomponentごとにLoglevelが設定できる
-1
は親の指定を引き継ぐこと。下記でいうと、一番上のレベルで”verbosity”: 0
が宣言されていているので0
になる 0 - 5
まで設定することでき、数字が高いほどverboseとなる。0はinfoレベルのみで、1からdebugレベルの出力を行う。
{
"verbosity" : 0,
"accessControl" : {
"verbosity" : -1
},
"command" : {
"verbosity" : -1
},
"control" : {
"verbosity" : -1
},
"//": "omitted..."
}
ログレベルの設定
mongo admin --eval '
db.setLogLevel(0, "index")
'
ログを確認する手段は2つ
# via Mongo Shell
db.adminCommand({ "getLog": "global" })
# via command line
tail -f /data/db/mongod.log
5 Severity Levels
Logエントリの2つ目のフィールド
F - Fatal
E - Error
W - Warning
I - Information(Verbosity Level 0)
D - Debug(Verbosity Level 1-5)
Profilers
ログにはコマンドに関するデータも含まれていますが、クエリの最適化を開始するのに十分なデータはありません。
execution statsやクエリで使用されるインデックス、rejected planなどの情報を取得し、遅いoperationをデバッグするためには、ログではなくProfilerを利用する。
Profilerが取得するデータは下記の3つ
- CRUD operations
- Administrative operations
- Configuration operations
データベースレベルでProfilerを有効にできる。
有効にすると、system.profile
というcollectionに、CRUD operationのprofiling dataが格納される。
profilingLevelには3つの段階がある
- 0: profilingがオフになっている
- 1: slow operationのみprofilingする
- 2: 全てをprofilingする
デフォルトでは、100ms 以上の操作を「遅い」と判断するが、自分で設定することも可能
> db.getProfilingLevel()
0
> db.setProfilingLevel( 1, { slowms: 50 } )
{ "was": 0, "slowms": 50, "sampleRate": 1, "ok": 1 }
> db.system.profile.find().pretty()
Security
Authentication
MongoDB Enterprise Only
Authorization
RBAC
- Each user has one or more Roles
- Each Role has one or more Privilleges
- A Previlleges represents a group of Action and the Resources those actions apply to
mongodでauthenticationを有効にしても、デフォルトではMongoDBはユーザを作成しないので手詰まりになってしまいます。
そのため、最初にユーザを作成するまではlocalhostからなら認証なしで操作できるというLocalhost Exceptionが存在します。
必ず最初に管理者権限のあるユーザーを作っておき、Localhost Exceptionがclose後に、ユーザを作成できる状況にしておく必要があります。
$ mongo --host 127.0.0.1:27017
> use admin
> db.createUser({
user: "root",
pwd: "root123",
roles : [ "root" ]
})
Role
Role Structure
Role is composed of
- Set of Privilleges
- Network Authentication Restrictions
- clientSource
- serverAddress
Resources
- Database
- Collection
- Set of Collections
- Cluster
- ReplicaSet
- Sharded Cluster
Built-In Roles
Each Database or All Database (two scope)
- Database User Group
- Database Administration Group
- Super User Group
Only one scope
- Cluster Administration Group
- Backup/Restore Group
> use admin
> db.createUser(
{ user: "m103-application-user",
pwd: "m103-application-pass",
roles: [ { db: "applicationData", role: "readWrite" } ]
}
)
root@2a9d0458fdcc:/tmp# find /usr/bin/ -name "mongo*"
/usr/bin/mongodump
/usr/bin/mongod
/usr/bin/mongo
/usr/bin/mongos
/usr/bin/mongoexport
/usr/bin/mongotop
/usr/bin/mongosh
/usr/bin/mongoimport
/usr/bin/mongostat
/usr/bin/mongofiles
/usr/bin/mongorestore
mongostat
mongostat — MongoDB Database Tools
CRUD Operationやメモリ、ネットワークの統計を確認することができる
mongodump / mongorestore
mongodumpはBSONで保存されているCollectionをBSONのまま出力する。
データの変換がないために高速。ディレクトリを指定しないと、カレントディレクトリ配下にdump
ディレクトリが作成されてその中に吐かれる。metadataはJSON形式で出力される。
mongorestoreコマンドでcollectionをdumpから作成できる。
mongoexport / mongoimport
単一のファイルにJSONファイルとして吐ける(デフォルトでは標準出力に)。
BSONでなくJSONなのでそんなに速くない。また、metadataのファイルを作らないので、database名やcollection名を指定してあげる必要がある。
# mongoimport --port 27000 --file /dataset/products.json \
-d applicationData -c products -u m103-application-user \
-p m103-application-pass --authenticationDatabase=admin
Chapter 2: Replication
What is ReplicaSet?
同じデータセットを持つmongod
プロセスのグループ
ReplicaSetには最大1つのPrimaryが存在する。もしPrimaryがunavailableとなった場合には、新しいPrimaryがSecondaryからvoteのプロセスを経て選出されてサービスを継続する(fail over)。 Replica Set Elections に詳細がある。
全てのメンバがRead operatoinを受け入れられるが、Write operatoinはPrimaryのみ。Secondaryは非同期にPrimaryのデータ更新を受け取り同期する。
Replicationのプロトコルには異なるバージョンがあるが、デフォルトはProtocol Vesion1でRAFTをベースにしたもの。
Rplicationメカニズムの中心となるのが、oplog
になる。Primaryノードへの書き込みが成功するたびにoplog
がidempotentな形式で記録される。
ReplicaSetのメンバには、Primary/Secondaryの他にarbiterという役割も設定できる。arbiterはデータセットを持たない。なのでPrimaryにもなれない。electionのプロセスで頭数を揃えるためにある役割。分散データシステムの一貫性に大きな問題を引き起こすので利用をあまりおすすめしない。
Failoverには過半数のノードが利用可能であることを必要とする。
ReplicaSetは奇数のノードが必要(最低でも3ノード)
ReplicaSetは最大50メンバーまで利用可能だが、voting memberは7まで。7を超えると時間がかかりすぎてしまうため。
Secondary には特定のプロパティが存在する
- Hidden Node
- アプリケーションから隠されたデータのコピーを持つこと
- レプリケーションプロセスの遅延も設定できる(= Delayed Node)
- Delayed Node
- アプリケーションレベルの破損に対して、コールドバックアップファイルに頼らずに回復できるようにする目的
Initiate ReplicaSet
Replicationするための設定
replicationを設定すると、clientのauthenticationを行われるようになる
openssl rand -base64 741 > /var/mongodb/pki/m103-keyfile
chmod 400 /var/mongodb/pki/m103-keyfile
security:
keyFile: /var/mongodb/pki/m103-keyfile
replication:
replSetName: m103-example
Replicationを開始するためのコマンド
rs.initiate() # このコマンドを発行したノードがPrimaryになる
rs.isMaster() # どれが Primary になっているか確認
rs.stepDown() # 意図的にSecondaryをPrimaryに昇格させる
Secondaryを参加させるには、rs.add() を利用する必要がある
mongo --port 27003 -u m103-admin -p m103-pass —-authenticationDatabase admin
rs.add("localhost:27001")
rs.add("localhost:27002")
rs.status() # 確認
Replication Configuration
- JSON Objectで表現される設定
- mongo shellから手作業で設定することも可能
- helper method: rs.initiate(), rs.add(), etc…
Replica Set Configuration — MongoDB Manual
{
"_id": "replicaSetName",
"version": X,
"members": [
{
"_id": 1,
"host": "mongo.example.com:28017",
"arbiterOnly": false,
"hidden": false,
"priority": 1,
"secondaryDelaySecs": 3600,
}
]
}
Replication Command
rs.status()
- ReplicaSetの総合的な情報を出力してくれる
- Heartbeatも確認できる
rs.hello()
- ノードのRoleを表示する
rs.status()
よりシンプル
- 以前は、
rs.isMaster()
という名称だったがdeprecatedになっている
db.serverStatus()[‘repl’]
- r
s.isMaster()
に含まれていないrbidというのが出る
rs.printReplicationInfo()
Local DB
ReplicaSetの構成を組んでいる場合に local
DBの中身は下記のように複数ある
standaloneの場合には、startup.logのみ存在する
local DBに直接書いたデータはReplicationされない
殆どはサーバ内部で利用している情報
oplog.rsは、レプリケーションメカニズムの中心で、レプリケートされているすべてのステートメントを追跡するoplogコレクション
oplog.rsコレクションには、知っておくべきことが幾つか
- capped collection
- デフォルトではoplog.rsコレクションは、空きディスクの5%を占める(デカい)、もちろん設定で指定することも出来る
- oplogは短時間ですぐ増大する(Fear not, young Padawan.)
- oplogのサイズが埋まったら、古いログから上書きされていく
- oplogのサイズはどう影響するか?
- 例えば、Secondaryのノードの接続が途切れてしまった場合、そのノードは Recoveryモードになり同期できてた操作から直近までの操作を一気に書き込んで追いつきます。しかしながら、既に同期できてた操作がoplogに残っていなかった場合に追いつけずエラーになってしまう
> use local
> show collections
me
oplog.rs
replset.election
replset.minvalid
startup.log
system.replset
system.rollback.id
Reconfiguraion Replicaset
例えば、4ノードになっていたのでvote出来るnodeを奇数にしつつ、hiddenにしてしまうのケースの場合には…
これはDBを止めることなく反映することが可能
cfg = rs.conf()
cfg.members[3].votes = 0
cfg.members[3].hidden = true
cfg.members[3].priority = 0
rs.reconfig(cfg)
rs.conf()
Reads and Writes on ReplicaSet
Secondaryのノードに対してmongo shellを起動してデータを読み込もうとしてみます。ちなみに ReplicaSetの名称を指定すると自動的にPrimaryにつなぎにいくので注意が必要になる。
Secondaryのノードではこのままだとコマンドを実行できません。 MongoDBはconsistencyを重視しているため、Secondaryから読み込む場合には明示的に伝える必要がある。それが、rs.secondaryOk
になる。
consistencyを担保するために書き込みはPrimaryにしか出来ないようになっている。
ReplicaSetが過半数のノードに到達できなくなると、レプリカセットの残りのノードはすべてSecondaryになる。たとえ、残ったのがPrimaryのノードだったとしても。
Failover and Election
Primaryが利用できなくなる理由
一般的にはメンテナンスがそう
例えばローリングアップグレード(e.g. v3.4→ v3.6)
- SecondaryのMongoDBプロセスを停止し、新しいDBのバージョンで戻ってきます(1台ずつ)
- 最後に Primary で
rs.stepDown()
で利用して安全にelectionを開始します
- electionが完了すると最後の古いバージョンのMongoDBプロセスはSecondaryになる
- そして同じようにプロセスを新しいバージョンで起動すれば全て完了!かつ可用性も損なわない
Electionの仕組み
どのSecondaryがPrimaryに立候補するかという点に関してはロジックが存在する
- Priorityが全ノード同じ値
- その場合は最新データを持っているノードが立候補する
- そのノード自身が自分に投票を行う
- 2ノードが名乗り出たとしても、投票権を持っているのが奇数ノードであれば問題ないが、偶数ノードだった場合には同点となる可能性があり、もし同点になった場合はelectionをやり直すことになる(=処理がストップする)
- Priorityがノードごとに違う場合
- Priorityが高いほど、Primaryになる可能性が高くなる
- ノードをPrimaryにしたくない場合には、Priorityを0にする
- Primaryになる資格のないノードのことをpassive nodeと言うらしい
前述したが、ReplicaSetの過半数のノードがダウンしたときには、たとえPrimaryだとしても疎通できなくなる
Write concern
write concern
はdeveloperが、write operationに追加できるAckknowledgement(確認)の仕組み
ACKのレベルが高いほどDurability(耐久性)が増す
書き込みが成功したことを確認するReplicaSetのメンバが多ければ多いほど、障害が発生したときに永続化が継続する可能性が高い
Durabilityを高めようとすると、各ノードから書き込み確認の応答を貰う必要があるので待ちが必要になります。
Write Concern level
- 0: クライアントは確認応答を待たないので、ノードの接続に成功したかどうかを確認するだけ
- 1: デフォルトの値。クライアントはPrimaryからの確認応答を待つ
- => 2 : Primaryと1つ以上のSecondary、例えばlevelが3のときには1つのPrimaryと2つのSecondaryから確認応答を待つ
majority
というキーワードを利用することができる。これはReplicaSetのサイズが変わってもいちいち変えなくても良い。levelはメンバの数を2で割って切り上げた値となる。
Write Concernはsharded clusterも対応している。
MongoDBには更に2つのWrite Concernオプションがある。
wtimeout
: クライアントが操作に失敗したと判断するまでの最大時間。重要なのは書き込みが無かったことになるわけではなく要求した耐久性を満たさなかったということ
j
: journalの意。各ReplicaSetのメンバが書き込みを受け取ってジャーナルファイルにコミットしないと確認応答を返せない。これをtrue
にすることでディスクに書き込まれることまで保証できる。false
のときにはメモリに保存する所までの確認。
MongoDB 3.2.6以降は、Write concernが過半数になるとデフォルトでj
がtrueになる。
Write concernはクライアントのアプリケーションから指定するっぽい。
Read Concern
起きうる不味いシチュエーション
- クライアントアプリケーションがdocumentをinsertする
- そのdocumentがSecondaryにReplicateされる前に、クライアントアプリケーションがReadする
- 突然Primaryが壊れる
- ReadしたdocumentはまだSecondaryにReplicateされてない
- 古いPrimaryがオンラインに戻ったとき、同期プロセスの中でそのデータはRollbackして存在していないことになります
- 📝ここで古いと行っているのはFailoverが起こるため、復帰したときにはSecondaryになっているからと思います
このシチュエーションを許容できない要件のアプリケーションの場合に困ります。
そんなときに役に立つのがRead Concernである。
Read Concernで指定された数のReplicaSetメンバに書き込まれたと認められたデータのみが返されるようになる。
Read Concern Level
- Local: Primaryを読み取るときのデフォルトの設定。クラスタ内の最新のデータを返す。Failover時のデータの安全性が保証されない。
- Available: Secondaryを読み取るときのデフォルトの設定。ReplicaSetのときはLocalと同じで、Sharded Clusterのときに挙動が変わる
- Majority: 過半数のReplicaSetメンバに書き込まれたことが確認されたデータのみを返す。Durabilityと Fastの中間だが古いデータを返すこともある
- Linearizeable:
read your own write
を提供する。常に新しいデータを返すが、読み取り操作が遅かったり、制限がある。
どのようなRead Concern Levelにするかは、”Fast”, “Safe”, “Latest” の観点で考えると良さそう
LocalやAvailableはSecondaryの読み取りに関しては最新が返ってくるとは限らない
Read Preference
読み込みのoperationをルーティングする設定
- primary(default): Primaryのノードからしか読み取らない
- primaryPreffered: PrimaryがUnavailableになったときにはSecondaryから読み取る
- secondry: Secondaryのノードからしか読み取らない
- secondaryPreffered: Secondaryが全てがUnavailableになったときにはPrimaryから読み取る
- nearest: 地理的に一番近いところ
Chapter 3: Sharding
What is Sharding?
画像引用元
MongoDB sharded cluster はshard
, mongos
, config servers
で構成されている。
When to sharding?
いつShardingが必要になるのかを考えてみましょう
まず最初にVerticcal scaleが経済的に可能かを確認する。特定されたボトルネックにリソースを増やすことでダウンタイムなしにパフォーマンスが向上する。
しかしながら、経済的な理由や非常に困難なポイントにぶち当たりスケールアップが難しくなります。
もう一つ考慮すべき点は、運用業務への影響です。
データセットの量が10テラバイト級になるとバックアップやリストアに時間がかかる。ディスクのサイズを増やすとインデックスのサイズも増えることになり、多くのRAMが必要になる。
一般的には、個々のサーバーには2~5テラバイトのデータを格納することが望ましいとされている
それ以上になると時間がかかりすぎてしまう。
最後に、下記のようなケースでもshardingは有用
- 並列化可能なSingle Thread Operation
- 地理的に分散したデータ → zone sharding
- 特定の地域に保存する必要があるデータ(M121でしっかり理解出来るやつ)
- ↑のようなデータを取得するクライアント
Sharding Architecuture
https://youtu.be/6cCL4-3gF8o
動画がとても分かりやすかった
mongos
は config server
からクエリされたデータがどのshardにあるかを取得し、ルーティングする。各shardに含まれる情報は時間とともに変化する可能性があるのでとても重要。mongos
は頻繁にconfig server
にアクセスする。
なぜ変化するかというと、各シャードのデータ量が均等になるように分散させるため。
sharded clusterはprimary shardという概念も存在する。各データベースにはprimary shardが割り当てられ、そのデータベースのshard化されていないcollectionは全てそのshardに残る。
primary shardには他にも幾つか役割がある。一つは、aggregation commandのMERGE操作によるもの。
shard key以外を条件にしたクエリの場合、例えば動画の例だとサッカー選手の名前でshardを分けているが、年齢に関するクエリを受け取った場合には全てのshardにクエリを送信する mongos
もしくはcluster内でランダムに選択されたshardで、それぞれの結果を収集しソートなどを行います。これをSHARD_MERGE
ステージと呼ぶ。
Setting Up a Sharded Cluster
config server
の実態はMongoDBのReplicaSetです
ただし、.sharding.clusterRole: configsvr
を設定ファイルに追加する必要がある
.security.keyFile
をレクチャーでは利用しているが、本番環境においてはX509証明書を利用することになる
既存の単一のReplicaSetをsharding構成にRolling Upgradeするには下記の手順を取る
- CSRS (= Config Server Replica Set) を起動させる
mongos
を起動させる
- データを保存する必要が無いため設定ファイルで
dbpath
プロパティが無い
mongos
はconfig server
で作成したユーザを継承する
config server
の向き先を設定する必要がある
- 既存のReplicaSetのnodeの設定を変更して再起動する
.sharding.clusterRole: shardsvr
を設定にいれる
- Secondaryをそれぞれshutdownする
- PrimaryでstepDownを行い、他のnodeにPrimaryの役割を引き渡した後にshutdownする
mongos
からshardを追加する
sh.addShard( "rs1/mongodb0.example.net:27018" )
- ReplicaSetの中の1つのノードを指定するだけでPrimaryを認識可能
参考:
Configuration File Options — MongoDB Manual
ConfigDB
MongoDBが内部的に使うものなので、ユーザ側でデータの書き込みは行わないが有用な情報を読み取ることが出来る
use config
# databaseが幾つにpartitioningされているか
db.databases.find().pretty()
# collectionのshardkeyに関する情報
db.collections.find().pretty()
# shardとして利用しているReplicaSetの情報
db.shards.find().pretty()
# chunkがshardkeyのどの範囲を持っているかとどのshardに属するか
db.chunks.find().pretty()
# mongosの情報
db.mongos.find().pretty()
Shard Keys
sharded collectionのデータを分割して、cluster内のshardに分割するために使用する、indexed field(s) をshard keyと言う
chunkはshard keyを使ってdocumentを分けた論理的なグループのこと
shard keyとして選択したフィールドの値によって、各chunkのinclusive lower boundとexclusive upper boundが決まる(e.g. 1 <= x < 6)
新しいdocumentをcollectionに書き込むたびに、mongosルータはどのshardにそのdocumentのkey valueに対応するchunkがあるかを確認し、そのshardのみにdocumentを送る。
つまり、挿入されるdocument全てにshard keyの項目が必要になる。
shardingを行う手順
- databaseのshardingを有効に:
sh.enableSharding("m103")
- indexを作成:
db.products.createIndex( { "sku": 1 } )
- shard keyを選択:
sh.shardCollection( "m103.products", { "sku": 1 } )
- 📝 shard keyはimmutableとLecture動画で言われているが、shard fieldがimmutableな
_id
で無い限り、refineCollectionShardKey
で更新することができる
Picking a Good Shard Key
What makes a Good Shard Key?
- High Cardinality
- Cardinalityが高ければChunkが増えshardの数も増えるのでクラスタの成長を妨げることがない
- 例えばbool値をkeyにした場合には、上限が2chunkになりshardも2つまでになってしまいます
- Low Frequency
- frequencyはデータの中でuniqueな値が発生する頻度を表す
- 例えばアメリカの州をshard keyにしたとして、90%の確率で「New York」のdocumentが挿入される場合、書き込みの90%以上が1つのシャードに行くことになる = HotSpot!
- Non-Monotonically Change
- 単調に変化する値(e.g. タイムスタンプやID)は、chunkが下限と上限を持つ性質上、相性が悪い
- Monotonicなshard keyを分散させるためには後述の
hashed shard key
を利用する必要がある
shard keyの選定でもう一つ重要なことはread isolation
です。よく実行するクエリに対応しているかどうかを検討する必要がある
shard keyを条件にしたクエリであれば、多くの場合は1つのshardにアクセスするだけで済むが、そうでなければ全てのshardにアクセスするscatter gather
な操作になってしまい時間がかかる
shard keyを選ぶときに注意する点
- 1度shardingしたcollectionはunsharding出来ない
- 1度shardingしたcollectionはshard keyを更新できない(条件によっては可能)
- shard keyのvalueは更新できない(これも可能と全セクションで言及があった)
Hashed Shard Keys
hashed shard key
を利用する場合、MongoDBはshard keyに指定しているField Valueのhash値を使ってどのchunkにdocumentを置くか決めることになる。
実際に保管するデータがhash値になるわけではなく、shard keyのベースとなるindex自体がhash値で保存される。データをより均等に分散させることができる。
前述したようにMonotonicな値でもちゃんと分散されるようになる。
hashed shard key
の欠点
- shard key fieldに対する範囲のクエリは単一のshardではなく複数shardに投げられることになる
- データを地理的なグループに分離できない
- 利用できるのは単一のField Shard Keyのみで、配列や複合インデックスはサポートしていないし、hash化されているのでsortも速くない
Chunks
Chunkはdocumentsの論理的なグループである config server
が保持する最も重要な情報の1つはchunkとshardのマッピングである
chunkが作成されてからの流れは下記のような形
- collectionをshardingした瞬間に、1つの初期chunkを定義する
- この初期chunkは$minKeyから$maxKeyまでの範囲にある
- 時間とともに初期chunkを複数のchunkに分割してshard間でデータが均等になるようにする
shard内のchunk数を決める要素はshard keyのcardinalityの他にchunk sizeがある
デフォルトのChunk Sizeは64MB
設定によって1MB <= ChunkSize <= 1024MB
の幅で変更することが可能
Chunk Sizeは稼働中に変更することが可能だが、新しいデータを入ってこないとmongosはアクションを起こさないのですぐにはchunk数に反映されない可能性がある
Jumbo chunkという概念がある
新しいdocumentの90%が同じshard keyを持っていたりすると、定義されたchunk sizeよりも大きくなる可能性が高い。そうなった場合は、Jumbo chunkとしてマークされる。Jumbo chunkは大きくて動かせないという判断をされる。
これを避けるためにも、Shard KeyのFrequencyの考慮は大事だ
Balancing
config server
のReplicaSetのPrimaryで実行されているbalancer
プロセスがshard間でchunkを移動させてデータを均等にしている
balancer
は1ラウンドで複数のMigrationを並行に行うことができるが、1つのノードが複数のMigrationプロセスに一度に参加することは出来ない
balancer
はデータの移動だけではなく、必要に応じてchunkの分割を行う
balancer
は特にユーザの入力や指示を必要としないが、開始停止や設定の変更など行うこともできる
Targeted Queries vs Scatter Gather
Targeted Queryは全てのshardにクエリを飛ばさなくて済むクエリ
combound indexを利用したshard keyを設定することで、scatter gatherなクエリを避けることができる
# Shard Key
{ "sku": 1, "type": 1, "name": 1 }
# Targetable Queries
db.products.find( { "sku": … } )
db.products.find( { "sku": …, "type": … } )
db.products.find( { "sku": …, "type": …, "name": … } )
db.products.find( { "type": …, "name": …, "sku": … } )
# Scatter Gather
db.products.find( "type": … )
db.products.find( "name": … )
db.products.find({"sku" : 1000000749 }).explain()
のようにexplainでクエリがtargetedになっているかを確認できる
winningPlan.stage
の値がSINGLE_SHARD
になっているか
inputStage
の値がIXSCAN
(index scan)になっているか
Targeted Queryにはshard keyが必須になることが大事!
まとめ
ちゃんとモチベーションを維持しつつ進められるような仕組みになっていた。
次はM201のPerformanceをやりたいところだけど、Datadogも触りたいので一旦後回しにしちゃいそう。
この記事なんと2万字になっててビビる。