概要
前回 MongoDB Write Concern - Carpe Diem にてWrite Concernについて説明しました。
今回はRead Concernについて説明します。Read Concernはデータの分離性・一貫性・最新性を考慮する際に気をつけるべき設定です。
環境
- MongoDB 3.6+
前提知識
Point-in-time snapshot (MVCC)
でも触れましたが、異なる時間から見たデータベースのデータを参照することによる不整合(読み取りスキュー)を防ぐためにスナップショットという概念があります。
WiredTigerではMVCC(Multi-Version Concurrency Control)を用いてそれを実現しています。
ざっくり説明するとRead/Write操作時にスナップショットデータを取得し、そのスナップショットに対してRead/Writeすることでそれぞれ独立したオペレーションを実現できます。
majority write snapshotの更新
レプリケーションの場合、enableMajorityReadConcern: true
(デフォルトtrue)であれば過半数にwriteされたかどうかがスナップショットで管理されます。
そしてそれが更新されたかどうかがRead Concernの判断要素になります。
この更新タイミングは
- Primaryであればdurableになった(=過半数のデータノードからackが返った)時点
- SecondaryであればPrimaryから更新通知を受け取った時点
になります。
具体的なイメージは以下です。
時間軸で表すと
この⑦が完了した時間が公式の時間軸でいうt5
にあたります。
上図ではw: "majority"
を指定していますが、write concernに関わらず過半数にwriteされればスナップショットは更新されます。
Durable(データ耐久性がある)かどうか
Read Concernではデータがdurableかどうかも判断要素になります。
durableかどうかは構成によってことなり、以下の定義となっています。
構成 | durableといえる状態 |
---|---|
Standalone | journalファイルに書き込まれたら |
ReplicaSet | 過半数のvoting members(投票可能なデータノード)のjournalファイルに書き込まれたら |
ref: https://www.mongodb.com/docs/manual/reference/glossary/#std-term-durable
Read Concern
Read Concern Level
Read Concern Levelには以下があります。
- local
- available
- majority
- linearizable
- snapshot
今回は非トランザクションのケースを想定してsnapshot
以外についてフォーカスします。
local
local
レベルの場合、そのノードにあるデータがそのまま返ります。
上図ではPrimaryはABC
全てのデータを持っており、クライアントがアクセスすればABC
が返ります(=最新のデータが返る)。
Secondaryもその時点で持っているデータを返します。
時間軸で考えると
先の時間軸で言うと以下です。
ノード | いつ新しいデータが取得できるか |
---|---|
Primary | t0以降 |
Secondary1 | t1以降 |
Secondary2 | t2以降 |
メリット
- 最新のデータを早くに取得する事ができる
デメリット
available
available
はシャード構成でなければlocal
と同様です。
シャード構成の場合チャンクのマイグレーション下でorphaned documentsも返せますが、データ整合性としては下がります。
majority
majority
ではdurableかつ直近w: "majority"
で書き込まれたことがsnapshotにも反映されたデータが返ってきます。
上図ではC
はまだレプリカセットに伝播していないため、PrimaryにアクセスしてもAB
が返ります(=最新ではない)。
またSecondary(赤)にはまだデータB
すら反映されていないため、アクセスした場合A
が返ります。
時間軸で考えると
先の時間軸で言うと以下です。
ノード | いつ新しいデータが取得できるか |
---|---|
Primary | t3以降 |
Secondary1 | t5以降 |
Secondary2 | t6以降 |
t3の時点でPrimaryとSecondary1はdurableなデータを保持できたと言えますが、Secondary1はまだsnapshotの更新ができていないためPrimaryに比べてやや遅れて新しいデータを返すようになります。
このsnapshot更新までの時間は不透明であるため、これを考慮した上でread after write整合性を保ちたい場合はCausal Consistencyという仕組みを利用する必要があります。
メリット
- データ耐久性を持つ(Primaryが落ちても巻き戻らない)
- レプリケーションの反映を待つ分
local
よりも同じデータが返りやすい
デメリット
local
よりも最新のデータの反映が遅い- アクセスするノードによって異なるデータが返る可能性がある
linearizable
線形化可能なデータ整合性を保証します。
これまでのRead Concernは並行して実行されたwriteは考慮されませんでした(=writeにブロックされない)が、linearizable
ではそのwriteデータが過半数のノードに反映されるのを待ち、レスポンスにもその結果を含めます。
概念的に処理が直列化されるので、高いデータ一貫性を保証します。
トランザクションの分離レベルで出てくる用語 - Carpe Diem
で説明したSerializableと同様の概念です。
時間軸で考えると
上図のように同時に書き込まれたデータが過半数に反映されるまでブロックされ、ack後にレスポンスが返ります。
メリット
- データが一貫している(Primaryからのみ読むためSecondaryとのズレを考慮しなくていい)
- データ耐久性を持つ(Primaryが落ちても巻き戻らない)
- 最新のデータを読み出せる(直前のwriteも反映されている)
デメリット
- Primaryに限定されるため負荷分散できない
- 直前に並行してwriteが走った場合、線形性を保証するためデータが反映されるまで待つ(=
local
、majority
に比べ大幅にレイテンシが増える)
Goでの実装
GoではClientを生成する際に指定することができます。
mongoURI := "mongodb://server:port/" opts := options.Client().ApplyURI(mongoURI). SetWriteConcern(writeconcern.New(writeconcern.WMajority())). SetReadConcern(readconcern.Majority()). SetReadPreference(readpref.SecondaryPreferred()) client, err := mongo.NewClient(opts) if err != nil { panic(err) }
linearizable
以外は負荷分散のためRead Preferenceを付ける(デフォルトはPrimary)のが良いでしょう。
まとめ
MongoDBのRead Concernについて説明しました。選定する際は
- 最新のデータを取得したい
- 耐久性のあるデータを取得したい
- 低レイテンシで取得したい
- 負荷分散させたい
これらの軸をトレードオフにRead Concernを指定することになるでしょう。